* Add shared detail helpers for the #5891 cleanup Add detail::is_primitive(value_t), detail::hex4_lower(), detail::utf8_shape(), detail::input_format_name(), detail::empty_input_message(), the exception ids 116, 319, 320 and 416 to detail::exception_id, and make detail::is_ascii_copyable() constexpr. Later commits switch the duplicated code over to them. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.4: BSON writers share one write-pass UTF-8 sanitizer (#5891) write_bson_entry_header and write_bson_string re-derived the error-handler policy differently. Both now use sanitize_utf8_for_bson_write, which treats strict like keep (the size pass already validated) and sanitizes replace/ignore into the bytes the size pass measured. No context plumbing is needed. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.1: one MessagePack container prefix helper (#5891) Array and object prefixes differed only in the fix marker and the 16/32-bit markers; both now delegate to write_msgpack_container_prefix. Output is byte-identical. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.2: one iterative walker for CBOR and MessagePack (#5891) write_cbor_iterative and write_msgpack_iterative (and their *_value_or_push helpers) were identical but for the head, scalar, and key writers. They are now one template over a small policy struct, so everything inlines. BON8 keeps its own walker (open string run, closing marker), noted in a comment. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.3, 2.1.8: one UBJSON container start helper, shared frame base (#5891) write_ubjson_start_array/_object are now write_ubjson_start_container<IsArray>, keeping the array-only begin()+1 scan and excessive_valueless rule. ubjson_frame derives from binary_container_frame instead of duplicating it. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.4: write_bytes helper for the byte-string write sites (#5891) The 13 oa.write_characters(reinterpret_cast<const CharType*>(x.data()), n) sites now call write_bytes(const void*, size_t); copy_characters sites and the buffer-based writes are untouched. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.5: shared object-key writer for CBOR and MessagePack (#5891) write_cbor_key and write_msgpack_key were the same SFINAE pair apart from the string/scalar writers; both now delegate to write_object_key<Walk>. UBJSON key and to_format_length stay as they are. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.10: writer-side format names from input_format_name (#5891) The CBOR/MessagePack/UBJSON/BSON/BJData/BON8 literals in throw_on_discarded and throw_integer_out_of_range calls now come from detail::input_format_name; the texts are identical, and UBJSON vs BJData still follows use_bjdata. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * serializer: constexpr ascii table, hex4_lower, dedupe helpers (#5891) 1.1: build ascii_copyable() at compile time from is_ascii_copyable and test find_ascii_copyable_run against it for every byte. 1.2/2.5.4: use detail::hex4_lower in both \uXXXX writers. 2.1.6: factor the repeated flush/emit blocks in dump_escaped_impl into always-inline helpers. 2.1.7: comment why the escape copy does not use put_buffer. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.6: use string_t-aware to_string/concat in meta() (#5891) Replaces the four std::to_string sites; rendered text unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7: use detail::is_primitive in json.hpp switches (#5891) has_no_children, set_parents, max_size and the two diff walkers. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.1: factor insert()/iterator throw helpers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.2: share swap() overloads via swap_impl (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.4: factor primitive reset and range check in erase()/range ctor (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.6: share push_back array bodies via push_back_array_impl (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.7: route parse/accept/sax_parse through *_impl helpers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.8: share the vector-returning to_* bodies via to_binary_vector (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.10: merge destroy_string/destroy_binary into destroy_pointer (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.1/1.7: exceptions: share what_impl, flatten diagnostics switch (#5891) The four create() bodies build their message through one protected static helper; the diagnostics parent walk handles array/object and ignores all other types. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.2/2.4.3/1.7: json_pointer: share slash and escape checks, drop primitive case lists (#5891) The leading-slash and invalid '~' escape checks are shared helpers; the reference-token walks test object and array first and treat everything else as primitive. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7/2.4.9: iter_impl, iteration_proxy: drop primitive case lists, share compare preamble (#5891) Object and array stay the first branches; everything else is the primitive case. operator== and operator< share the different-container check. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.5/2.4.6: from_json: share get_number_value and reserve_if_possible (#5891) The three number cases of get_arithmetic_value and the arithmetic from_json share one helper (boolean stays accepted only by the latter); the array and object reserve overload pairs become one. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.8: ordered_map: share erase_impl between the key erase overloads (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7: json_pointer: flatten walk drops the primitive case list (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.2/1.3/1.5/2.5.1/2.5.2/2.5.5: share helpers in the view code (#5891) - detail::exception_id at every numeric id; throw_* helpers take it - hex_byte, hex4_lower, utf8_shape replace local hex/table code - throw_array_index_out_of_range/_past_end, throw_key_not_found - has_native_float_format replaces the hand-written IEEE check No change of messages, ids or output. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.5: use utf8_shape in get_bon8_string, add exhaustive UTF-8 validator test (#5891) get_bon8_string takes the valid range of the second byte from utf8_shape() (0xF5..0xF7 stay errors, unget behavior unchanged). validate_one_utf8 keeps its if-chain (hot bulk path); the new test checks decode, validate_one_utf8, utf8_shape, json::accept and the BON8 reader against each other for every lead and second byte. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.1: get_cbor_sized_bytes for the CBOR chunk getters (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.2: get_string_with_length for the plain-order string sites (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.3, 2.3.4, 2.3.10: unsupported_key_type, empty_input_message, input_format_name in the readers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.7, 2.3.9: read_object_key and leave_container_and_check for CBOR/MessagePack/BON8 (#5891) UBJSON keys (different getter signature) and its container tail stay as is. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.5: json_sax_dom_error_state base for the DOM SAX parsers (#5891) Holds errored, allow_exceptions, parse_error() and is_errored(). The scalar handlers stay in each class (parse hot path). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.6: table of control-character messages in the lexer (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.8: finish_input helper for the strict-EOF block in the parser (#5891) sax_parse keeps its if (result) guard and return value. Also adds the clang-tidy NOLINT for the 2.3.6 message table. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.5.7: share parse_array_index between json_pointer and json_view (#5891) Move json_pointer::parse_array_index() and its status enum to namespace detail (json_pointer keeps a forwarding member), and let the json_view pointer resolution use it instead of its own copy of the RFC 6901 index grammar. Both accepted the same tokens with the same outcome. Also update the comment on detail::decode(), which listed four of the five UTF-8 validators and now points to the test that keeps them in sync. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Amalgamate (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.5: keep the inlined number cases in from_json(ArithmeticType&) (#5891) Calling get_number_value() from from_json(ArithmeticType&) made GetArray<std::array<int, 1000>> 2.4x slower (it changes the inlining of the std::array conversion), so that overload keeps its own switch, with a comment. get_arithmetic_value() still uses the shared helper. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.5.7: force-inline detail::parse_array_index (#5891) Moving parse_array_index out of json_pointer stopped clang from inlining it into the pointer resolution (JsonPointerAt/citm_catalog +17%, JsonPointerAt/twitter +8%); JSON_HEDLEY_ALWAYS_INLINE restores the baseline. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.5: keep get_bon8_string's own UTF-8 ranges (#5891) Taking the second-byte range from utf8_shape() made BON8 decoding 2-3% slower (FromBinaryShape/scalars, FromBinaryBuffer, alignment-pinned builds), so the byte path keeps its hand-written ranges. The exhaustive validator test still checks that it accepts the same sequences as the other validators. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Amalgamate (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix CI failures: constexpr, NRVO, cppcheck, IWYU (#5891) - json_sax_dom_error_state::is_errored() cannot be constexpr: the class is not a literal type (GCC 4.8, GCC -Wpedantic, Clang 3.5). - to_binary_vector: destroy the vector writer before returning, as the output_vector_sink sets the size of the vector in its destructor. Without NRVO (MSVC Debug in C++14 mode), the vector was moved out before it was resized. - parser::sax_parse: return false instead of result after the early exit (cppcheck identicalConditionAfterEarlyExit). - json.hpp: export detail/exceptions.hpp for IWYU, so json_view.hpp can name exception_id without including a detail header. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Address review: throw helpers take input_format_t, simpler type checks - throw_on_discarded() and throw_integer_out_of_range() take an input_format_t and call input_format_name() themselves - has_no_children(), set_parents() and max_size() check array, then object, instead of calling is_primitive() first - parse_impl() chains like accept_impl(); sax_parse_impl() uses if/else - std::array for the control character messages; negation<> in write_object_key(); clarify the is_primitive() comment Signed-off-by: Niels Lohmann <mail@nlohmann.me> * json_view: use exception_id in the value() overloads Signed-off-by: Niels Lohmann <mail@nlohmann.me> * json_sax: assert the invariant of place_container for the static analyzer Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
1160 lines
42 KiB
C++
1160 lines
42 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> // max
|
|
#include <array> // array
|
|
#include <cmath> // isfinite
|
|
#include <cstddef> // size_t
|
|
#include <cstdint> // uint8_t, uint32_t
|
|
#include <cstring> // memcpy, memset
|
|
#include <limits> // numeric_limits
|
|
#include <type_traits> // integral_constant
|
|
#include <vector> // vector
|
|
|
|
#include <nlohmann/json.hpp>
|
|
#include <nlohmann/detail/view/document_data.hpp>
|
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
|
#include <nlohmann/detail/view/node.hpp>
|
|
#include <nlohmann/detail/view/number.hpp>
|
|
#include <nlohmann/detail/view/simd.hpp>
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
namespace detail
|
|
{
|
|
namespace view
|
|
{
|
|
|
|
/// append-only output buffer: writes through a raw pointer into a string that
|
|
/// is resized ahead, and trimmed by finish(). The estimate is reserved, and the
|
|
/// string grows in steps of 64 KiB within it: resize() fills the new bytes
|
|
/// with zeros (before C++23, a string cannot grow without), and a small step
|
|
/// is filled while the writer is about to use it, in the cache, instead of
|
|
/// filling the whole estimate in memory first.
|
|
template<typename StringType>
|
|
class output_buffer
|
|
{
|
|
public:
|
|
output_buffer(StringType& out, std::size_t estimate)
|
|
: m_out(sized(out, estimate))
|
|
, m_pos(&m_out[0])
|
|
, m_end(m_pos + m_out.size())
|
|
{}
|
|
|
|
void finish()
|
|
{
|
|
const auto size = static_cast<std::size_t>(m_pos - m_out.data());
|
|
m_out.resize(size);
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE void reserve(std::size_t n)
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(m_end - m_pos) < n))
|
|
{
|
|
grow(n);
|
|
}
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE void put(char c)
|
|
{
|
|
reserve(1);
|
|
*m_pos++ = c;
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE void put(const char* s, std::size_t n)
|
|
{
|
|
reserve(n);
|
|
std::memcpy(m_pos, s, n);
|
|
m_pos += n;
|
|
}
|
|
|
|
/// a string literal, without its terminating NUL
|
|
template<std::size_t N>
|
|
NLOHMANN_VIEW_ALWAYS_INLINE void put(const char (&s)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
|
{
|
|
put(s, N - 1);
|
|
}
|
|
|
|
/// the write position and the end of the writable space, for a writer
|
|
/// that keeps the position in a local variable (set_cursor() hands it back)
|
|
char* cursor() const noexcept
|
|
{
|
|
return m_pos;
|
|
}
|
|
|
|
char* limit() const noexcept
|
|
{
|
|
return m_end;
|
|
}
|
|
|
|
void set_cursor(char* p) noexcept
|
|
{
|
|
m_pos = p;
|
|
}
|
|
|
|
private:
|
|
/// the size of a growth step
|
|
static constexpr std::size_t step = 0x10000;
|
|
|
|
static StringType& sized(StringType& out, std::size_t estimate)
|
|
{
|
|
out.reserve(estimate);
|
|
out.resize(detail::clamp(estimate, std::size_t{64}, step));
|
|
return out;
|
|
}
|
|
|
|
NLOHMANN_VIEW_NOINLINE void grow(std::size_t n)
|
|
{
|
|
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
|
// (a step does not go beyond the reserved estimate, so that a good
|
|
// estimate is never copied to a larger allocation; not a clamp: the
|
|
// lower bound used + n + 256 can exceed capacity(), and then it must win)
|
|
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256);
|
|
if (size > m_out.capacity())
|
|
{
|
|
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
|
}
|
|
m_out.resize(size);
|
|
m_pos = &m_out[0] + used;
|
|
m_end = &m_out[0] + m_out.size();
|
|
}
|
|
|
|
StringType& m_out;
|
|
char* m_pos;
|
|
char* m_end;
|
|
};
|
|
|
|
/// The length of the run at s that dump() writes unchanged without
|
|
/// ensure_ascii: all bytes but quotes, backslashes, and control characters.
|
|
/// Unlike detail::string_bulk_run(), non-ASCII bytes are not validated: the
|
|
/// strings of a document are valid UTF-8 (a damaged image loaded with
|
|
/// image_check::bounds can have others, which are then written unchanged).
|
|
inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexcept
|
|
{
|
|
std::size_t i = 0;
|
|
#if NLOHMANN_VIEW_SSE2
|
|
// (16 bytes per step; with NEON, the mask of a step costs more than it saves on the
|
|
// short runs that are typical here)
|
|
// NOLINTBEGIN(portability-simd-intrinsics)
|
|
for (; i + 16 <= n; i += 16)
|
|
{
|
|
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(s + i))); // NOLINT(bugprone-casting-through-void)
|
|
// (a byte is below 0x20 if it is its own minimum with 0x1F)
|
|
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
|
_mm_cmpeq_epi8(_mm_min_epu8(in, _mm_set1_epi8(0x1F)), in));
|
|
const auto mask = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
|
if (mask != 0)
|
|
{
|
|
return i + static_cast<std::size_t>(count_trailing_zeros(mask));
|
|
}
|
|
}
|
|
// NOLINTEND(portability-simd-intrinsics)
|
|
#endif
|
|
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
|
for (; i + 8 <= n; i += 8)
|
|
{
|
|
const std::uint64_t v = read_eight_bytes(s + i);
|
|
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"'
|
|
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\'
|
|
const std::uint64_t stop = (((q - ones) & ~q) | ((b - ones) & ~b) | ((v - 0x2020202020202020ull) & ~v)) & high;
|
|
if (stop != 0)
|
|
{
|
|
// the lowest flagged byte is the first stop: borrows only flag bytes above a true one
|
|
return i + (static_cast<std::size_t>(count_trailing_zeros(stop)) / 8);
|
|
}
|
|
}
|
|
for (; i < n; ++i)
|
|
{
|
|
if (s[i] == '"' || s[i] == '\\' || s[i] < 0x20)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/// Whether dump() with ensure_ascii writes the string at s unchanged, for a
|
|
/// string of the source without escape sequences (which has no quote,
|
|
/// backslash, or control character): no byte is DEL (0x7F) or above. Adding 1
|
|
/// to each byte sets its high bit exactly for 0x7F, and a carry into the next
|
|
/// byte only comes from 0xFF, whose own high bit is set anyway.
|
|
inline bool is_ascii_without_delete(const char* s, std::size_t n) noexcept
|
|
{
|
|
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
|
std::size_t i = 0;
|
|
for (; i + 8 <= n; i += 8)
|
|
{
|
|
const std::uint64_t v = read_eight_bytes(s + i);
|
|
if (((v | (v + ones)) & high) != 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
for (; i < n; ++i)
|
|
{
|
|
if (static_cast<unsigned char>(s[i]) >= 0x7F)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#if NLOHMANN_VIEW_VECTOR
|
|
/// the bytes of the 16 at p that ensure_ascii cannot copy (a quote, a
|
|
/// backslash, a control character, DEL, or not ASCII; a signed compare with
|
|
/// 0x20 finds control characters and non-ASCII bytes at once), one bit per
|
|
/// byte (x86) or one nibble per byte (NEON)
|
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t ascii_stop_mask(const unsigned char* p) noexcept
|
|
{
|
|
#if NLOHMANN_VIEW_NEON
|
|
const uint8x16_t in = vld1q_u8(p);
|
|
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
|
vorrq_u8(vceqq_u8(in, vdupq_n_u8(0x7F)), vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20))));
|
|
return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
|
#else
|
|
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
|
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
|
_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F)), _mm_cmplt_epi8(in, _mm_set1_epi8(0x20))));
|
|
return static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
|
#endif
|
|
}
|
|
|
|
/// the width in bits of a byte in the mask of ascii_stop_mask()
|
|
constexpr unsigned ascii_stop_bits() noexcept
|
|
{
|
|
return NLOHMANN_VIEW_NEON ? 4u : 1u;
|
|
}
|
|
#endif
|
|
|
|
/// As find_ascii_copyable_run(), 16 bytes per step. end is the end of the
|
|
/// buffer that holds the n bytes at p (a short run is read as 16 bytes if
|
|
/// they are in the buffer).
|
|
inline std::size_t ascii_copyable_run(const unsigned char* p, std::size_t n, const unsigned char* end) noexcept
|
|
{
|
|
#if NLOHMANN_VIEW_VECTOR
|
|
if (n >= 16)
|
|
{
|
|
std::size_t i = 0;
|
|
for (; i + 16 <= n; i += 16)
|
|
{
|
|
const std::uint64_t mask = ascii_stop_mask(p + i);
|
|
if (mask != 0)
|
|
{
|
|
return i + (static_cast<std::size_t>(count_trailing_zeros(mask)) / ascii_stop_bits());
|
|
}
|
|
}
|
|
if (i == n)
|
|
{
|
|
return n;
|
|
}
|
|
// the last 16 bytes: those before i are known to be copyable
|
|
const std::uint64_t mask = ascii_stop_mask(p + n - 16);
|
|
return mask != 0 ? n - 16 + (static_cast<std::size_t>(count_trailing_zeros(mask)) / ascii_stop_bits()) : n;
|
|
}
|
|
if (end - p >= 16)
|
|
{
|
|
// (the bytes after the run are masked out)
|
|
const std::uint64_t mask = ascii_stop_mask(p) & ((std::uint64_t{1} << (n * ascii_stop_bits())) - 1u);
|
|
return mask != 0 ? static_cast<std::size_t>(count_trailing_zeros(mask)) / ascii_stop_bits() : n;
|
|
}
|
|
#else
|
|
static_cast<void>(end);
|
|
#endif
|
|
return find_ascii_copyable_run(p, n);
|
|
}
|
|
|
|
/// A stack that starts in a buffer of the caller (a local array) and moves to
|
|
/// the heap (a vector of the caller) only when that is full, so that dumps of
|
|
/// shallow documents need no allocation. The top is a pointer, as in
|
|
/// std::vector. grow() is static and out of line: it gets the vector and the
|
|
/// used range and returns the new storage, so the address of the stack never
|
|
/// reaches a call. Otherwise the compiler would have to keep the three
|
|
/// pointers in memory and load and store them on every push_back() and
|
|
/// pop_back(), not just when growing.
|
|
template<typename T>
|
|
class small_stack
|
|
{
|
|
public:
|
|
small_stack(T* buffer, std::size_t capacity, std::vector<T>& heap) noexcept
|
|
: m_begin(buffer), m_top(buffer), m_end(buffer + capacity), m_heap(heap)
|
|
{}
|
|
small_stack(const small_stack&) = delete;
|
|
small_stack(small_stack&&) = delete;
|
|
small_stack& operator=(const small_stack&) = delete;
|
|
small_stack& operator=(small_stack&&) = delete;
|
|
~small_stack() = default;
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE void push_back(const T& x)
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(m_top == m_end))
|
|
{
|
|
const std::size_t used = size();
|
|
const std::size_t capacity = 2 * static_cast<std::size_t>(m_end - m_begin);
|
|
m_begin = grow(m_heap, m_begin, used, capacity);
|
|
m_top = m_begin + used;
|
|
m_end = m_begin + capacity;
|
|
}
|
|
*m_top++ = x;
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE T& back() noexcept
|
|
{
|
|
return m_top[-1];
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE void pop_back() noexcept
|
|
{
|
|
--m_top;
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE bool empty() const noexcept
|
|
{
|
|
return m_top == m_begin;
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE std::size_t size() const noexcept
|
|
{
|
|
return static_cast<std::size_t>(m_top - m_begin);
|
|
}
|
|
|
|
private:
|
|
/// the used entries moved to heap storage of the given capacity
|
|
NLOHMANN_VIEW_NOINLINE static T* grow(std::vector<T>& heap, const T* begin, std::size_t used, std::size_t capacity)
|
|
{
|
|
std::vector<T> bigger(capacity);
|
|
std::copy(begin, begin + used, bigger.begin());
|
|
heap.swap(bigger);
|
|
return heap.data();
|
|
}
|
|
|
|
T* m_begin;
|
|
T* m_top;
|
|
T* m_end;
|
|
std::vector<T>& m_heap;
|
|
};
|
|
|
|
/// a string literal at w, without its terminating NUL (after room())
|
|
/// @return a pointer past it
|
|
template<std::size_t N>
|
|
NLOHMANN_VIEW_ALWAYS_INLINE char* put_literal(char* w, const char (&s)[N]) noexcept // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
|
{
|
|
std::memcpy(w, s, N - 1);
|
|
return w + N - 1;
|
|
}
|
|
|
|
/// the depth of the innermost open container, counted only for indented output
|
|
template<bool Counted>
|
|
struct depth_counter
|
|
{
|
|
std::size_t value = 0;
|
|
void enter() noexcept
|
|
{
|
|
++value;
|
|
}
|
|
std::size_t leave() noexcept
|
|
{
|
|
return --value;
|
|
}
|
|
std::size_t get() const noexcept
|
|
{
|
|
return value;
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct depth_counter<false>
|
|
{
|
|
void enter() noexcept {}
|
|
std::size_t leave() noexcept // NOLINT(readability-convert-member-functions-to-static): same interface as depth_counter<true>
|
|
{
|
|
return 0;
|
|
}
|
|
std::size_t get() const noexcept // NOLINT(readability-convert-member-functions-to-static): same interface as depth_counter<true>
|
|
{
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
/// how the view's dump() writes a value
|
|
struct dump_style
|
|
{
|
|
bool pretty = false; ///< indent >= 0
|
|
std::size_t indent = 0; ///< characters per level
|
|
char indent_char = ' ';
|
|
bool ensure_ascii = false;
|
|
bool source_numbers = false; ///< copy number tokens from the source
|
|
};
|
|
|
|
/*!
|
|
@brief write a view's subtree as basic_json::dump() writes the value
|
|
|
|
The output of a subtree equals ordered_json::parse(text).dump() of it for
|
|
the same arguments (members in document order): strings are escaped by the
|
|
same rules, with the library's scanning kernels; floats are written with
|
|
the library's conversion; integers are copied from the source, where they
|
|
are canonical (except "-0", which parse() reads as 0). The walk is
|
|
iterative, so the nesting depth is limited by memory only.
|
|
*/
|
|
template<typename BasicJsonType, bool Editable>
|
|
class view_serializer
|
|
{
|
|
using nav = navigation<Editable>;
|
|
using string_t = typename BasicJsonType::string_t;
|
|
using number_float_t = typename BasicJsonType::number_float_t;
|
|
/// doubles that are IEEE 754 binary64 are written from their bits (Zmij)
|
|
using fast_double = std::integral_constant < bool, std::is_same<number_float_t, double>::value && ::nlohmann::detail::dtoa_impl::is_binary64<double>::value >;
|
|
|
|
public:
|
|
view_serializer(const document_data& d, string_t& out, std::size_t estimate, const dump_style& style)
|
|
: m_doc(d), m_out(out, estimate), m_style(style)
|
|
{}
|
|
|
|
void dump(const node* root)
|
|
{
|
|
if (m_style.ensure_ascii)
|
|
{
|
|
dump_ascii(root);
|
|
}
|
|
else if (m_style.pretty)
|
|
{
|
|
dump_pretty(root);
|
|
}
|
|
else if (m_style.source_numbers)
|
|
{
|
|
dump_text<true, false, false>(root);
|
|
}
|
|
else
|
|
{
|
|
dump_text<false, false, false>(root);
|
|
}
|
|
}
|
|
|
|
private:
|
|
/// dump_text() with ensure_ascii, out of line: the walk without it is
|
|
/// faster when it is not merged with this one into dump()
|
|
NLOHMANN_VIEW_NOINLINE void dump_ascii(const node* root)
|
|
{
|
|
if (m_style.pretty)
|
|
{
|
|
if (m_style.source_numbers)
|
|
{
|
|
dump_text<true, true, true>(root);
|
|
}
|
|
else
|
|
{
|
|
dump_text<false, true, true>(root);
|
|
}
|
|
}
|
|
else if (m_style.source_numbers)
|
|
{
|
|
dump_text<true, false, true>(root);
|
|
}
|
|
else
|
|
{
|
|
dump_text<false, false, true>(root);
|
|
}
|
|
}
|
|
|
|
/// dump_text() with indentation, out of line: the compact walk is faster
|
|
/// when it is not merged with the indented ones into dump()
|
|
NLOHMANN_VIEW_NOINLINE void dump_pretty(const node* root)
|
|
{
|
|
if (m_style.source_numbers)
|
|
{
|
|
dump_text<true, true, false>(root);
|
|
}
|
|
else
|
|
{
|
|
dump_text<false, true, false>(root);
|
|
}
|
|
}
|
|
|
|
/// a string that is not a plain string of the source (decoded, or written
|
|
/// by an edit), without ensure_ascii: runs without characters to escape
|
|
/// are copied. If validate is set, the string must be valid UTF-8: the
|
|
/// runs are found by the lexer's scan, which validates the sequences, and
|
|
/// invalid UTF-8 throws what basic_json::dump() throws. (The compact output
|
|
/// writes the bytes of a damaged image unchanged, and scans without
|
|
/// validating them.)
|
|
NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n, bool validate)
|
|
{
|
|
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
m_out.put('"');
|
|
for (std::size_t i = 0; i < n.len;)
|
|
{
|
|
const std::size_t run = validate ? string_bulk_run(s + i, n.len - i) : plain_output_run(s + i, n.len - i);
|
|
if (run != 0)
|
|
{
|
|
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
i += run;
|
|
continue;
|
|
}
|
|
if (validate && s[i] >= 0x80)
|
|
{
|
|
invalid_utf8(s, n.len); // (the scan stopped at ill-formed UTF-8)
|
|
}
|
|
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
|
|
++i;
|
|
}
|
|
m_out.put('"');
|
|
}
|
|
|
|
/// whether dump_text() copies a string or key from the source: one of
|
|
/// the source, and with ensure_ascii one without bytes >= 0x7F (an overload
|
|
/// each, so that the walk without ensure_ascii is compiled exactly as
|
|
/// before: with GCC, a condition that only folds away later changed its
|
|
/// block layout and made it 4-6% slower)
|
|
static bool copied(const node& n, const char* /*src*/, std::false_type /*ensure_ascii*/) noexcept
|
|
{
|
|
return (n.flags & node_flags::storage) == 0;
|
|
}
|
|
|
|
static bool copied(const node& n, const char* src, std::true_type /*ensure_ascii*/) noexcept
|
|
{
|
|
return (n.flags & node_flags::storage) == 0 && is_ascii_without_delete(src + n.off, n.len);
|
|
}
|
|
|
|
/*!
|
|
@brief a string with ensure_ascii: the characters that are not printable ASCII as \\uXXXX
|
|
(a character beyond U+FFFF as a surrogate pair); strings of a document
|
|
are valid UTF-8, except in a damaged image loaded with image_check::bounds,
|
|
for which this throws what basic_json::dump() throws for the string
|
|
*/
|
|
NLOHMANN_VIEW_NOINLINE void write_ascii(const node& n)
|
|
{
|
|
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
const std::size_t size = n.len;
|
|
char* w = m_out.cursor();
|
|
char* lim = m_out.limit();
|
|
// room for k bytes
|
|
const auto room = [&](std::size_t k)
|
|
{
|
|
const auto left = static_cast<std::size_t>(lim - w);
|
|
if (NLOHMANN_VIEW_UNLIKELY(left < k))
|
|
{
|
|
m_out.set_cursor(w);
|
|
m_out.reserve(k);
|
|
w = m_out.cursor();
|
|
lim = m_out.limit();
|
|
}
|
|
};
|
|
static constexpr const char* hex = "0123456789abcdef";
|
|
// \\uXXXX
|
|
const auto u_escape = [&](std::uint32_t u)
|
|
{
|
|
w[0] = '\\';
|
|
w[1] = 'u';
|
|
w[2] = hex[(u >> 12u) & 0xFu];
|
|
w[3] = hex[(u >> 8u) & 0xFu];
|
|
w[4] = hex[(u >> 4u) & 0xFu];
|
|
w[5] = hex[u & 0xFu];
|
|
w += 6;
|
|
};
|
|
room(1);
|
|
*w++ = '"';
|
|
std::size_t i = 0;
|
|
// (a string of the source starts with a run that has nothing to escape, 16 bytes per step)
|
|
const std::size_t prefix = (n.flags & node_flags::storage) == 0 ? ascii_copyable_run(s, size, reinterpret_cast<const unsigned char*>(m_doc.src + m_doc.size)) : 0; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
if (prefix != 0)
|
|
{
|
|
room(prefix);
|
|
std::memcpy(w, s, prefix);
|
|
w += prefix;
|
|
i = prefix;
|
|
}
|
|
while (i < size)
|
|
{
|
|
// (a character that needs an escape sequence is not worth a call)
|
|
const std::size_t run = is_ascii_copyable(s[i]) ? find_ascii_copyable_run(s + i, size - i) : 0;
|
|
if (run != 0)
|
|
{
|
|
room(run);
|
|
std::memcpy(w, s + i, run);
|
|
w += run;
|
|
i += run;
|
|
continue;
|
|
}
|
|
room(12);
|
|
if (s[i] >= 0x80)
|
|
{
|
|
const unsigned char lead = s[i];
|
|
// (the two-byte sequences of Latin text and the three-byte sequences of most other
|
|
// text, with continuation bytes of any value: the ranges that are restricted (E0,
|
|
// ED, and the sequences of four bytes) go through validate_one_utf8())
|
|
if (lead >= 0xE1 && lead <= 0xEF && lead != 0xED && size - i >= 3 && (s[i + 1] & 0xC0u) == 0x80 && (s[i + 2] & 0xC0u) == 0x80)
|
|
{
|
|
u_escape(((lead & 0x0Fu) << 12u) | ((s[i + 1] & 0x3Fu) << 6u) | (s[i + 2] & 0x3Fu));
|
|
i += 3;
|
|
continue;
|
|
}
|
|
if (lead >= 0xC2 && lead <= 0xDF && size - i >= 2 && (s[i + 1] & 0xC0u) == 0x80)
|
|
{
|
|
u_escape(((lead & 0x1Fu) << 6u) | (s[i + 1] & 0x3Fu));
|
|
i += 2;
|
|
continue;
|
|
}
|
|
const std::size_t len = validate_one_utf8(s + i, size - i);
|
|
if (NLOHMANN_VIEW_UNLIKELY(len == 0))
|
|
{
|
|
m_out.set_cursor(w);
|
|
invalid_utf8(s, size);
|
|
return; // LCOV_EXCL_LINE (invalid_utf8 throws)
|
|
}
|
|
std::uint32_t codepoint = s[i] & (0xFFu >> (len + 1));
|
|
for (std::size_t k = 1; k < len; ++k)
|
|
{
|
|
codepoint = (codepoint << 6u) | (s[i + k] & 0x3Fu);
|
|
}
|
|
i += len;
|
|
if (codepoint <= 0xFFFF)
|
|
{
|
|
u_escape(codepoint);
|
|
}
|
|
else
|
|
{
|
|
u_escape(0xD7C0u + (codepoint >> 10u));
|
|
u_escape(0xDC00u + (codepoint & 0x3FFu));
|
|
}
|
|
continue;
|
|
}
|
|
// a quote, a backslash, or a control character (or DEL)
|
|
const unsigned char c = s[i++];
|
|
char escape = 0;
|
|
switch (c)
|
|
{
|
|
case 0x08:
|
|
escape = 'b';
|
|
break;
|
|
case 0x09:
|
|
escape = 't';
|
|
break;
|
|
case 0x0A:
|
|
escape = 'n';
|
|
break;
|
|
case 0x0C:
|
|
escape = 'f';
|
|
break;
|
|
case 0x0D:
|
|
escape = 'r';
|
|
break;
|
|
case 0x22:
|
|
escape = '"';
|
|
break;
|
|
case 0x5C:
|
|
escape = '\\';
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (escape != 0)
|
|
{
|
|
w[0] = '\\';
|
|
w[1] = escape;
|
|
w += 2;
|
|
}
|
|
else
|
|
{
|
|
u_escape(c);
|
|
}
|
|
}
|
|
room(1);
|
|
*w++ = '"';
|
|
m_out.set_cursor(w);
|
|
}
|
|
|
|
/// the copies of dump_text() that are not fixed-size moves (long
|
|
/// strings, or near the end of the source); out of line, so that the
|
|
/// compiler does not merge the fixed-size moves into this call
|
|
NLOHMANN_VIEW_NOINLINE static void copy_long(char* to, const char* from, std::size_t n) noexcept
|
|
{
|
|
std::memcpy(to, from, n);
|
|
}
|
|
|
|
/// a compile-time option as a runtime condition: testing the template
|
|
/// argument directly makes a condition like `Editable && c` constant when
|
|
/// the option is off, which MSVC reports as C4127
|
|
static NLOHMANN_VIEW_ALWAYS_INLINE bool enabled(bool option) noexcept
|
|
{
|
|
return option;
|
|
}
|
|
|
|
/*!
|
|
@brief the output, compact (the default dump()) or indented, with or without ensure_ascii
|
|
|
|
The same walk as dump(), with the write position in a local variable
|
|
(stores through char pointers would otherwise force a reload of the
|
|
buffer's members after each one), and with strings and number tokens of
|
|
the source copied by fixed-size moves of 32 bytes where the source has
|
|
that many bytes left, instead of a library call per token. The buffer
|
|
keeps 64 bytes of slack for the overshoot. With ensure_ascii (Ascii), a
|
|
string of the source is copied only if it has nothing to escape (see
|
|
copied()); any other string is written by write_ascii().
|
|
*/
|
|
template<bool SourceNumbers, bool Pretty, bool Ascii>
|
|
void dump_text(const node* root)
|
|
{
|
|
struct frame
|
|
{
|
|
const node* pos; ///< (editable documents) next element, or key of the next member
|
|
const node* end;
|
|
bool object;
|
|
};
|
|
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
|
std::vector<frame> heap;
|
|
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
|
|
const char* const src = m_doc.src;
|
|
const char* const src_end = src + m_doc.size;
|
|
char* w = m_out.cursor();
|
|
char* lim = m_out.limit();
|
|
// (indented output) a line break and 63 indentation characters, from
|
|
// which the line break of a level of up to 63 characters is a copy
|
|
std::array<char, 64> ruler; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled below if used
|
|
if (Pretty)
|
|
{
|
|
ruler.fill(m_style.indent_char);
|
|
ruler[0] = '\n';
|
|
}
|
|
const std::size_t indent = m_style.indent;
|
|
depth_counter<Pretty> level; // depth of the innermost open container (indented output)
|
|
// room for n bytes and the slack
|
|
const auto room = [&](std::size_t n)
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(lim - w) < n + 64))
|
|
{
|
|
m_out.set_cursor(w);
|
|
m_out.reserve(n + 64);
|
|
w = m_out.cursor();
|
|
lim = m_out.limit();
|
|
}
|
|
};
|
|
// copy n bytes of the source (after room(n))
|
|
const auto copy = [&](const char* from, std::size_t n)
|
|
{
|
|
if (n <= 32 && static_cast<std::size_t>(src_end - from) >= 32)
|
|
{
|
|
std::memcpy(w, from, 32);
|
|
}
|
|
else if (n <= 256 && static_cast<std::size_t>(src_end - from) >= n + 32)
|
|
{
|
|
for (std::size_t i = 0; i < n; i += 32)
|
|
{
|
|
std::memcpy(w + i, from + i, 32);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
copy_long(w, from, n);
|
|
}
|
|
w += n;
|
|
};
|
|
// (indented output) the line break and the indentation of a level
|
|
const auto newline = [&](std::size_t depth)
|
|
{
|
|
const std::size_t count = 1 + (depth * indent);
|
|
room(count);
|
|
if (count <= ruler.size())
|
|
{
|
|
std::memcpy(w, ruler.data(), ruler.size());
|
|
}
|
|
else
|
|
{
|
|
*w = '\n';
|
|
std::memset(w + 1, m_style.indent_char, count - 1);
|
|
}
|
|
w += count;
|
|
};
|
|
// a string that is not a plain string of the source (out of line, so
|
|
// that the cursor stays in a register here)
|
|
const auto escaped = [&](const node & n)
|
|
{
|
|
m_out.set_cursor(w);
|
|
if (enabled(Ascii))
|
|
{
|
|
write_ascii(n);
|
|
}
|
|
else
|
|
{
|
|
write_decoded(n, Pretty);
|
|
}
|
|
w = m_out.cursor();
|
|
lim = m_out.limit();
|
|
};
|
|
|
|
// Read-only documents: the elements of a container follow it in the
|
|
// node array, so the walk goes through the array in order, and a
|
|
// frame only needs the end of its container. Editable documents: the
|
|
// elements of a moved container live elsewhere, so a frame keeps the
|
|
// position of the next element (see navigation).
|
|
// The innermost open container is kept in registers (cur; end ==
|
|
// nullptr: none), the stack holds the ones around it.
|
|
frame cur{nullptr, nullptr, false};
|
|
const node* n = root;
|
|
for (;;)
|
|
{
|
|
// write the value at n (read-only documents: and advance n)
|
|
bool opened = false;
|
|
// false positive: n comes from nav::value(), which never returns null for a valid index
|
|
// @infer-ignore NULLPTR_DEREFERENCE
|
|
switch (static_cast<value_t>(n->kind))
|
|
{
|
|
case value_t::string:
|
|
if (copied(*n, src, std::integral_constant<bool, Ascii>()))
|
|
{
|
|
room(n->len + 2);
|
|
*w++ = '"';
|
|
copy(src + n->off, n->len);
|
|
*w++ = '"';
|
|
}
|
|
else
|
|
{
|
|
escaped(*n);
|
|
}
|
|
break;
|
|
case value_t::number_integer:
|
|
case value_t::number_unsigned:
|
|
{
|
|
const std::uint32_t len = number_length(*n);
|
|
room(len);
|
|
if (enabled(Editable) && (n->flags & node_flags::storage) != 0)
|
|
{
|
|
copy_long(w, m_doc.str(*n), len); // a canonical token written by an edit
|
|
w += len;
|
|
break;
|
|
}
|
|
const char* const token = src + n->off;
|
|
if (!enabled(SourceNumbers) && NLOHMANN_VIEW_UNLIKELY(len == 2 && token[0] == '-' && token[1] == '0'))
|
|
{
|
|
*w++ = '0'; // parse() reads -0 as the integer 0
|
|
}
|
|
else
|
|
{
|
|
copy(token, len);
|
|
}
|
|
break;
|
|
}
|
|
case value_t::number_float:
|
|
if (enabled(SourceNumbers) && (n->flags & node_flags::storage) != node_flags::edited)
|
|
{
|
|
room(n->len);
|
|
copy(src + n->off, n->len);
|
|
}
|
|
else if (enabled(fast_double::value))
|
|
{
|
|
room(64);
|
|
w = write_double_at(w, *n);
|
|
}
|
|
else
|
|
{
|
|
m_out.set_cursor(w);
|
|
write_float_node(*n);
|
|
w = m_out.cursor();
|
|
lim = m_out.limit();
|
|
}
|
|
break;
|
|
case value_t::boolean:
|
|
room(8);
|
|
if ((n->flags & node_flags::is_true) != 0)
|
|
{
|
|
w = put_literal(w, "true");
|
|
}
|
|
else
|
|
{
|
|
w = put_literal(w, "false");
|
|
}
|
|
break;
|
|
case value_t::object:
|
|
case value_t::array:
|
|
{
|
|
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
|
room(8);
|
|
if (n->len == 0)
|
|
{
|
|
w = object ? put_literal(w, "{}") : put_literal(w, "[]");
|
|
}
|
|
else
|
|
{
|
|
*w++ = object ? '{' : '[';
|
|
stack.push_back(cur);
|
|
level.enter();
|
|
if (Editable)
|
|
{
|
|
cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object};
|
|
}
|
|
else
|
|
{
|
|
cur = frame{nullptr, n + n->next, object};
|
|
}
|
|
opened = true;
|
|
}
|
|
break;
|
|
}
|
|
case value_t::null:
|
|
room(8);
|
|
w = put_literal(w, "null");
|
|
break;
|
|
case value_t::binary: // LCOV_EXCL_LINE (not in a document)
|
|
case value_t::discarded: // LCOV_EXCL_LINE
|
|
default: // LCOV_EXCL_LINE
|
|
break; // LCOV_EXCL_LINE
|
|
}
|
|
if (!Editable)
|
|
{
|
|
++n; // the next node: the first element of an opened container, or the node after a scalar
|
|
}
|
|
|
|
// go to the next value: close finished containers, then separate
|
|
// (a container just opened has an element)
|
|
if (!opened)
|
|
{
|
|
for (;;)
|
|
{
|
|
if (cur.end == nullptr)
|
|
{
|
|
m_out.set_cursor(w);
|
|
m_out.finish();
|
|
return;
|
|
}
|
|
if ((Editable ? cur.pos : n) != cur.end)
|
|
{
|
|
break;
|
|
}
|
|
if (Pretty)
|
|
{
|
|
newline(level.leave());
|
|
}
|
|
room(1);
|
|
*w++ = cur.object ? '}' : ']';
|
|
cur = stack.back();
|
|
stack.pop_back();
|
|
}
|
|
room(1);
|
|
*w++ = ',';
|
|
}
|
|
if (Pretty)
|
|
{
|
|
newline(level.get());
|
|
}
|
|
const node* const at = Editable ? cur.pos : n;
|
|
if (cur.object)
|
|
{
|
|
const node& key = *at;
|
|
if (copied(key, src, std::integral_constant<bool, Ascii>()))
|
|
{
|
|
room(key.len + (Pretty ? 4 : 3));
|
|
*w++ = '"';
|
|
copy(src + key.off, key.len);
|
|
w[0] = '"';
|
|
w[1] = ':';
|
|
w += 2;
|
|
}
|
|
else
|
|
{
|
|
escaped(key);
|
|
room(Pretty ? 2 : 1);
|
|
*w++ = ':';
|
|
}
|
|
if (Pretty)
|
|
{
|
|
*w++ = ' ';
|
|
}
|
|
if (Editable)
|
|
{
|
|
n = nav::value(at + 1);
|
|
cur.pos = document_data::after(at + 1);
|
|
}
|
|
else
|
|
{
|
|
++n;
|
|
}
|
|
}
|
|
else if (Editable)
|
|
{
|
|
n = nav::value(at);
|
|
cur.pos = document_data::after(at);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// a float node as dump() writes it
|
|
void write_float_node(const node& n)
|
|
{
|
|
write_float_node(n, fast_double {});
|
|
}
|
|
|
|
void write_float_node(const node& n, std::false_type /*other*/)
|
|
{
|
|
write_float(float_value<number_float_t>(m_doc, n));
|
|
}
|
|
|
|
void write_float_node(const node& n, std::true_type /*double*/)
|
|
{
|
|
m_out.reserve(64);
|
|
m_out.set_cursor(write_double_at(m_out.cursor(), n));
|
|
}
|
|
|
|
/*!
|
|
@brief (doubles) the float at n as dump() writes it, at w (64 bytes of room)
|
|
|
|
A token of at most 15 significant digits is written from its digits,
|
|
without a conversion: two decimals of at most 15 digits are farther
|
|
apart than the rounding interval of a (normal) double (the argument
|
|
behind DBL_DIG), so the token's digits are the shortest ones of its
|
|
double, which the library's conversion writes (Zmij). Other tokens are
|
|
converted from the digits already read.
|
|
*/
|
|
char* write_double_at(char* w, const node& n)
|
|
{
|
|
const unsigned int_digits = n.extra & 0xFFu;
|
|
const unsigned frac_digits = n.extra >> 8u;
|
|
if ((n.flags & node_flags::storage) != node_flags::edited && int_digits + frac_digits <= 19)
|
|
{
|
|
const auto* const first = reinterpret_cast<const unsigned char*>(m_doc.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
const float_significand d = layout_decimal(first, first + n.len, int_digits, frac_digits, reinterpret_cast<const unsigned char*>(m_doc.src + m_doc.size)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
// (the exponent keeps the value far from subnormals and overflow)
|
|
if (d.w != 0 && d.w < 1000000000000000u && d.exponent >= -290 && d.exponent <= 290)
|
|
{
|
|
*w = '-';
|
|
w += d.negative ? 1 : 0;
|
|
// (without leading zeros, all digits of the token count; the
|
|
// check also keeps an image that was only checked for bounds,
|
|
// whose token may not be made of digits, from the counted
|
|
// overload)
|
|
const auto& powers = ::nlohmann::detail::dtoa_impl::powers_of_ten();
|
|
const unsigned count = int_digits + frac_digits;
|
|
return count - 1u < 15u && d.w >= powers[count - 1u] && d.w < powers[count]
|
|
? ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(count), static_cast<int>(d.exponent))
|
|
: ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(d.exponent));
|
|
}
|
|
return write_double_value_at(w, decimal_to_float<double>(d)); // (without reading the token again)
|
|
}
|
|
return write_double_value_at(w, static_cast<double>(float_value<number_float_t>(m_doc, n)));
|
|
}
|
|
|
|
/// n bytes of text at w
|
|
static char* write_text_at(char* w, const char* text, std::size_t n) noexcept
|
|
{
|
|
std::memcpy(w, text, n);
|
|
return w + n;
|
|
}
|
|
|
|
/// a double as dump() writes it, at w (64 bytes of room)
|
|
static char* write_double_value_at(char* w, double x)
|
|
{
|
|
// (from the bits: without the checks of to_chars())
|
|
auto bits = bit_cast<std::uint64_t>(x);
|
|
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
|
|
{
|
|
return write_text_at(w, "null", 4);
|
|
}
|
|
*w = '-';
|
|
w += bits >> 63u;
|
|
bits &= ~(std::uint64_t{1} << 63u);
|
|
if (bits == 0)
|
|
{
|
|
return write_text_at(w, "0.0", 3);
|
|
}
|
|
return ::nlohmann::detail::dtoa_impl::write_shortest(w, ::nlohmann::detail::zmij::to_shortest(bits));
|
|
}
|
|
|
|
/// as serializer::dump_float()
|
|
void write_float(number_float_t x)
|
|
{
|
|
if (!std::isfinite(x))
|
|
{
|
|
m_out.put("null");
|
|
return;
|
|
}
|
|
write_float(x, std::integral_constant<bool, ::nlohmann::detail::has_native_float_format<number_float_t>::value>());
|
|
}
|
|
|
|
void write_float(number_float_t x, std::true_type /*has_native_float_format*/)
|
|
{
|
|
std::array<char, 64> buf{};
|
|
const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x);
|
|
m_out.put(buf.data(), static_cast<std::size_t>(end - buf.data()));
|
|
}
|
|
|
|
void write_float(number_float_t x, std::false_type /*has_native_float_format*/)
|
|
{
|
|
// other types (e.g. long double) are rare: the library writes them
|
|
const string_t s = BasicJsonType(x).dump();
|
|
m_out.put(s.data(), s.size());
|
|
}
|
|
|
|
/// throw what basic_json::dump() throws for a string that is not valid UTF-8
|
|
NLOHMANN_VIEW_NOINLINE static void invalid_utf8(const unsigned char* s, std::size_t n)
|
|
{
|
|
const string_t dumped = BasicJsonType(string_t(reinterpret_cast<const char*>(s), n)).dump(); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
static_cast<void>(dumped);
|
|
}
|
|
|
|
template<bool EnsureAscii>
|
|
void write_codepoint(std::uint32_t codepoint, const unsigned char* bytes, std::size_t len)
|
|
{
|
|
switch (codepoint)
|
|
{
|
|
case 0x08:
|
|
m_out.put("\\b");
|
|
return;
|
|
case 0x09:
|
|
m_out.put("\\t");
|
|
return;
|
|
case 0x0A:
|
|
m_out.put("\\n");
|
|
return;
|
|
case 0x0C:
|
|
m_out.put("\\f");
|
|
return;
|
|
case 0x0D:
|
|
m_out.put("\\r");
|
|
return;
|
|
case 0x22:
|
|
m_out.put("\\\"");
|
|
return;
|
|
case 0x5C:
|
|
m_out.put("\\\\");
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
if (codepoint <= 0x1F || (EnsureAscii && codepoint >= 0x7F))
|
|
{
|
|
if (codepoint <= 0xFFFF)
|
|
{
|
|
write_u_escape(codepoint);
|
|
}
|
|
else
|
|
{
|
|
write_u_escape(0xD7C0u + (codepoint >> 10u));
|
|
write_u_escape(0xDC00u + (codepoint & 0x3FFu));
|
|
}
|
|
return;
|
|
}
|
|
m_out.put(reinterpret_cast<const char*>(bytes), len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) LCOV_EXCL_LINE (printable characters are copied in runs)
|
|
}
|
|
|
|
void write_u_escape(std::uint32_t u)
|
|
{
|
|
std::array<char, 6> e = {{'\\', 'u', 0, 0, 0, 0}};
|
|
hex4_lower(static_cast<std::uint16_t>(u), &e[2]);
|
|
m_out.put(e.data(), e.size());
|
|
}
|
|
|
|
const document_data& m_doc;
|
|
output_buffer<string_t> m_out;
|
|
const dump_style m_style;
|
|
};
|
|
|
|
} // namespace view
|
|
} // namespace detail
|
|
NLOHMANN_JSON_NAMESPACE_END
|