* Write the indented output of json_view's dump() with the fast writer
The compact writer of dump() (cursor in a register, fixed-size copies of
tokens) served dump(-1) only; every dump(indent), and everything with
ensure_ascii that was indented, went through a generic walk that calls put()
for each token and put_repeated() (a memset call) for each line break. The
fast writer now writes indented output, too, with or without ensure_ascii, and
the generic walk is gone. A line break is a copy of a 64-byte ruler (a line
break and 63 indentation characters) when the line is at most 64 bytes, and a
store plus memset beyond that. The depth is counted only in the indented
instantiations (counting it in the compact one cost an editable document 5 %
with Clang). The indented walks are out of line (dump_pretty()): inlined
into dump() next to the compact ones, they made compact dumps of integer
arrays 7 % slower.
A decoded string (one with escape sequences) of a damaged image loaded with
image_check::bounds can contain invalid UTF-8. As before, the compact dump
writes such bytes unchanged, and the indented dump throws type_error.316; its
runs are found by the lexer's validating scan (a scalar validation of every
sequence made dump(2) of decoded CJK text twice as slow as before).
dump(2) instructions per call, macOS arm64 (before -> after):
twitter citm canada jeopardy(4k)
json 3.75M -> 1.85M 8.28M -> 3.93M 67.1M -> 56.0M 9.56M -> 4.81M
editable 3.82M -> 1.84M 8.48M -> 4.02M 68.1M -> 56.2M 9.66M -> 4.75M
Linux instructions (GCC 13 / Clang 18), dump(2) and dump(2, ' ', true):
twitter -25 % / -30 % -11 % / -11 %
citm -29 % / -28 % -24 % / -24 %
jeopardy(4k) -25 % / -32 % -20 % / -24 %
CJK text with \u escapes -6 % / -11 % -2 % / -2 %
The compact dump is unchanged (within +0.4 % on Linux, within noise on the Mac).
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Scan the strings of json_view's dump() with ensure_ascii 16 bytes at a time
With ensure_ascii, a string of the source that has a byte >= 0x7F was written
by a generic path: put() per piece and a validate-and-decode step for every
character beyond ASCII. It now goes through a writer with the cursor in a
local variable. The part of the string that has nothing to escape is found
16 bytes at a time (SSE2 or NEON; the existing 8-byte scan without vector
support) and copied; strings that are ASCII throughout are still decided by
the 8-byte check of dump() with ensure_ascii (a 16-byte scan for them was
2-5 % slower for short strings). The two-byte sequences (lead C2..DF) and
the three-byte sequences whose continuation bytes may have any value (lead
E1..EF except ED) are decoded with a few compares; E0, ED, four-byte
sequences, and damaged input go through validate_one_utf8() as before, so
damaged images still throw type_error.316.
dump(-1, ' ', true) instructions per call, macOS arm64 (develop -> now);
the compact dump without ensure_ascii and dumps of ASCII text are unchanged:
twitter 5.42M -> 3.57M citm 3.28M -> 3.24M jeopardy(4k) 5.78M -> 5.65M
CJK text, raw 66.5M -> 35.3M escaped \u 32.7M -> 17.0M
Latin text with accents 11.5M -> 9.4M ASCII text 4.40M -> 4.40M
Linux instructions (GCC 13 / Clang 18), dump(-1, ' ', true):
twitter -9 % / -33 % CJK text -14 % / -47 % Latin text -8 % / -18 %
citm -0.2 % / -0.4 % ASCII text -0.1 % / -0.3 %
The compact dump without ensure_ascii is unchanged (canada 0.0 %).
The unit test covers every character of the Basic Multilingual Plane, and a
character that needs an escape at each position of strings of 0..40 bytes.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Find the end of the plain run of a decoded string 16 bytes at a time with SSE2
The strings of a document that had escape sequences are written in runs
without a quote, backslash, or control character; the compact dump found the
end of a run 8 bytes at a time with a SWAR test. With SSE2 (x86-64) it now
compares 16 bytes per step. NEON keeps the SWAR code: there the mask of a
step costs more than it saves on the short runs that are typical here.
Instructions of dump(), Linux (GCC 13 / Clang 18):
twitter.json 1.99M -> 1.88M (-5.2 %) / 1.97M -> 1.92M (-3.0 %)
CJK text with \u escapes 5.98M -> 4.52M (-24.5 %) / 5.30M -> 4.25M (-19.9 %)
ASCII text with a few escapes 4.28M -> 4.09M (-4.4 %) / 4.24M -> 4.11M (-3.2 %)
jeopardy (4,000 questions) 5.08M -> 4.78M (-5.8 %) / 4.97M -> 4.80M (-3.4 %)
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Rename depth_counter's open() and close() to enter() and leave()
Flawfinder took them for the file functions open() and close().
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Fix the clang-tidy findings in json_view's dump and its tests
- the SSE2 loop of the string scan is within NOLINTBEGIN/END
(portability-simd-intrinsics), as in to_chars.hpp
- depth_counter<false>'s leave() and get() keep the interface of
depth_counter<true> instead of being static
- write_ascii() compares the room left as a size_t variable
- the test's texts are a std::array, and a JSON fragment with escapes
is a raw string literal
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Move dump_ascii() and dump_pretty() next to dump()
Address a review comment: the two dispatchers that dump() calls now follow
it directly.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
---------
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
1164 lines
42 KiB
C++
1164 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;
|
|
}
|
|
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>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024));
|
|
write_float(x, std::integral_constant<bool, is_ieee_single_or_double>());
|
|
}
|
|
|
|
void write_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
|
|
{
|
|
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 /*is_ieee_single_or_double*/)
|
|
{
|
|
// 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)
|
|
{
|
|
static constexpr const char* hex = "0123456789abcdef";
|
|
const std::array<char, 6> e = {{'\\', 'u', hex[(u >> 12u) & 0xFu], hex[(u >> 8u) & 0xFu], hex[(u >> 4u) & 0xFu], hex[u & 0xFu]}};
|
|
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
|