mirror of
https://github.com/nlohmann/json.git
synced 2026-10-22 22:08:55 +00:00
The previous change handles plain strings of up to 16 characters with two overlapping 8-byte words. With SSE2 or NEON, two overlapping 16-byte vectors do the same for 16 to 32 characters: one test for a quote, backslash, control character, DEL or non-ASCII byte over both, and two stores. Targets without either keep the limit of 16. Apple M1, Apple clang, retired instructions per dump(), on top of the previous changes (it replaces the table of #5878 for these strings): keys and values of 1..40 characters 7.07M -> 4.94M (-30 %) twitter 4.15M -> 3.30M (-21 %) paths 9.09M -> 8.08M (-11 %) citm_catalog 5.76M -> 5.53M (-4 %) jeopardy 461M -> 451M (-2 %) Linux x86-64 (callgrind), gcc 13 / clang 18: twitter -11 % / -9 %, keys and values of 1..40 characters -19 % / -17 %, paths -7 % / -7 %. Documents without strings in that range are unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2160 lines
85 KiB
C++
2160 lines
85 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#pragma once
|
|
|
|
#include <algorithm> // remove, fill, find, none_of, min
|
|
#include <array> // array
|
|
#include <clocale> // localeconv, lconv
|
|
#include <cmath> // isfinite
|
|
#include <cstddef> // size_t, ptrdiff_t
|
|
#include <cstdint> // uint8_t
|
|
#include <cstdio> // snprintf
|
|
#include <cstring> // memcpy, memset
|
|
#include <iterator> // next
|
|
#include <limits> // numeric_limits
|
|
#include <string> // string, char_traits
|
|
#include <type_traits> // is_same
|
|
#include <vector> // vector
|
|
|
|
#include <nlohmann/detail/conversions/to_chars.hpp>
|
|
#include <nlohmann/detail/exceptions.hpp>
|
|
#include <nlohmann/detail/input/string_scan.hpp>
|
|
#include <nlohmann/detail/macro_scope.hpp>
|
|
#include <nlohmann/detail/meta/cpp_future.hpp>
|
|
#include <nlohmann/detail/output/error_handler.hpp>
|
|
#include <nlohmann/detail/output/output_adapters.hpp>
|
|
#include <nlohmann/detail/recursion_depth_limit.hpp>
|
|
#include <nlohmann/detail/string_concat.hpp>
|
|
#include <nlohmann/detail/string_utils.hpp>
|
|
#include <nlohmann/detail/value_t.hpp>
|
|
|
|
// the short-string fast path of the serializer assembles words from little-endian
|
|
// loads, see serializer::put_short_quoted()
|
|
#if defined(_MSC_VER) || defined(__x86_64__) || defined(__i386__) || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
|
#define JSON_DUMP_SHORT_STRINGS 1
|
|
#else
|
|
#define JSON_DUMP_SHORT_STRINGS 0
|
|
#endif
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
namespace detail
|
|
{
|
|
|
|
///////////////////
|
|
// serialization //
|
|
///////////////////
|
|
|
|
template<typename BasicJsonType>
|
|
class serializer
|
|
{
|
|
using string_t = typename BasicJsonType::string_t;
|
|
using number_float_t = typename BasicJsonType::number_float_t;
|
|
using number_integer_t = typename BasicJsonType::number_integer_t;
|
|
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
|
/// type of the absolute value of an integer: number_unsigned_t, or its
|
|
/// unsigned counterpart if it is signed (it may be number_integer_t, #2573)
|
|
using number_magnitude_t = typename std::conditional <
|
|
std::is_integral<number_unsigned_t>::value && std::is_signed<number_unsigned_t>::value,
|
|
std::make_unsigned<number_unsigned_t>,
|
|
std::enable_if<true, number_unsigned_t >>::type::type;
|
|
using binary_char_t = typename BasicJsonType::binary_t::value_type;
|
|
|
|
public:
|
|
/*!
|
|
@param[in] s output adapter to serialize to; not owned by the serializer,
|
|
so it must outlive it (it lives at the call site)
|
|
@param[in] ichar indentation character to use
|
|
@param[in] pretty_print_ whether the output shall be pretty-printed
|
|
@param[in] ensure_ascii_ If @a ensure_ascii_ is true, all non-ASCII
|
|
characters in the output are escaped with `\\uXXXX` sequences, and the
|
|
result consists of ASCII characters only.
|
|
@param[in] indent_step_ the indent level
|
|
@param[in] error_handler_ how to react on decoding errors
|
|
|
|
None of @a pretty_print_, @a ensure_ascii_ and @a indent_step_ change over
|
|
the life of the serializer, so they are captured once here instead of
|
|
being threaded through every call to @ref dump, @ref dump_internal and
|
|
@ref dump_iteratively.
|
|
*/
|
|
serializer(output_adapter_protocol<char>& s, const char ichar, // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): the buffers are written before they are read
|
|
const bool pretty_print_ = false,
|
|
const bool ensure_ascii_ = false,
|
|
const std::size_t indent_step_ = 0,
|
|
error_handler_t error_handler_ = error_handler_t::strict)
|
|
: o(&s)
|
|
, indent_char(ichar)
|
|
, pretty_print(pretty_print_)
|
|
, ensure_ascii(ensure_ascii_)
|
|
, indent_step(indent_step_)
|
|
, error_handler(error_handler_)
|
|
{}
|
|
|
|
// deleted because of pointer members
|
|
serializer(const serializer&) = delete;
|
|
serializer& operator=(const serializer&) = delete;
|
|
serializer(serializer&&) = delete;
|
|
serializer& operator=(serializer&&) = delete;
|
|
~serializer() = default;
|
|
|
|
/*!
|
|
@brief internal implementation of the serialization function
|
|
|
|
This function is called by the public member function dump and organizes
|
|
the serialization internally. The indentation level is propagated as
|
|
additional parameter. Arrays and objects are serialized without recursion,
|
|
however deeply they are nested.
|
|
|
|
- strings and object keys are escaped using @ref dump_escaped
|
|
- integer numbers are converted using a digit-pair lookup table (@ref dump_integer)
|
|
- floating-point numbers are converted to a string using @ref dump_float, which
|
|
uses `to_chars` for IEEE-754 types and `snprintf` otherwise
|
|
- binary values are serialized as objects containing the subtype and the
|
|
byte array
|
|
|
|
@param[in] val value to serialize
|
|
@param[in] current_indent the current indent level (only used internally)
|
|
*/
|
|
void dump(const BasicJsonType& val,
|
|
const std::size_t current_indent = 0)
|
|
{
|
|
dump_internal(val, current_indent);
|
|
flush();
|
|
}
|
|
|
|
JSON_PRIVATE_UNLESS_TESTED:
|
|
/*!
|
|
@brief worker for @ref dump
|
|
|
|
Identical in behavior to the historical @ref dump, but writes into the
|
|
serializer's internal @ref write_buffer instead of issuing a virtual call
|
|
per token. The public @ref dump wraps this and flushes the buffer once the
|
|
top-level value has been serialized.
|
|
|
|
Serializing a container descends into its elements, so a value nested deeply
|
|
enough used to exhaust the call stack and terminate the process with no
|
|
exception to catch. The descent is bounded here: once @ref recursion_depth_limit
|
|
levels have been entered, @ref dump_iteratively writes out what is left
|
|
without the call stack. A value nested less deeply than that - all but a
|
|
vanishing minority - is written by exactly the code that always wrote it.
|
|
|
|
@sa https://github.com/nlohmann/json/issues/5387
|
|
*/
|
|
void dump_internal(const BasicJsonType& val,
|
|
const std::size_t current_indent = 0,
|
|
const std::size_t depth = 0)
|
|
{
|
|
switch (val.m_data.m_type)
|
|
{
|
|
case value_t::object:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
|
{
|
|
dump_iteratively(val, current_indent);
|
|
return;
|
|
}
|
|
|
|
if (val.m_data.m_value.object->empty())
|
|
{
|
|
put_literal("{}");
|
|
return;
|
|
}
|
|
|
|
if (pretty_print)
|
|
{
|
|
put_literal("{\n");
|
|
|
|
// variable to hold indentation for recursive calls
|
|
const auto new_indent = next_indent(current_indent, indent_step);
|
|
|
|
// first n-1 elements
|
|
auto i = val.m_data.m_value.object->cbegin();
|
|
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
|
{
|
|
put_indent(new_indent);
|
|
put_quoted(i->first, "\": ");
|
|
dump_element(i->second, new_indent, depth + 1);
|
|
put_literal(",\n");
|
|
}
|
|
|
|
// last element
|
|
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
|
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
|
put_indent(new_indent);
|
|
put_quoted(i->first, "\": ");
|
|
dump_element(i->second, new_indent, depth + 1);
|
|
|
|
put_char('\n');
|
|
put_indent(current_indent);
|
|
put_char('}');
|
|
}
|
|
else
|
|
{
|
|
put_char('{');
|
|
|
|
// first n-1 elements
|
|
auto i = val.m_data.m_value.object->cbegin();
|
|
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
|
{
|
|
put_quoted(i->first, "\":");
|
|
dump_element(i->second, current_indent, depth + 1);
|
|
put_char(',');
|
|
}
|
|
|
|
// last element
|
|
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
|
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
|
put_quoted(i->first, "\":");
|
|
dump_element(i->second, current_indent, depth + 1);
|
|
|
|
put_char('}');
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
case value_t::array:
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
|
{
|
|
dump_iteratively(val, current_indent);
|
|
return;
|
|
}
|
|
|
|
if (val.m_data.m_value.array->empty())
|
|
{
|
|
put_literal("[]");
|
|
return;
|
|
}
|
|
|
|
if (pretty_print)
|
|
{
|
|
put_literal("[\n");
|
|
|
|
// variable to hold indentation for recursive calls
|
|
const auto new_indent = next_indent(current_indent, indent_step);
|
|
|
|
// first n-1 elements
|
|
for (auto i = val.m_data.m_value.array->cbegin();
|
|
i != val.m_data.m_value.array->cend() - 1; ++i)
|
|
{
|
|
put_indent(new_indent);
|
|
dump_element(*i, new_indent, depth + 1);
|
|
put_literal(",\n");
|
|
}
|
|
|
|
// last element
|
|
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);
|
|
|
|
put_char('\n');
|
|
put_indent(current_indent);
|
|
put_char(']');
|
|
}
|
|
else
|
|
{
|
|
put_char('[');
|
|
|
|
// first n-1 elements
|
|
for (auto i = val.m_data.m_value.array->cbegin();
|
|
i != val.m_data.m_value.array->cend() - 1; ++i)
|
|
{
|
|
dump_element(*i, current_indent, depth + 1);
|
|
put_char(',');
|
|
}
|
|
|
|
// last element
|
|
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
|
dump_element(val.m_data.m_value.array->back(), current_indent, depth + 1);
|
|
|
|
put_char(']');
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
case value_t::string:
|
|
case value_t::binary:
|
|
case value_t::boolean:
|
|
case value_t::number_integer:
|
|
case value_t::number_unsigned:
|
|
case value_t::number_float:
|
|
case value_t::discarded:
|
|
case value_t::null:
|
|
default:
|
|
dump_scalar(val, current_indent);
|
|
return;
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
Emits the same bytes as @ref dump_internal, keeping the containers it has
|
|
entered on an explicit stack instead of descending into them. Only reached
|
|
for values nested deeper than @ref recursion_depth_limit, which is why it is not
|
|
written for speed: walking every value this way measured up to 20% slower on
|
|
object-heavy documents than letting the compiler drive the descent.
|
|
*/
|
|
void dump_iteratively(const BasicJsonType& val,
|
|
const std::size_t current_indent = 0)
|
|
{
|
|
// Scalars, empty containers and binary values are written by dump_value
|
|
// alone, so nothing is allocated for them: only a container with
|
|
// elements is ever pushed.
|
|
std::vector<dump_frame> stack;
|
|
|
|
dump_value(val, current_indent, stack);
|
|
|
|
while (!stack.empty())
|
|
{
|
|
dump_frame& frame = stack.back();
|
|
|
|
if (frame.value->m_data.m_type == value_t::object)
|
|
{
|
|
const auto* object = frame.value->m_data.m_value.object;
|
|
|
|
if (frame.object_it == object->cend())
|
|
{
|
|
if (pretty_print)
|
|
{
|
|
put_char('\n');
|
|
put_indent(frame.current_indent);
|
|
}
|
|
|
|
put_char('}');
|
|
stack.pop_back();
|
|
continue;
|
|
}
|
|
|
|
// the separator goes in front of every element but the first,
|
|
// which puts exactly one between each pair and none at the end
|
|
if (frame.object_it != object->cbegin())
|
|
{
|
|
if (pretty_print)
|
|
{
|
|
put_literal(",\n");
|
|
}
|
|
else
|
|
{
|
|
put_char(',');
|
|
}
|
|
}
|
|
|
|
if (pretty_print)
|
|
{
|
|
put_indent(frame.child_indent);
|
|
}
|
|
|
|
if (pretty_print)
|
|
{
|
|
put_quoted(frame.object_it->first, "\": ");
|
|
}
|
|
else
|
|
{
|
|
put_quoted(frame.object_it->first, "\":");
|
|
}
|
|
|
|
const BasicJsonType& element = frame.object_it->second;
|
|
++frame.object_it;
|
|
|
|
// read everything needed from the frame before this: entering a
|
|
// container pushes another one and can move them all
|
|
const std::size_t element_indent = frame.child_indent;
|
|
dump_value(element, element_indent, stack);
|
|
}
|
|
else
|
|
{
|
|
const auto* array = frame.value->m_data.m_value.array;
|
|
|
|
if (frame.array_it == array->cend())
|
|
{
|
|
if (pretty_print)
|
|
{
|
|
put_char('\n');
|
|
put_indent(frame.current_indent);
|
|
}
|
|
|
|
put_char(']');
|
|
stack.pop_back();
|
|
continue;
|
|
}
|
|
|
|
if (frame.array_it != array->cbegin())
|
|
{
|
|
if (pretty_print)
|
|
{
|
|
put_literal(",\n");
|
|
}
|
|
else
|
|
{
|
|
put_char(',');
|
|
}
|
|
}
|
|
|
|
if (pretty_print)
|
|
{
|
|
put_indent(frame.child_indent);
|
|
}
|
|
|
|
const BasicJsonType& element = *frame.array_it;
|
|
++frame.array_it;
|
|
|
|
// see above
|
|
const std::size_t element_indent = frame.child_indent;
|
|
dump_value(element, element_indent, stack);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
/// @brief a container that has been opened but not closed yet
|
|
struct dump_frame
|
|
{
|
|
dump_frame(const BasicJsonType* value_, const std::size_t current_indent_,
|
|
const std::size_t child_indent_) noexcept
|
|
: value(value_)
|
|
, current_indent(current_indent_)
|
|
, child_indent(child_indent_)
|
|
{}
|
|
|
|
/// the object or array being serialized
|
|
const BasicJsonType* value;
|
|
/// the element to serialize next; which of the two is live follows from
|
|
/// the type of @a value. They are kept side by side rather than in a
|
|
/// union, which would need its special members written out by hand, see
|
|
/// detail/iterators/internal_iterator.hpp
|
|
typename BasicJsonType::object_t::const_iterator object_it{};
|
|
typename BasicJsonType::array_t::const_iterator array_it{};
|
|
/// the indentation of the container itself, used by its closing bracket
|
|
std::size_t current_indent;
|
|
/// the indentation of the container's elements
|
|
std::size_t child_indent;
|
|
};
|
|
|
|
/*!
|
|
@brief serialize the value @a val, but not the elements of a container
|
|
|
|
An object or array with elements is opened and pushed onto @a stack for
|
|
@ref dump_iteratively to walk; everything else - including a binary value,
|
|
which looks like an object but has no elements to descend into - is written
|
|
out in full by @ref dump_scalar.
|
|
*/
|
|
void dump_value(const BasicJsonType& val,
|
|
const std::size_t current_indent,
|
|
std::vector<dump_frame>& stack)
|
|
{
|
|
switch (val.m_data.m_type)
|
|
{
|
|
case value_t::object:
|
|
{
|
|
if (val.m_data.m_value.object->empty())
|
|
{
|
|
put_literal("{}");
|
|
return;
|
|
}
|
|
|
|
std::size_t child_indent = current_indent;
|
|
|
|
if (pretty_print)
|
|
{
|
|
put_literal("{\n");
|
|
child_indent = next_indent(current_indent, indent_step);
|
|
}
|
|
else
|
|
{
|
|
put_char('{');
|
|
}
|
|
|
|
stack.emplace_back(&val, current_indent, child_indent);
|
|
stack.back().object_it = val.m_data.m_value.object->cbegin();
|
|
return;
|
|
}
|
|
|
|
case value_t::array:
|
|
{
|
|
if (val.m_data.m_value.array->empty())
|
|
{
|
|
put_literal("[]");
|
|
return;
|
|
}
|
|
|
|
std::size_t child_indent = current_indent;
|
|
|
|
if (pretty_print)
|
|
{
|
|
put_literal("[\n");
|
|
child_indent = next_indent(current_indent, indent_step);
|
|
}
|
|
else
|
|
{
|
|
put_char('[');
|
|
}
|
|
|
|
stack.emplace_back(&val, current_indent, child_indent);
|
|
stack.back().array_it = val.m_data.m_value.array->cbegin();
|
|
return;
|
|
}
|
|
|
|
case value_t::string:
|
|
case value_t::binary:
|
|
case value_t::boolean:
|
|
case value_t::number_integer:
|
|
case value_t::number_unsigned:
|
|
case value_t::number_float:
|
|
case value_t::discarded:
|
|
case value_t::null:
|
|
default:
|
|
dump_scalar(val, current_indent);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
@brief serialize the value @a val, which is neither an object nor an array
|
|
|
|
Shared by @ref dump_internal and @ref dump_value, so that a value is written
|
|
the same way however deeply it is nested. A binary value is written out here
|
|
in full: it looks like an object, but has no elements to descend into.
|
|
|
|
@param[in] val value to serialize; not an object or array
|
|
@param[in] current_indent the indentation of @a val, used for a
|
|
pretty-printed binary value
|
|
*/
|
|
void dump_scalar(const BasicJsonType& val, const std::size_t current_indent)
|
|
{
|
|
switch (val.m_data.m_type)
|
|
{
|
|
case value_t::string:
|
|
{
|
|
dump_string_value(*val.m_data.m_value.string);
|
|
return;
|
|
}
|
|
|
|
case value_t::binary:
|
|
{
|
|
dump_binary_value(current_indent, *val.m_data.m_value.binary);
|
|
return;
|
|
}
|
|
|
|
case value_t::boolean:
|
|
{
|
|
if (val.m_data.m_value.boolean)
|
|
{
|
|
put_literal("true");
|
|
}
|
|
else
|
|
{
|
|
put_literal("false");
|
|
}
|
|
return;
|
|
}
|
|
|
|
case value_t::number_integer:
|
|
{
|
|
dump_signed_value(val.m_data.m_value.number_integer);
|
|
return;
|
|
}
|
|
|
|
case value_t::number_unsigned:
|
|
{
|
|
dump_unsigned_value(val.m_data.m_value.number_unsigned);
|
|
return;
|
|
}
|
|
|
|
case value_t::number_float:
|
|
{
|
|
dump_float_value(val.m_data.m_value.number_float);
|
|
return;
|
|
}
|
|
|
|
case value_t::discarded:
|
|
{
|
|
put_literal("<discarded>");
|
|
return;
|
|
}
|
|
|
|
case value_t::null:
|
|
{
|
|
put_literal("null");
|
|
return;
|
|
}
|
|
|
|
case value_t::object: // LCOV_EXCL_LINE
|
|
case value_t::array: // LCOV_EXCL_LINE
|
|
default: // LCOV_EXCL_LINE
|
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
|
}
|
|
}
|
|
|
|
/// @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
|
|
|
|
A very large @a indent_step can wrap the unsigned accumulation on deep
|
|
nesting, which would silently truncate the indentation. Far harder to reach
|
|
now that the accumulator is a std::size_t, but still reachable where that is
|
|
32 bits wide.
|
|
*/
|
|
static std::size_t next_indent(const std::size_t current_indent, const std::size_t indent_step)
|
|
{
|
|
const std::size_t new_indent = current_indent + indent_step;
|
|
JSON_ASSERT(new_indent >= current_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)
|
|
{
|
|
if (!copy_plain_vectors(d, p, n))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
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 16 to 32 bytes [@a p, @a p + @a n) to @a d with two overlapping
|
|
/// vectors, unless one of them is a quote, a backslash, a control
|
|
/// character, DEL or not ASCII
|
|
static bool copy_plain_vectors(char* d, const char* p, const std::size_t n) noexcept
|
|
{
|
|
#if JSON_SCAN_NEON
|
|
const uint8x16_t a = vld1q_u8(reinterpret_cast<const std::uint8_t*>(p));
|
|
const uint8x16_t b = vld1q_u8(reinterpret_cast<const std::uint8_t*>(p + n - 16));
|
|
const uint8x16_t special = vorrq_u8(vorrq_u8(vorrq_u8(vceqq_u8(a, vdupq_n_u8('"')), vceqq_u8(a, vdupq_n_u8('\\'))),
|
|
vorrq_u8(vceqq_u8(b, vdupq_n_u8('"')), vceqq_u8(b, vdupq_n_u8('\\')))),
|
|
vorrq_u8(vorrq_u8(vcltq_s8(vreinterpretq_s8_u8(a), vdupq_n_s8(0x20)), vcltq_s8(vreinterpretq_s8_u8(b), vdupq_n_s8(0x20))),
|
|
vorrq_u8(vceqq_u8(a, vdupq_n_u8(0x7F)), vceqq_u8(b, vdupq_n_u8(0x7F)))));
|
|
if (vmaxvq_u8(special) != 0)
|
|
{
|
|
return false;
|
|
}
|
|
vst1q_u8(reinterpret_cast<std::uint8_t*>(d), a);
|
|
vst1q_u8(reinterpret_cast<std::uint8_t*>(d + n - 16), b);
|
|
#else
|
|
const __m128i a = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
|
const __m128i b = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p + n - 16))); // NOLINT(bugprone-casting-through-void)
|
|
const __m128i quote = _mm_set1_epi8('"');
|
|
const __m128i backslash = _mm_set1_epi8('\\');
|
|
const __m128i space = _mm_set1_epi8(0x20);
|
|
const __m128i del = _mm_set1_epi8(0x7F);
|
|
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(a, quote), _mm_cmpeq_epi8(a, backslash)),
|
|
_mm_or_si128(_mm_cmpeq_epi8(b, quote), _mm_cmpeq_epi8(b, backslash))),
|
|
_mm_or_si128(_mm_or_si128(_mm_cmplt_epi8(a, space), _mm_cmplt_epi8(b, space)),
|
|
_mm_or_si128(_mm_cmpeq_epi8(a, del), _mm_cmpeq_epi8(b, del))));
|
|
if (_mm_movemask_epi8(special) != 0)
|
|
{
|
|
return false;
|
|
}
|
|
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(d)), a); // NOLINT(bugprone-casting-through-void)
|
|
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(d + n - 16)), b); // NOLINT(bugprone-casting-through-void)
|
|
#endif
|
|
return true;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
JSON_PRIVATE_UNLESS_TESTED:
|
|
/*!
|
|
@brief dump escaped string
|
|
|
|
Escape a string by replacing certain special characters by a sequence of an
|
|
escape character (backslash) and another character and other control
|
|
characters by a sequence of "\u" followed by a four-digit hex
|
|
representation. The escaped string is appended to @ref write_buffer.
|
|
|
|
@param[in] s the string to escape
|
|
|
|
Complexity: Linear in the length of string @a s.
|
|
|
|
@note kept out of line: with the short-string attempts in front of it gone
|
|
from here, compilers would otherwise inline it into @ref put_quoted
|
|
and then no longer into the loops that call that
|
|
*/
|
|
JSON_HEDLEY_NEVER_INLINE void dump_escaped(const string_t& s)
|
|
{
|
|
// Short strings of printable ASCII - most object keys and many values -
|
|
// are checked byte by byte against a table and then copied in one go.
|
|
// For them, this is cheaper than starting the vector scanner below,
|
|
// whose setup costs more than such a string takes to scan. The same
|
|
// check serves both values of ensure_ascii.
|
|
//
|
|
// Where put_short_quoted() exists and takes strings of up to 32 bytes,
|
|
// the callers have tried it already, and repeating the attempt only
|
|
// costs time for strings that need escaping or are not ASCII.
|
|
#if !(JSON_DUMP_SHORT_STRINGS && JSON_SCAN_VECTOR)
|
|
if (s.size() <= 32)
|
|
{
|
|
const auto& copyable = ascii_copyable();
|
|
const auto* const data = reinterpret_cast<const unsigned char*>(s.data());
|
|
const std::size_t n = s.size();
|
|
std::size_t k = 0;
|
|
while (k + 4 <= n && copyable[data[k]] != 0 && copyable[data[k + 1]] != 0
|
|
&& copyable[data[k + 2]] != 0 && copyable[data[k + 3]] != 0)
|
|
{
|
|
k += 4;
|
|
}
|
|
while (k < n && copyable[data[k]] != 0)
|
|
{
|
|
++k;
|
|
}
|
|
if (k == n)
|
|
{
|
|
put_string(s, 0, n);
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// dispatch once here rather than test the flag inside the loop: it does
|
|
// not change while a string is written, and folding it lets each of the
|
|
// two scanners be inlined into a loop of its own
|
|
if (ensure_ascii)
|
|
{
|
|
dump_escaped_impl<true>(s);
|
|
}
|
|
else
|
|
{
|
|
dump_escaped_impl<false>(s);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
@brief worker for @ref dump_escaped
|
|
|
|
@a ensure_ascii is a template parameter here so that the branch on it is
|
|
resolved once, outside the loop; see @ref dump_escaped.
|
|
*/
|
|
#ifdef JSON_HEDLEY_MSVC_VERSION
|
|
#pragma warning(push)
|
|
// EnsureAscii is a template parameter; C++11 has no if constexpr
|
|
#pragma warning(disable : 4127) // conditional expression is constant
|
|
#endif
|
|
template<bool EnsureAscii>
|
|
void dump_escaped_impl(const string_t& s)
|
|
{
|
|
std::uint32_t codepoint{};
|
|
std::uint8_t state = UTF8_ACCEPT;
|
|
std::size_t bytes = 0; // number of bytes written to string_buffer
|
|
|
|
// number of bytes written at the point of the last valid byte
|
|
std::size_t bytes_after_last_accept = 0;
|
|
std::size_t undumped_chars = 0;
|
|
|
|
for (std::size_t i = 0; i < s.size(); ++i)
|
|
{
|
|
// Fast path: at a character boundary (state == UTF8_ACCEPT),
|
|
// bulk-copy the longest run of bytes that need no escaping using a
|
|
// SWAR scanner shared with the lexer's contiguous path. The scanner
|
|
// stops exactly at the first byte dump_escaped would handle
|
|
// individually, so that byte is left to the byte-at-a-time path
|
|
// below, keeping escaping output and error diagnostics unchanged.
|
|
//
|
|
// - EnsureAscii == false: string_bulk_run() copies ordinary bytes
|
|
// and complete well-formed UTF-8, stopping at a quote, backslash,
|
|
// control character (< 0x20), or ill-formed/truncated sequence.
|
|
// - EnsureAscii == true: only printable ASCII may be copied
|
|
// verbatim; find_ascii_copyable_run() additionally stops at 0x7F
|
|
// and every non-ASCII byte (>= 0x80), which must be \u-escaped.
|
|
if (state == UTF8_ACCEPT)
|
|
{
|
|
const auto* const data = reinterpret_cast<const unsigned char*>(s.data());
|
|
// A run can only be non-empty when the very first byte is one
|
|
// the scanner may copy, so test that single byte before paying
|
|
// for the scan. Without it, text whose characters all have to be
|
|
// escaped - CJK under ensure_ascii, where every byte is >= 0x80 -
|
|
// runs the scanner once per character only to be told zero.
|
|
std::size_t run = 0;
|
|
if (!EnsureAscii)
|
|
{
|
|
run = string_bulk_run(data + i, s.size() - i);
|
|
}
|
|
else if (is_ascii_copyable(data[i]))
|
|
{
|
|
run = find_ascii_copyable_run(data + i, s.size() - i);
|
|
}
|
|
if (run != 0)
|
|
{
|
|
// emit any bytes still pending in string_buffer first to
|
|
// preserve output order, then write the run directly
|
|
if (bytes != 0)
|
|
{
|
|
put_buffer(string_buffer, bytes);
|
|
bytes = 0;
|
|
}
|
|
put_string(s, i, i + run);
|
|
bytes_after_last_accept = 0;
|
|
undumped_chars = 0;
|
|
i += run;
|
|
if (i >= s.size())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// A byte below 0x60 is a complete code point and needs no
|
|
// UTF-8 decoding. The scanner stops only at a control
|
|
// character, a quote, or a backslash in that range, so look up
|
|
// its escape sequence and write all 8 bytes of the table entry
|
|
// straight into the write buffer; only the length stored in the
|
|
// last byte of the entry is kept.
|
|
if (data[i] < 0x60)
|
|
{
|
|
// emit any bytes still pending in string_buffer first to
|
|
// preserve output order
|
|
if (bytes != 0)
|
|
{
|
|
put_buffer(string_buffer, bytes);
|
|
bytes = 0;
|
|
}
|
|
const auto& escape = ascii_escapes()[data[i]];
|
|
if (JSON_HEDLEY_UNLIKELY(write_buffer.size() - write_buffer_pos < escape.size()))
|
|
{
|
|
flush();
|
|
}
|
|
std::memcpy(write_buffer.data() + write_buffer_pos, escape.data(), escape.size());
|
|
write_buffer_pos += static_cast<std::size_t>(escape[7]);
|
|
bytes_after_last_accept = 0;
|
|
undumped_chars = 0;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
const auto byte = static_cast<std::uint8_t>(s[i]);
|
|
|
|
if (EnsureAscii && state == UTF8_ACCEPT && byte >= 0x80)
|
|
{
|
|
// a run of well-formed multi-byte characters is escaped
|
|
// straight into the write buffer; an ill-formed sequence
|
|
// ends the run and is left to the byte-at-a-time path
|
|
if (bytes != 0)
|
|
{
|
|
put_buffer(string_buffer, bytes);
|
|
bytes = 0;
|
|
bytes_after_last_accept = 0;
|
|
}
|
|
const std::size_t escaped = write_u_escapes(reinterpret_cast<const unsigned char*>(s.data()) + i, s.size() - i);
|
|
if (escaped != 0)
|
|
{
|
|
bytes_after_last_accept = 0;
|
|
undumped_chars = 0;
|
|
i += escaped - 1;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
switch (decode(state, codepoint, byte))
|
|
{
|
|
case UTF8_ACCEPT: // decode found a new code point
|
|
{
|
|
// the code point is at least 0x60 (smaller ones are handled
|
|
// above) and is not printable ASCII (which the scanner
|
|
// copies): it is DEL (0x7F) under EnsureAscii or a non-ASCII
|
|
// character
|
|
if (EnsureAscii)
|
|
{
|
|
JSON_ASSERT(codepoint >= 0x7F);
|
|
if (error_handler == error_handler_t::keep)
|
|
{
|
|
// this character was buffered as raw bytes below
|
|
// in case it turned out to be part of an
|
|
// ill-formed sequence (which is kept as is); now
|
|
// that it decoded to a well-formed code point,
|
|
// undo that and \u-escape it like any other
|
|
// character instead
|
|
bytes = bytes_after_last_accept;
|
|
}
|
|
if (codepoint <= 0xFFFF)
|
|
{
|
|
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
|
|
}
|
|
else
|
|
{
|
|
write_u_escape(bytes, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
|
|
write_u_escape(bytes, static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// copy byte to buffer (all previous bytes of the
|
|
// sequence have been copied in the default case below)
|
|
string_buffer[bytes++] = s[i];
|
|
}
|
|
|
|
// write buffer and reset index; there must be 13 bytes
|
|
// left, as this is the maximal number of bytes to be
|
|
// written ("\uxxxx\uxxxx\0") for one code point
|
|
if (string_buffer.size() - bytes < 13)
|
|
{
|
|
put_buffer(string_buffer, bytes);
|
|
bytes = 0;
|
|
}
|
|
|
|
// remember the byte position of this accept
|
|
bytes_after_last_accept = bytes;
|
|
undumped_chars = 0;
|
|
break;
|
|
}
|
|
|
|
case UTF8_REJECT: // decode found invalid UTF-8 byte
|
|
{
|
|
switch (error_handler)
|
|
{
|
|
case error_handler_t::strict:
|
|
{
|
|
JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", detail::hex_byte(byte)), nullptr));
|
|
}
|
|
|
|
case error_handler_t::ignore:
|
|
case error_handler_t::replace:
|
|
{
|
|
// in case we saw this character the first time, we
|
|
// would like to read it again, because the byte
|
|
// may be OK for itself, but just not OK for the
|
|
// previous sequence
|
|
if (undumped_chars > 0)
|
|
{
|
|
--i;
|
|
}
|
|
|
|
// reset length buffer to the last accepted index;
|
|
// thus removing/ignoring the invalid characters
|
|
bytes = bytes_after_last_accept;
|
|
|
|
if (error_handler == error_handler_t::replace)
|
|
{
|
|
// add a replacement character
|
|
if (EnsureAscii)
|
|
{
|
|
string_buffer[bytes++] = '\\';
|
|
string_buffer[bytes++] = 'u';
|
|
string_buffer[bytes++] = 'f';
|
|
string_buffer[bytes++] = 'f';
|
|
string_buffer[bytes++] = 'f';
|
|
string_buffer[bytes++] = 'd';
|
|
}
|
|
else
|
|
{
|
|
string_buffer[bytes++] = '\xEF';
|
|
string_buffer[bytes++] = '\xBF';
|
|
string_buffer[bytes++] = '\xBD';
|
|
}
|
|
|
|
// write buffer and reset index; there must be 13 bytes
|
|
// left, as this is the maximal number of bytes to be
|
|
// written ("\uxxxx\uxxxx\0") for one code point
|
|
if (string_buffer.size() - bytes < 13)
|
|
{
|
|
put_buffer(string_buffer, bytes);
|
|
bytes = 0;
|
|
}
|
|
|
|
bytes_after_last_accept = bytes;
|
|
}
|
|
|
|
undumped_chars = 0;
|
|
|
|
// continue processing the string
|
|
state = UTF8_ACCEPT;
|
|
break;
|
|
}
|
|
|
|
case error_handler_t::keep:
|
|
{
|
|
// the bytes of this (now abandoned) ill-formed
|
|
// sequence seen so far are already buffered below
|
|
// and are kept unchanged in the output
|
|
if (undumped_chars > 0)
|
|
{
|
|
// the byte that ended the sequence may be OK
|
|
// for itself (e.g., a quote that must still be
|
|
// escaped, or the lead byte of a well-formed
|
|
// code point), so read it again
|
|
--i;
|
|
}
|
|
else
|
|
{
|
|
// a byte that cannot start a sequence (e.g.,
|
|
// 0xFF or a stray continuation byte) is kept
|
|
// as well
|
|
string_buffer[bytes++] = s[i];
|
|
}
|
|
|
|
// write buffer and reset index; there must be 13 bytes
|
|
// left, as this is the maximal number of bytes to be
|
|
// written ("\uxxxx\uxxxx\0") for one code point
|
|
if (string_buffer.size() - bytes < 13)
|
|
{
|
|
put_buffer(string_buffer, bytes);
|
|
bytes = 0;
|
|
}
|
|
|
|
bytes_after_last_accept = bytes;
|
|
undumped_chars = 0;
|
|
|
|
// continue processing the string
|
|
state = UTF8_ACCEPT;
|
|
break;
|
|
}
|
|
|
|
default: // LCOV_EXCL_LINE
|
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
|
}
|
|
break;
|
|
}
|
|
|
|
default: // decode found yet incomplete multibyte code point
|
|
{
|
|
if (!EnsureAscii || error_handler == error_handler_t::keep)
|
|
{
|
|
// code point will not be escaped (or will be kept as
|
|
// is if it turns out to be ill-formed) - copy byte to
|
|
// buffer; dropped again above if it decodes to a
|
|
// well-formed code point that needs \u-escaping
|
|
string_buffer[bytes++] = s[i];
|
|
}
|
|
++undumped_chars;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// we finished processing the string
|
|
if (JSON_HEDLEY_LIKELY(state == UTF8_ACCEPT))
|
|
{
|
|
// write buffer
|
|
if (bytes > 0)
|
|
{
|
|
put_buffer(string_buffer, bytes);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we finish reading, but do not accept: string was incomplete
|
|
switch (error_handler)
|
|
{
|
|
case error_handler_t::strict:
|
|
{
|
|
JSON_THROW(type_error::create(exception_id::invalid_utf8, concat("incomplete UTF-8 string; last byte: 0x", detail::hex_byte(static_cast<std::uint8_t>(s[s.size() - 1]))), nullptr));
|
|
}
|
|
|
|
case error_handler_t::ignore:
|
|
{
|
|
// write all accepted bytes
|
|
put_buffer(string_buffer, bytes_after_last_accept);
|
|
break;
|
|
}
|
|
|
|
case error_handler_t::replace:
|
|
{
|
|
// write all accepted bytes
|
|
put_buffer(string_buffer, bytes_after_last_accept);
|
|
// add a replacement character
|
|
if (EnsureAscii)
|
|
{
|
|
put_literal("\\ufffd");
|
|
}
|
|
else
|
|
{
|
|
put_literal("\xEF\xBF\xBD");
|
|
}
|
|
break;
|
|
}
|
|
|
|
case error_handler_t::keep:
|
|
{
|
|
// write the ill-formed trailing bytes as is; they were
|
|
// buffered above regardless of EnsureAscii
|
|
put_buffer(string_buffer, bytes);
|
|
break;
|
|
}
|
|
|
|
default: // LCOV_EXCL_LINE
|
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
|
}
|
|
}
|
|
}
|
|
#ifdef JSON_HEDLEY_MSVC_VERSION
|
|
#pragma warning(pop)
|
|
#endif
|
|
|
|
private:
|
|
/*!
|
|
@brief append a single character to the write buffer
|
|
|
|
Structural characters ('{', '"', ',', ...) previously went straight to the
|
|
output adapter, one virtual call each. Buffering them and flushing in bulk
|
|
turns those many indirect calls into a single memcpy plus an occasional
|
|
flush, which dominates the cost of serializing object/array-heavy values.
|
|
*/
|
|
void put_char(char c)
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos == write_buffer.size()))
|
|
{
|
|
flush();
|
|
}
|
|
write_buffer[write_buffer_pos++] = c;
|
|
}
|
|
|
|
/*!
|
|
@brief append @a indent indentation characters to the write buffer
|
|
|
|
Writes the indentation straight into the buffer instead of copying it out of
|
|
a pre-grown indentation string, so no auxiliary string has to be sized,
|
|
resized, or kept in sync with the deepest nesting level reached.
|
|
|
|
An indentation wider than the buffer is emitted by filling the buffer with
|
|
the indentation character once and flushing that same content repeatedly:
|
|
flushing does not disturb what the buffer holds, so re-filling it between
|
|
flushes would be redundant work.
|
|
|
|
@note inlined on purpose, as is @ref put_literal: GCC otherwise stops
|
|
inlining both into the pretty-printing loops once @ref dump_escaped
|
|
has grown, which made indented output up to 13% slower
|
|
*/
|
|
JSON_HEDLEY_ALWAYS_INLINE void put_indent(std::size_t indent)
|
|
{
|
|
// closing braces at the outermost level ask for no indentation at all
|
|
if (indent == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// the usual indentation: four words of the indentation character are
|
|
// stored unconditionally and only the part wanted is claimed, which
|
|
// spares the call of memset
|
|
if (JSON_HEDLEY_LIKELY(indent <= 32 && write_buffer_pos + 32 <= write_buffer.size()))
|
|
{
|
|
const std::uint64_t word = 0x0101010101010101ull * static_cast<unsigned char>(indent_char);
|
|
char* const d = write_buffer.data() + write_buffer_pos;
|
|
std::memcpy(d, &word, sizeof(word));
|
|
std::memcpy(d + 8, &word, sizeof(word));
|
|
std::memcpy(d + 16, &word, sizeof(word));
|
|
std::memcpy(d + 24, &word, sizeof(word));
|
|
write_buffer_pos += indent;
|
|
return;
|
|
}
|
|
|
|
const std::size_t capacity = write_buffer.size();
|
|
|
|
// fill whatever room is left in the buffer; this is the whole job
|
|
// whenever the indentation is narrower than the buffer, which is the
|
|
// case for every sane indent_step
|
|
const std::size_t head = (std::min)(indent, capacity - write_buffer_pos);
|
|
std::memset(write_buffer.data() + write_buffer_pos, indent_char, head);
|
|
write_buffer_pos += head;
|
|
indent -= head;
|
|
|
|
if (JSON_HEDLEY_LIKELY(indent == 0))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// the buffer is full and the remainder spans whole buffer-fulls: flush
|
|
// what is pending, then fill the buffer with the indentation character
|
|
// exactly once and hand the same bytes to the adapter as often as needed
|
|
flush();
|
|
std::memset(write_buffer.data(), indent_char, capacity);
|
|
|
|
while (indent >= capacity)
|
|
{
|
|
write_buffer_pos = capacity;
|
|
flush();
|
|
indent -= capacity;
|
|
}
|
|
|
|
// the buffer still holds indentation characters throughout, so the tail
|
|
// only has to be claimed, not written again
|
|
write_buffer_pos = indent;
|
|
}
|
|
|
|
/*!
|
|
@brief append a string literal to the write buffer
|
|
|
|
The length comes from the array bound rather than a hand-written count, so
|
|
it cannot drift out of sync with the literal. A literal always fits into the
|
|
buffer (checked at compile time), so unlike @ref put_string this needs no
|
|
write-through path for oversized runs.
|
|
*/
|
|
template<std::size_t N>
|
|
JSON_HEDLEY_ALWAYS_INLINE void put_literal(const char (&s)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
|
{
|
|
static_assert(N >= 2, "put_literal expects a non-empty string literal");
|
|
// the array bound counts the terminating NUL, which is not written
|
|
constexpr std::size_t length = N - 1;
|
|
static_assert(length < write_buffer_size, "string literal must fit into the write buffer");
|
|
|
|
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + length > write_buffer.size()))
|
|
{
|
|
flush();
|
|
}
|
|
std::memcpy(write_buffer.data() + write_buffer_pos, s, length);
|
|
write_buffer_pos += length;
|
|
}
|
|
|
|
/*!
|
|
@brief append the characters of @a str in [@a start, @a end)
|
|
|
|
The only way to append a run of characters: @a str carries its own bound,
|
|
so the range can be checked against it, which a bare pointer plus a count
|
|
could not do. Runs that do not fit the buffer are written straight through
|
|
the output adapter (after flushing what is pending), so large string and
|
|
number payloads are not copied an extra time.
|
|
*/
|
|
template<typename StringType>
|
|
void put_string(const StringType& str, std::size_t start, std::size_t end)
|
|
{
|
|
JSON_ASSERT(start <= end);
|
|
JSON_ASSERT(end <= str.size());
|
|
|
|
const char* const s = str.data() + start;
|
|
const std::size_t length = end - start;
|
|
|
|
if (JSON_HEDLEY_UNLIKELY(length >= write_buffer.size()))
|
|
{
|
|
flush();
|
|
o->write_characters(s, length);
|
|
return;
|
|
}
|
|
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + length > write_buffer.size()))
|
|
{
|
|
flush();
|
|
}
|
|
std::memcpy(write_buffer.data() + write_buffer_pos, s, length);
|
|
write_buffer_pos += length;
|
|
}
|
|
|
|
/*!
|
|
@brief append the first @a length characters of a fixed-size buffer
|
|
*/
|
|
template<std::size_t N>
|
|
void put_buffer(const std::array<char, N>& buffer, std::size_t length)
|
|
{
|
|
put_string(buffer, 0, length);
|
|
}
|
|
|
|
JSON_PRIVATE_UNLESS_TESTED:
|
|
/*!
|
|
@brief flush the write buffer to the output adapter
|
|
|
|
Writing zero characters is a well-defined no-op for every output adapter, so
|
|
the buffered length is passed through unconditionally (no empty-guard branch
|
|
to leave uncovered).
|
|
|
|
@note dump_escaped() and dump_integer()/dump_float() write into the internal
|
|
write buffer; callers that invoke them directly (rather than through the
|
|
public dump()) must call flush() before inspecting the output.
|
|
*/
|
|
void flush()
|
|
{
|
|
o->write_characters(write_buffer.data(), write_buffer_pos);
|
|
write_buffer_pos = 0;
|
|
}
|
|
|
|
private:
|
|
/*!
|
|
@brief count digits
|
|
|
|
Count the number of decimal (base 10) digits for an input unsigned integer.
|
|
|
|
@param[in] x unsigned integer number to count its digits
|
|
@return number of decimal digits
|
|
*/
|
|
unsigned int count_digits(number_magnitude_t x) noexcept
|
|
{
|
|
unsigned int n_digits = 1;
|
|
for (;;)
|
|
{
|
|
if (x < 10)
|
|
{
|
|
return n_digits;
|
|
}
|
|
if (x < 100)
|
|
{
|
|
return n_digits + 1;
|
|
}
|
|
if (x < 1000)
|
|
{
|
|
return n_digits + 2;
|
|
}
|
|
if (x < 10000)
|
|
{
|
|
return n_digits + 3;
|
|
}
|
|
x = x / 10000u;
|
|
n_digits += 4;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
@brief whether each byte can be written verbatim under either value of ensure_ascii
|
|
|
|
Entry c is 1 for printable ASCII other than '"' and '\\' (see
|
|
is_ascii_copyable()) and 0 otherwise. A table lookup per byte is the
|
|
cheapest test for the short strings handled first in dump_escaped().
|
|
*/
|
|
static const std::array<std::uint8_t, 256>& ascii_copyable() noexcept
|
|
{
|
|
static const std::array<std::uint8_t, 256> copyable =
|
|
{
|
|
{
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10
|
|
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 0x50
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 0x70
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xA0
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xB0
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xC0
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xD0
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xE0
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 0xF0
|
|
}
|
|
};
|
|
return copyable;
|
|
}
|
|
|
|
/*!
|
|
@brief the JSON form of each character below 0x60
|
|
|
|
Entry c holds the escape sequence of character c - the short form for
|
|
\\b, \\t, \\n, \\f, \\r, \\" and \\\\, \\u00xx for the other control characters -
|
|
or c itself if it needs no escaping, padded with zeros to 7 bytes. The last
|
|
byte is the length of the sequence. The 8 bytes of an entry are copied at
|
|
once, so writing an escape needs neither a branch nor a byte-wise loop.
|
|
*/
|
|
static const std::array<std::array<char, 8>, 0x60>& ascii_escapes() noexcept
|
|
{
|
|
static const std::array<std::array<char, 8>, 0x60> escapes =
|
|
{
|
|
{
|
|
{{'\\', 'u', '0', '0', '0', '0', 0, 6}}, // 0x00
|
|
{{'\\', 'u', '0', '0', '0', '1', 0, 6}}, // 0x01
|
|
{{'\\', 'u', '0', '0', '0', '2', 0, 6}}, // 0x02
|
|
{{'\\', 'u', '0', '0', '0', '3', 0, 6}}, // 0x03
|
|
{{'\\', 'u', '0', '0', '0', '4', 0, 6}}, // 0x04
|
|
{{'\\', 'u', '0', '0', '0', '5', 0, 6}}, // 0x05
|
|
{{'\\', 'u', '0', '0', '0', '6', 0, 6}}, // 0x06
|
|
{{'\\', 'u', '0', '0', '0', '7', 0, 6}}, // 0x07
|
|
{{'\\', 'b', 0, 0, 0, 0, 0, 2}}, // 0x08
|
|
{{'\\', 't', 0, 0, 0, 0, 0, 2}}, // 0x09
|
|
{{'\\', 'n', 0, 0, 0, 0, 0, 2}}, // 0x0A
|
|
{{'\\', 'u', '0', '0', '0', 'b', 0, 6}}, // 0x0B
|
|
{{'\\', 'f', 0, 0, 0, 0, 0, 2}}, // 0x0C
|
|
{{'\\', 'r', 0, 0, 0, 0, 0, 2}}, // 0x0D
|
|
{{'\\', 'u', '0', '0', '0', 'e', 0, 6}}, // 0x0E
|
|
{{'\\', 'u', '0', '0', '0', 'f', 0, 6}}, // 0x0F
|
|
{{'\\', 'u', '0', '0', '1', '0', 0, 6}}, // 0x10
|
|
{{'\\', 'u', '0', '0', '1', '1', 0, 6}}, // 0x11
|
|
{{'\\', 'u', '0', '0', '1', '2', 0, 6}}, // 0x12
|
|
{{'\\', 'u', '0', '0', '1', '3', 0, 6}}, // 0x13
|
|
{{'\\', 'u', '0', '0', '1', '4', 0, 6}}, // 0x14
|
|
{{'\\', 'u', '0', '0', '1', '5', 0, 6}}, // 0x15
|
|
{{'\\', 'u', '0', '0', '1', '6', 0, 6}}, // 0x16
|
|
{{'\\', 'u', '0', '0', '1', '7', 0, 6}}, // 0x17
|
|
{{'\\', 'u', '0', '0', '1', '8', 0, 6}}, // 0x18
|
|
{{'\\', 'u', '0', '0', '1', '9', 0, 6}}, // 0x19
|
|
{{'\\', 'u', '0', '0', '1', 'a', 0, 6}}, // 0x1A
|
|
{{'\\', 'u', '0', '0', '1', 'b', 0, 6}}, // 0x1B
|
|
{{'\\', 'u', '0', '0', '1', 'c', 0, 6}}, // 0x1C
|
|
{{'\\', 'u', '0', '0', '1', 'd', 0, 6}}, // 0x1D
|
|
{{'\\', 'u', '0', '0', '1', 'e', 0, 6}}, // 0x1E
|
|
{{'\\', 'u', '0', '0', '1', 'f', 0, 6}}, // 0x1F
|
|
{{' ', 0, 0, 0, 0, 0, 0, 1}}, // 0x20
|
|
{{'!', 0, 0, 0, 0, 0, 0, 1}}, // 0x21
|
|
{{'\\', '"', 0, 0, 0, 0, 0, 2}}, // 0x22
|
|
{{'#', 0, 0, 0, 0, 0, 0, 1}}, // 0x23
|
|
{{'$', 0, 0, 0, 0, 0, 0, 1}}, // 0x24
|
|
{{'%', 0, 0, 0, 0, 0, 0, 1}}, // 0x25
|
|
{{'&', 0, 0, 0, 0, 0, 0, 1}}, // 0x26
|
|
{{'\'', 0, 0, 0, 0, 0, 0, 1}}, // 0x27
|
|
{{'(', 0, 0, 0, 0, 0, 0, 1}}, // 0x28
|
|
{{')', 0, 0, 0, 0, 0, 0, 1}}, // 0x29
|
|
{{'*', 0, 0, 0, 0, 0, 0, 1}}, // 0x2A
|
|
{{'+', 0, 0, 0, 0, 0, 0, 1}}, // 0x2B
|
|
{{',', 0, 0, 0, 0, 0, 0, 1}}, // 0x2C
|
|
{{'-', 0, 0, 0, 0, 0, 0, 1}}, // 0x2D
|
|
{{'.', 0, 0, 0, 0, 0, 0, 1}}, // 0x2E
|
|
{{'/', 0, 0, 0, 0, 0, 0, 1}}, // 0x2F
|
|
{{'0', 0, 0, 0, 0, 0, 0, 1}}, // 0x30
|
|
{{'1', 0, 0, 0, 0, 0, 0, 1}}, // 0x31
|
|
{{'2', 0, 0, 0, 0, 0, 0, 1}}, // 0x32
|
|
{{'3', 0, 0, 0, 0, 0, 0, 1}}, // 0x33
|
|
{{'4', 0, 0, 0, 0, 0, 0, 1}}, // 0x34
|
|
{{'5', 0, 0, 0, 0, 0, 0, 1}}, // 0x35
|
|
{{'6', 0, 0, 0, 0, 0, 0, 1}}, // 0x36
|
|
{{'7', 0, 0, 0, 0, 0, 0, 1}}, // 0x37
|
|
{{'8', 0, 0, 0, 0, 0, 0, 1}}, // 0x38
|
|
{{'9', 0, 0, 0, 0, 0, 0, 1}}, // 0x39
|
|
{{':', 0, 0, 0, 0, 0, 0, 1}}, // 0x3A
|
|
{{';', 0, 0, 0, 0, 0, 0, 1}}, // 0x3B
|
|
{{'<', 0, 0, 0, 0, 0, 0, 1}}, // 0x3C
|
|
{{'=', 0, 0, 0, 0, 0, 0, 1}}, // 0x3D
|
|
{{'>', 0, 0, 0, 0, 0, 0, 1}}, // 0x3E
|
|
{{'?', 0, 0, 0, 0, 0, 0, 1}}, // 0x3F
|
|
{{'@', 0, 0, 0, 0, 0, 0, 1}}, // 0x40
|
|
{{'A', 0, 0, 0, 0, 0, 0, 1}}, // 0x41
|
|
{{'B', 0, 0, 0, 0, 0, 0, 1}}, // 0x42
|
|
{{'C', 0, 0, 0, 0, 0, 0, 1}}, // 0x43
|
|
{{'D', 0, 0, 0, 0, 0, 0, 1}}, // 0x44
|
|
{{'E', 0, 0, 0, 0, 0, 0, 1}}, // 0x45
|
|
{{'F', 0, 0, 0, 0, 0, 0, 1}}, // 0x46
|
|
{{'G', 0, 0, 0, 0, 0, 0, 1}}, // 0x47
|
|
{{'H', 0, 0, 0, 0, 0, 0, 1}}, // 0x48
|
|
{{'I', 0, 0, 0, 0, 0, 0, 1}}, // 0x49
|
|
{{'J', 0, 0, 0, 0, 0, 0, 1}}, // 0x4A
|
|
{{'K', 0, 0, 0, 0, 0, 0, 1}}, // 0x4B
|
|
{{'L', 0, 0, 0, 0, 0, 0, 1}}, // 0x4C
|
|
{{'M', 0, 0, 0, 0, 0, 0, 1}}, // 0x4D
|
|
{{'N', 0, 0, 0, 0, 0, 0, 1}}, // 0x4E
|
|
{{'O', 0, 0, 0, 0, 0, 0, 1}}, // 0x4F
|
|
{{'P', 0, 0, 0, 0, 0, 0, 1}}, // 0x50
|
|
{{'Q', 0, 0, 0, 0, 0, 0, 1}}, // 0x51
|
|
{{'R', 0, 0, 0, 0, 0, 0, 1}}, // 0x52
|
|
{{'S', 0, 0, 0, 0, 0, 0, 1}}, // 0x53
|
|
{{'T', 0, 0, 0, 0, 0, 0, 1}}, // 0x54
|
|
{{'U', 0, 0, 0, 0, 0, 0, 1}}, // 0x55
|
|
{{'V', 0, 0, 0, 0, 0, 0, 1}}, // 0x56
|
|
{{'W', 0, 0, 0, 0, 0, 0, 1}}, // 0x57
|
|
{{'X', 0, 0, 0, 0, 0, 0, 1}}, // 0x58
|
|
{{'Y', 0, 0, 0, 0, 0, 0, 1}}, // 0x59
|
|
{{'Z', 0, 0, 0, 0, 0, 0, 1}}, // 0x5A
|
|
{{'[', 0, 0, 0, 0, 0, 0, 1}}, // 0x5B
|
|
{{'\\', '\\', 0, 0, 0, 0, 0, 2}}, // 0x5C
|
|
{{']', 0, 0, 0, 0, 0, 0, 1}}, // 0x5D
|
|
{{'^', 0, 0, 0, 0, 0, 0, 1}}, // 0x5E
|
|
{{'_', 0, 0, 0, 0, 0, 0, 1}}, // 0x5F
|
|
}
|
|
};
|
|
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
|
|
*
|
|
* Branch-free replacement for `snprintf(buf, 7, "\\u%04x", codeunit)` in the
|
|
* string escaping hot path. It writes exactly six characters ('\\', 'u' and
|
|
* four hex digits) at position @a pos of @a string_buffer via a nibble
|
|
* lookup table, avoiding the format-string parsing and locale machinery of
|
|
* `snprintf`. Advances @a pos by the number of bytes written (6).
|
|
*
|
|
* @param[in] pos position in @a string_buffer to write at; there must
|
|
* be at least 6 bytes of headroom
|
|
* @param[in] codeunit 16-bit value to encode
|
|
*/
|
|
void write_u_escape(std::size_t& pos, std::uint16_t codeunit) noexcept
|
|
{
|
|
JSON_ASSERT(string_buffer.size() - pos >= 6);
|
|
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
|
string_buffer[pos + 0] = '\\';
|
|
string_buffer[pos + 1] = 'u';
|
|
string_buffer[pos + 2] = nibble_to_hex[(codeunit >> 12u) & 0x0Fu];
|
|
string_buffer[pos + 3] = nibble_to_hex[(codeunit >> 8u) & 0x0Fu];
|
|
string_buffer[pos + 4] = nibble_to_hex[(codeunit >> 4u) & 0x0Fu];
|
|
string_buffer[pos + 5] = nibble_to_hex[codeunit & 0x0Fu];
|
|
pos += 6;
|
|
}
|
|
|
|
/*!
|
|
@brief convert a single element of a binary value to its byte value
|
|
|
|
The elements of a binary value are dumped as the numbers 0..255, regardless
|
|
of the value type of the configured BinaryType: that type may be signed
|
|
(`char`), unsigned (`std::uint8_t`), or not an integer at all
|
|
(`std::byte`), none of which @ref dump_integer can handle uniformly.
|
|
*/
|
|
static std::uint8_t to_byte_value(binary_char_t x) noexcept
|
|
{
|
|
return static_cast<std::uint8_t>(x);
|
|
}
|
|
|
|
// templates to avoid warnings about useless casts
|
|
template <typename NumberType, enable_if_t<std::is_signed<NumberType>::value, int> = 0>
|
|
bool is_negative_number(NumberType x)
|
|
{
|
|
return x < 0;
|
|
}
|
|
|
|
template < typename NumberType, enable_if_t <std::is_unsigned<NumberType>::value, int > = 0 >
|
|
bool is_negative_number(NumberType /*unused*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/*!
|
|
@brief write the decimal representation of the byte @a value
|
|
|
|
A binary value's bytes are always in [0, 255], so writing one needs neither
|
|
the digit counting nor the 64-bit arithmetic that @ref dump_integer does for
|
|
an arbitrary number, and the three digits it takes at most are written
|
|
straight into the write buffer.
|
|
|
|
Any byte type that is not a plain unsigned byte is converted to its
|
|
@ref to_byte_value "byte value" and left to @ref dump_integer, so a signed
|
|
or non-integral BinaryType::value_type (`char`, `std::byte`, ...) still
|
|
dumps as 0..255.
|
|
*/
|
|
template<typename ByteType>
|
|
void dump_byte(const ByteType value)
|
|
{
|
|
dump_byte(value, std::integral_constant < bool,
|
|
std::is_unsigned<ByteType>::value && sizeof(ByteType) == 1
|
|
&& !std::is_same<ByteType, bool>::value > {});
|
|
}
|
|
|
|
template<typename ByteType>
|
|
void dump_byte(const ByteType value, std::false_type /*is_plain_byte*/)
|
|
{
|
|
dump_integer(to_byte_value(value));
|
|
}
|
|
|
|
template<typename ByteType>
|
|
void dump_byte(const ByteType value, std::true_type /*is_plain_byte*/)
|
|
{
|
|
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + 3 > write_buffer.size()))
|
|
{
|
|
flush();
|
|
}
|
|
|
|
const auto byte = static_cast<unsigned>(value);
|
|
// Accumulate the offset in a local and store it back once. Writing
|
|
// through write_buffer[] is a char write, which may alias any object,
|
|
// so with the member updated in place the compiler has to reload and
|
|
// store it around every digit - measured 2.4x slower on a dump of a
|
|
// multi-megabyte binary value.
|
|
std::size_t pos = write_buffer_pos;
|
|
|
|
if (byte >= 100)
|
|
{
|
|
write_buffer[pos++] = static_cast<char>('0' + (byte / 100));
|
|
write_buffer[pos++] = static_cast<char>('0' + ((byte / 10) % 10));
|
|
}
|
|
else if (byte >= 10)
|
|
{
|
|
write_buffer[pos++] = static_cast<char>('0' + (byte / 10));
|
|
}
|
|
|
|
write_buffer[pos++] = static_cast<char>('0' + (byte % 10));
|
|
|
|
write_buffer_pos = pos;
|
|
}
|
|
|
|
/*!
|
|
@brief dump an integer
|
|
|
|
Dump a given integer, appending it to @ref write_buffer (directly: copying
|
|
the digits from another buffer right after writing them waits until the
|
|
stores are done).
|
|
|
|
@param[in] x integer number (signed or unsigned) to dump
|
|
@tparam NumberType either @a number_integer_t or @a number_unsigned_t
|
|
*/
|
|
template < typename NumberType, detail::enable_if_t <
|
|
std::is_integral<NumberType>::value ||
|
|
std::is_same<NumberType, number_unsigned_t>::value ||
|
|
std::is_same<NumberType, number_integer_t>::value,
|
|
int > = 0 >
|
|
void dump_integer(NumberType x)
|
|
{
|
|
static constexpr std::array<std::array<char, 2>, 100> digits_to_99
|
|
{
|
|
{
|
|
{{'0', '0'}}, {{'0', '1'}}, {{'0', '2'}}, {{'0', '3'}}, {{'0', '4'}}, {{'0', '5'}}, {{'0', '6'}}, {{'0', '7'}}, {{'0', '8'}}, {{'0', '9'}},
|
|
{{'1', '0'}}, {{'1', '1'}}, {{'1', '2'}}, {{'1', '3'}}, {{'1', '4'}}, {{'1', '5'}}, {{'1', '6'}}, {{'1', '7'}}, {{'1', '8'}}, {{'1', '9'}},
|
|
{{'2', '0'}}, {{'2', '1'}}, {{'2', '2'}}, {{'2', '3'}}, {{'2', '4'}}, {{'2', '5'}}, {{'2', '6'}}, {{'2', '7'}}, {{'2', '8'}}, {{'2', '9'}},
|
|
{{'3', '0'}}, {{'3', '1'}}, {{'3', '2'}}, {{'3', '3'}}, {{'3', '4'}}, {{'3', '5'}}, {{'3', '6'}}, {{'3', '7'}}, {{'3', '8'}}, {{'3', '9'}},
|
|
{{'4', '0'}}, {{'4', '1'}}, {{'4', '2'}}, {{'4', '3'}}, {{'4', '4'}}, {{'4', '5'}}, {{'4', '6'}}, {{'4', '7'}}, {{'4', '8'}}, {{'4', '9'}},
|
|
{{'5', '0'}}, {{'5', '1'}}, {{'5', '2'}}, {{'5', '3'}}, {{'5', '4'}}, {{'5', '5'}}, {{'5', '6'}}, {{'5', '7'}}, {{'5', '8'}}, {{'5', '9'}},
|
|
{{'6', '0'}}, {{'6', '1'}}, {{'6', '2'}}, {{'6', '3'}}, {{'6', '4'}}, {{'6', '5'}}, {{'6', '6'}}, {{'6', '7'}}, {{'6', '8'}}, {{'6', '9'}},
|
|
{{'7', '0'}}, {{'7', '1'}}, {{'7', '2'}}, {{'7', '3'}}, {{'7', '4'}}, {{'7', '5'}}, {{'7', '6'}}, {{'7', '7'}}, {{'7', '8'}}, {{'7', '9'}},
|
|
{{'8', '0'}}, {{'8', '1'}}, {{'8', '2'}}, {{'8', '3'}}, {{'8', '4'}}, {{'8', '5'}}, {{'8', '6'}}, {{'8', '7'}}, {{'8', '8'}}, {{'8', '9'}},
|
|
{{'9', '0'}}, {{'9', '1'}}, {{'9', '2'}}, {{'9', '3'}}, {{'9', '4'}}, {{'9', '5'}}, {{'9', '6'}}, {{'9', '7'}}, {{'9', '8'}}, {{'9', '9'}},
|
|
}
|
|
};
|
|
|
|
// special case for "0"
|
|
if (x == 0)
|
|
{
|
|
put_char('0');
|
|
return;
|
|
}
|
|
|
|
// use a pointer to fill the buffer (room for as much as number_buffer holds)
|
|
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + number_buffer.size() > write_buffer.size()))
|
|
{
|
|
flush();
|
|
}
|
|
auto* buffer_ptr = write_buffer.data() + write_buffer_pos;
|
|
|
|
number_magnitude_t abs_value;
|
|
|
|
// one byte for the minus sign
|
|
unsigned int n_chars = 0;
|
|
|
|
if (is_negative_number(x))
|
|
{
|
|
*buffer_ptr = '-';
|
|
abs_value = remove_sign(static_cast<number_integer_t>(x));
|
|
n_chars = 1;
|
|
}
|
|
else
|
|
{
|
|
abs_value = static_cast<number_magnitude_t>(x);
|
|
}
|
|
|
|
// up to 16 digits: eight at a time (as the digits of floats), written
|
|
// without leading zeros
|
|
if (abs_value < 10000000000000000u)
|
|
{
|
|
const auto value = static_cast<std::uint64_t>(abs_value); // fits: abs_value < 10^16
|
|
const std::uint64_t upper = value / 100000000u;
|
|
const std::uint64_t first = dtoa_impl::eight_digit_bytes(upper != 0 ? upper : value);
|
|
const auto leading = static_cast<unsigned>(count_leading_zeros(first) / 8); // (first is not 0)
|
|
char* const p = buffer_ptr + n_chars;
|
|
dtoa_impl::store_msb_first(p, (first << (8 * leading)) + 0x3030303030303030u);
|
|
n_chars += 8 - leading;
|
|
if (upper != 0)
|
|
{
|
|
dtoa_impl::store_msb_first(p + 8 - leading, dtoa_impl::eight_digit_bytes(value - (upper * 100000000u)) + 0x3030303030303030u);
|
|
n_chars += 8;
|
|
}
|
|
write_buffer_pos += n_chars;
|
|
return;
|
|
}
|
|
|
|
n_chars += count_digits(abs_value);
|
|
|
|
// spare 1 byte for '\0'
|
|
JSON_ASSERT(n_chars < number_buffer.size() - 1);
|
|
|
|
// jump to the end to generate the string from backward,
|
|
// so we later avoid reversing the result
|
|
buffer_ptr += n_chars;
|
|
|
|
// Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu
|
|
// See: https://www.youtube.com/watch?v=o4-CwDo2zpg
|
|
while (abs_value >= 100)
|
|
{
|
|
const auto digits_index = static_cast<unsigned>((abs_value % 100));
|
|
abs_value /= 100;
|
|
*(--buffer_ptr) = digits_to_99[digits_index][1];
|
|
*(--buffer_ptr) = digits_to_99[digits_index][0];
|
|
}
|
|
|
|
if (abs_value >= 10)
|
|
{
|
|
const auto digits_index = static_cast<unsigned>(abs_value);
|
|
*(--buffer_ptr) = digits_to_99[digits_index][1];
|
|
*(--buffer_ptr) = digits_to_99[digits_index][0];
|
|
}
|
|
else
|
|
{
|
|
*(--buffer_ptr) = static_cast<char>('0' + abs_value);
|
|
}
|
|
|
|
write_buffer_pos += n_chars;
|
|
}
|
|
|
|
/*!
|
|
@brief dump a floating-point number
|
|
|
|
Dump a given floating-point number, appending it to @ref write_buffer.
|
|
|
|
@param[in] x floating-point number to dump
|
|
*/
|
|
void dump_float(number_float_t x)
|
|
{
|
|
// NaN / inf
|
|
if (!std::isfinite(x))
|
|
{
|
|
put_literal("null");
|
|
return;
|
|
}
|
|
|
|
// If number_float_t is an IEEE-754 single or double precision number,
|
|
// use the Grisu2 algorithm to produce short numbers which are
|
|
// guaranteed to round-trip, using strtof and strtod, resp.
|
|
//
|
|
// NB: The test below works if <long double> == <double>.
|
|
static constexpr bool is_ieee_single_or_double
|
|
= (std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128) ||
|
|
(std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024);
|
|
|
|
dump_float(x, std::integral_constant<bool, is_ieee_single_or_double>());
|
|
}
|
|
|
|
void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
|
|
{
|
|
// directly into the write buffer: copying the text from number_buffer
|
|
// right after to_chars() wrote it waits until its stores are done
|
|
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + number_buffer.size() > write_buffer.size()))
|
|
{
|
|
flush();
|
|
}
|
|
auto* begin = write_buffer.data() + write_buffer_pos;
|
|
auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x);
|
|
write_buffer_pos += static_cast<std::size_t>(end - begin);
|
|
}
|
|
|
|
JSON_HEDLEY_NON_NULL(1)
|
|
static int snprintf_float(char* buf, std::size_t size, int d, double x)
|
|
{
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
|
return (std::snprintf)(buf, size, "%.*g", d, x);
|
|
}
|
|
|
|
JSON_HEDLEY_NON_NULL(1)
|
|
static int snprintf_float(char* buf, std::size_t size, int d, long double x)
|
|
{
|
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
|
return (std::snprintf)(buf, size, "%.*Lg", d, x);
|
|
}
|
|
|
|
void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
|
|
{
|
|
// get the number of digits for a float -> text -> float round-trip
|
|
static constexpr auto d = std::numeric_limits<number_float_t>::max_digits10;
|
|
|
|
// the actual conversion
|
|
std::ptrdiff_t len = snprintf_float(number_buffer.data(), number_buffer.size(), d, x);
|
|
|
|
// negative value indicates an error
|
|
JSON_ASSERT(len > 0);
|
|
// check if the buffer was large enough
|
|
JSON_ASSERT(static_cast<std::size_t>(len) < number_buffer.size());
|
|
|
|
// look up the locale's thousands separator and decimal point now,
|
|
// matching what snprintf_float() just used (see lexer::get_decimal_point())
|
|
const auto* loc = std::localeconv();
|
|
JSON_ASSERT(loc != nullptr);
|
|
const char thousands_sep = (loc->thousands_sep == nullptr) ? '\0' : *loc->thousands_sep;
|
|
const char decimal_point = (loc->decimal_point == nullptr) ? '\0' : *loc->decimal_point;
|
|
|
|
// erase thousands separators
|
|
if (thousands_sep != '\0')
|
|
{
|
|
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::remove returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
|
const auto end = std::remove(number_buffer.begin(), number_buffer.begin() + len, thousands_sep);
|
|
std::fill(end, number_buffer.end(), '\0');
|
|
JSON_ASSERT((end - number_buffer.begin()) <= len);
|
|
len = (end - number_buffer.begin());
|
|
}
|
|
|
|
// convert decimal point to '.'
|
|
if (decimal_point != '\0' && decimal_point != '.')
|
|
{
|
|
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::find returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
|
const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point);
|
|
if (dec_pos != number_buffer.end())
|
|
{
|
|
*dec_pos = '.';
|
|
}
|
|
}
|
|
|
|
put_buffer(number_buffer, static_cast<std::size_t>(len));
|
|
|
|
// determine if we need to append ".0"
|
|
const bool value_is_int_like =
|
|
std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1,
|
|
[](char c)
|
|
{
|
|
return c == '.' || c == 'e';
|
|
});
|
|
|
|
if (value_is_int_like)
|
|
{
|
|
put_literal(".0");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Helper function for dump_integer
|
|
*
|
|
* This function takes a negative signed integer and returns its absolute
|
|
* value as an unsigned integer. The plus/minus shuffling is necessary as we
|
|
* cannot directly remove the sign of an arbitrary signed integer as the
|
|
* absolute values of INT_MIN and INT_MAX are usually not the same. See
|
|
* #1708 for details.
|
|
*/
|
|
number_magnitude_t remove_sign(number_integer_t x) noexcept
|
|
{
|
|
JSON_ASSERT(x < 0);
|
|
return static_cast<number_magnitude_t>(-(x + 1)) + 1;
|
|
}
|
|
|
|
private:
|
|
/// the output of the serializer (non-owning; the adapter lives at the call site)
|
|
output_adapter_protocol<char>* o = nullptr;
|
|
|
|
// The three buffers below are left uninitialized: they 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.
|
|
|
|
/// a (hopefully) large enough character buffer
|
|
std::array<char, 64> number_buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
|
|
|
|
/// string buffer
|
|
std::array<char, 512> string_buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
|
|
|
|
/// the indentation character
|
|
const char indent_char;
|
|
|
|
/// whether to pretty-print the output
|
|
const bool pretty_print;
|
|
|
|
/// whether to escape non-ASCII characters with \\uXXXX sequences
|
|
const bool ensure_ascii;
|
|
|
|
/// the indent level
|
|
const std::size_t indent_step;
|
|
|
|
/// error_handler how to react on decoding errors
|
|
const error_handler_t error_handler;
|
|
|
|
/// buffer collecting output before it is flushed to the output adapter, so
|
|
/// that the many small structural writes become few bulk writes
|
|
static constexpr std::size_t write_buffer_size = 1024;
|
|
std::array<char, write_buffer_size> write_buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
|
|
/// number of valid bytes currently held in @ref write_buffer
|
|
std::size_t write_buffer_pos = 0;
|
|
};
|
|
|
|
} // namespace detail
|
|
NLOHMANN_JSON_NAMESPACE_END
|