mirror of
https://github.com/nlohmann/json.git
synced 2026-10-22 22:08:55 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cf9df3eae | ||
|
|
86049706a0 | ||
|
|
cc946adf68 | ||
|
|
272d07b48b | ||
|
|
ef45593bda |
No files matched your search
@@ -86,7 +86,7 @@ JSON_INLINE_VARIABLE constexpr std::size_t max_valueless_container_size = 1 << 2
|
|||||||
/*!
|
/*!
|
||||||
@brief deserialization of BJData, BON8, BSON, CBOR, MessagePack, and UBJSON values
|
@brief deserialization of BJData, BON8, BSON, CBOR, MessagePack, and UBJSON values
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_binary_parser<BasicJsonType, InputAdapterType>>
|
template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_parser<BasicJsonType, InputAdapterType>>
|
||||||
class binary_reader
|
class binary_reader
|
||||||
{
|
{
|
||||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -14,7 +14,6 @@
|
|||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
#include <cstring> // memcmp
|
#include <cstring> // memcmp
|
||||||
#include <initializer_list> // initializer_list
|
#include <initializer_list> // initializer_list
|
||||||
#include <limits> // numeric_limits
|
|
||||||
#include <string> // char_traits, string
|
#include <string> // char_traits, string
|
||||||
#include <utility> // move
|
#include <utility> // move
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
@@ -1817,15 +1816,6 @@ scan_number_done:
|
|||||||
/// significand (see scan_number_bulk_contiguous())
|
/// significand (see scan_number_bulk_contiguous())
|
||||||
bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept
|
bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept
|
||||||
{
|
{
|
||||||
// accept() only needs to know that the value is finite (see the parser):
|
|
||||||
// w < 10^19, so a value below 10^max_exponent10 cannot overflow, and a
|
|
||||||
// value that underflows is a finite number as well. Only a token that
|
|
||||||
// may overflow has to be converted to find out.
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(discard_number_values && s.exponent + 19 <= std::numeric_limits<number_float_t>::max_exponent10))
|
|
||||||
{
|
|
||||||
value_float = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
value_float = decimal_to_float<number_float_t>(s);
|
value_float = decimal_to_float<number_float_t>(s);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ using parser_callback_t =
|
|||||||
template<typename SAX>
|
template<typename SAX>
|
||||||
struct sax_ignores_number_text : std::false_type {};
|
struct sax_ignores_number_text : std::false_type {};
|
||||||
|
|
||||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect>
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType, Collect>> : std::true_type {};
|
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
||||||
|
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
||||||
@@ -131,7 +131,7 @@ class parser
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
json_sax_dom_parser<BasicJsonType, InputAdapterType, true> sdp(result, allow_exceptions, &m_lexer);
|
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
||||||
|
|
||||||
// in case of an error, return a discarded value
|
// in case of an error, return a discarded value
|
||||||
if (!parse_dom(sdp, strict))
|
if (!parse_dom(sdp, strict))
|
||||||
@@ -250,13 +250,6 @@ class parser
|
|||||||
// value to avoid a goto (see comment where set to true)
|
// value to avoid a goto (see comment where set to true)
|
||||||
bool skip_to_state_evaluation = false;
|
bool skip_to_state_evaluation = false;
|
||||||
|
|
||||||
// a document that starts with a container pushes onto the stack at
|
|
||||||
// once: allocate it in one step instead of growing it from 1 byte
|
|
||||||
if (last_token == token_type::begin_object || last_token == token_type::begin_array)
|
|
||||||
{
|
|
||||||
states.reserve(32);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (!skip_to_state_evaluation)
|
if (!skip_to_state_evaluation)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ template<typename BasicJsonType, bool Editable>
|
|||||||
BasicJsonType materialize(const document_data& d, const node* n)
|
BasicJsonType materialize(const document_data& d, const node* n)
|
||||||
{
|
{
|
||||||
using string_t = typename BasicJsonType::string_t;
|
using string_t = typename BasicJsonType::string_t;
|
||||||
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>, true>;
|
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>>;
|
||||||
using nav = navigation<Editable>;
|
using nav = navigation<Editable>;
|
||||||
|
|
||||||
// the open containers: their next element (or key of the next member),
|
// the open containers: their next element (or key of the next member),
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||||
#include <nlohmann/detail/view/node.hpp>
|
#include <nlohmann/detail/view/node.hpp>
|
||||||
#include <nlohmann/detail/view/number.hpp>
|
#include <nlohmann/detail/view/number.hpp>
|
||||||
|
#include <nlohmann/detail/view/simd.hpp>
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -80,13 +81,6 @@ class output_buffer
|
|||||||
put(s, N - 1);
|
put(s, N - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void put_repeated(char c, std::size_t n)
|
|
||||||
{
|
|
||||||
reserve(n);
|
|
||||||
std::memset(m_pos, c, n);
|
|
||||||
m_pos += n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the write position and the end of the writable space, for a writer
|
/// 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)
|
/// that keeps the position in a local variable (set_cursor() hands it back)
|
||||||
char* cursor() const noexcept
|
char* cursor() const noexcept
|
||||||
@@ -143,9 +137,27 @@ class output_buffer
|
|||||||
/// image_check::bounds can have others, which are then written unchanged).
|
/// 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
|
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 ones = 0x0101010101010101ull;
|
||||||
constexpr std::uint64_t high = 0x8080808080808080ull;
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||||
std::size_t i = 0;
|
|
||||||
for (; i + 8 <= n; i += 8)
|
for (; i + 8 <= n; i += 8)
|
||||||
{
|
{
|
||||||
const std::uint64_t v = read_eight_bytes(s + i);
|
const std::uint64_t v = read_eight_bytes(s + i);
|
||||||
@@ -196,6 +208,70 @@ inline bool is_ascii_without_delete(const char* s, std::size_t n) noexcept
|
|||||||
return true;
|
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
|
/// 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
|
/// 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
|
/// shallow documents need no allocation. The top is a pointer, as in
|
||||||
@@ -275,6 +351,39 @@ NLOHMANN_VIEW_ALWAYS_INLINE char* put_literal(char* w, const char (&s)[N]) noexc
|
|||||||
return w + 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
|
/// how the view's dump() writes a value
|
||||||
struct dump_style
|
struct dump_style
|
||||||
{
|
{
|
||||||
@@ -311,139 +420,95 @@ class view_serializer
|
|||||||
|
|
||||||
void dump(const node* root)
|
void dump(const node* root)
|
||||||
{
|
{
|
||||||
if (!m_style.pretty)
|
if (m_style.ensure_ascii)
|
||||||
{
|
{
|
||||||
if (m_style.ensure_ascii)
|
dump_ascii(root);
|
||||||
{
|
|
||||||
dump_compact_ascii(root);
|
|
||||||
}
|
|
||||||
else if (m_style.source_numbers)
|
|
||||||
{
|
|
||||||
dump_compact<true, false>(root);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dump_compact<false, false>(root);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
struct frame
|
else if (m_style.pretty)
|
||||||
{
|
{
|
||||||
const node* pos; ///< next element, or key of the next member
|
dump_pretty(root);
|
||||||
const node* end;
|
}
|
||||||
bool object;
|
else if (m_style.source_numbers)
|
||||||
bool first; ///< nothing written yet
|
|
||||||
};
|
|
||||||
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 node* n = root;
|
|
||||||
for (;;)
|
|
||||||
{
|
{
|
||||||
// write the value at n
|
dump_text<true, false, false>(root);
|
||||||
if (is_container(*n))
|
}
|
||||||
{
|
else
|
||||||
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
{
|
||||||
if (n->len == 0)
|
dump_text<false, false, false>(root);
|
||||||
{
|
|
||||||
m_out.put(object ? "{}" : "[]", 2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_out.put(object ? '{' : '[');
|
|
||||||
stack.push_back(frame{nav::first(m_doc, n), nav::end(m_doc, n), object, true});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_scalar(*n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// go to the next value: close finished containers, then separate
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
if (stack.empty())
|
|
||||||
{
|
|
||||||
m_out.finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (stack.back().pos == stack.back().end)
|
|
||||||
{
|
|
||||||
const bool object = stack.back().object;
|
|
||||||
stack.pop_back();
|
|
||||||
newline(stack.size());
|
|
||||||
m_out.put(object ? '}' : ']');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
frame& f = stack.back();
|
|
||||||
if (!f.first)
|
|
||||||
{
|
|
||||||
m_out.put(',');
|
|
||||||
}
|
|
||||||
f.first = false;
|
|
||||||
newline(stack.size());
|
|
||||||
if (f.object)
|
|
||||||
{
|
|
||||||
write_string(*f.pos);
|
|
||||||
if (m_style.pretty)
|
|
||||||
{
|
|
||||||
m_out.put(": ");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_out.put(':');
|
|
||||||
}
|
|
||||||
n = nav::value(f.pos + 1);
|
|
||||||
f.pos = document_data::after(f.pos + 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
n = nav::value(f.pos);
|
|
||||||
f.pos = document_data::after(f.pos);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// a string that is not a plain string of the source (decoded, or written
|
/// 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
|
/// by an edit), without ensure_ascii: runs without characters to escape
|
||||||
/// are copied
|
/// are copied. If validate is set, the string must be valid UTF-8: the
|
||||||
NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n)
|
/// 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)
|
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
m_out.put('"');
|
m_out.put('"');
|
||||||
for (std::size_t i = 0; i < n.len;)
|
for (std::size_t i = 0; i < n.len;)
|
||||||
{
|
{
|
||||||
const std::size_t run = plain_output_run(s + i, n.len - i);
|
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)
|
if (run != 0)
|
||||||
{
|
{
|
||||||
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
i += run;
|
i += run;
|
||||||
continue;
|
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
|
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
m_out.put('"');
|
m_out.put('"');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// dump_compact() with ensure_ascii, out of line: the walk without it is
|
/// dump_text() with indentation, out of line: the compact walk is faster
|
||||||
/// faster when it is not merged with this one into dump()
|
/// when it is not merged with the indented ones into dump()
|
||||||
NLOHMANN_VIEW_NOINLINE void dump_compact_ascii(const node* root)
|
NLOHMANN_VIEW_NOINLINE void dump_pretty(const node* root)
|
||||||
{
|
{
|
||||||
if (m_style.source_numbers)
|
if (m_style.source_numbers)
|
||||||
{
|
{
|
||||||
dump_compact<true, true>(root);
|
dump_text<true, true, false>(root);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dump_compact<false, true>(root);
|
dump_text<false, true, false>(root);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// whether dump_compact() copies a string or key from the source: one of
|
/// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
/// the source, and with ensure_ascii one without bytes >= 0x7F (an overload
|
||||||
/// each, so that the walk without ensure_ascii is compiled exactly as
|
/// each, so that the walk without ensure_ascii is compiled exactly as
|
||||||
/// before: with GCC, a condition that only folds away later changed its
|
/// before: with GCC, a condition that only folds away later changed its
|
||||||
@@ -458,13 +523,155 @@ class view_serializer
|
|||||||
return (n.flags & node_flags::storage) == 0 && is_ascii_without_delete(src + n.off, n.len);
|
return (n.flags & node_flags::storage) == 0 && is_ascii_without_delete(src + n.off, n.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// a string with ensure_ascii (dump_compact()), out of line
|
/*!
|
||||||
NLOHMANN_VIEW_NOINLINE void write_ascii_string(const node& n)
|
@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)
|
||||||
{
|
{
|
||||||
write_string(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_compact() that are not fixed-size moves (long
|
/// 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
|
/// 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
|
/// 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
|
NLOHMANN_VIEW_NOINLINE static void copy_long(char* to, const char* from, std::size_t n) noexcept
|
||||||
@@ -481,7 +688,7 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief the compact output (the default dump(), or with ensure_ascii)
|
@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
|
The same walk as dump(), with the write position in a local variable
|
||||||
(stores through char pointers would otherwise force a reload of the
|
(stores through char pointers would otherwise force a reload of the
|
||||||
@@ -489,11 +696,11 @@ class view_serializer
|
|||||||
the source copied by fixed-size moves of 32 bytes where the source has
|
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
|
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
|
keeps 64 bytes of slack for the overshoot. With ensure_ascii (Ascii), a
|
||||||
string of the source is copied only if it has no byte >= 0x7F (see
|
string of the source is copied only if it has nothing to escape (see
|
||||||
is_ascii_without_delete()); any other string is written by write_string().
|
copied()); any other string is written by write_ascii().
|
||||||
*/
|
*/
|
||||||
template<bool SourceNumbers, bool Ascii>
|
template<bool SourceNumbers, bool Pretty, bool Ascii>
|
||||||
void dump_compact(const node* root)
|
void dump_text(const node* root)
|
||||||
{
|
{
|
||||||
struct frame
|
struct frame
|
||||||
{
|
{
|
||||||
@@ -508,6 +715,16 @@ class view_serializer
|
|||||||
const char* const src_end = src + m_doc.size;
|
const char* const src_end = src + m_doc.size;
|
||||||
char* w = m_out.cursor();
|
char* w = m_out.cursor();
|
||||||
char* lim = m_out.limit();
|
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
|
// room for n bytes and the slack
|
||||||
const auto room = [&](std::size_t n)
|
const auto room = [&](std::size_t n)
|
||||||
{
|
{
|
||||||
@@ -539,6 +756,22 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
w += 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
|
// a string that is not a plain string of the source (out of line, so
|
||||||
// that the cursor stays in a register here)
|
// that the cursor stays in a register here)
|
||||||
const auto escaped = [&](const node & n)
|
const auto escaped = [&](const node & n)
|
||||||
@@ -546,11 +779,11 @@ class view_serializer
|
|||||||
m_out.set_cursor(w);
|
m_out.set_cursor(w);
|
||||||
if (enabled(Ascii))
|
if (enabled(Ascii))
|
||||||
{
|
{
|
||||||
write_ascii_string(n);
|
write_ascii(n);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
write_decoded(n);
|
write_decoded(n, Pretty);
|
||||||
}
|
}
|
||||||
w = m_out.cursor();
|
w = m_out.cursor();
|
||||||
lim = m_out.limit();
|
lim = m_out.limit();
|
||||||
@@ -651,6 +884,7 @@ class view_serializer
|
|||||||
{
|
{
|
||||||
*w++ = object ? '{' : '[';
|
*w++ = object ? '{' : '[';
|
||||||
stack.push_back(cur);
|
stack.push_back(cur);
|
||||||
|
level.enter();
|
||||||
if (Editable)
|
if (Editable)
|
||||||
{
|
{
|
||||||
cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object};
|
cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object};
|
||||||
@@ -693,6 +927,10 @@ class view_serializer
|
|||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (Pretty)
|
||||||
|
{
|
||||||
|
newline(level.leave());
|
||||||
|
}
|
||||||
room(1);
|
room(1);
|
||||||
*w++ = cur.object ? '}' : ']';
|
*w++ = cur.object ? '}' : ']';
|
||||||
cur = stack.back();
|
cur = stack.back();
|
||||||
@@ -701,13 +939,17 @@ class view_serializer
|
|||||||
room(1);
|
room(1);
|
||||||
*w++ = ',';
|
*w++ = ',';
|
||||||
}
|
}
|
||||||
|
if (Pretty)
|
||||||
|
{
|
||||||
|
newline(level.get());
|
||||||
|
}
|
||||||
const node* const at = Editable ? cur.pos : n;
|
const node* const at = Editable ? cur.pos : n;
|
||||||
if (cur.object)
|
if (cur.object)
|
||||||
{
|
{
|
||||||
const node& key = *at;
|
const node& key = *at;
|
||||||
if (copied(key, src, std::integral_constant<bool, Ascii>()))
|
if (copied(key, src, std::integral_constant<bool, Ascii>()))
|
||||||
{
|
{
|
||||||
room(key.len + 3);
|
room(key.len + (Pretty ? 4 : 3));
|
||||||
*w++ = '"';
|
*w++ = '"';
|
||||||
copy(src + key.off, key.len);
|
copy(src + key.off, key.len);
|
||||||
w[0] = '"';
|
w[0] = '"';
|
||||||
@@ -717,9 +959,13 @@ class view_serializer
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
escaped(key);
|
escaped(key);
|
||||||
room(1);
|
room(Pretty ? 2 : 1);
|
||||||
*w++ = ':';
|
*w++ = ':';
|
||||||
}
|
}
|
||||||
|
if (Pretty)
|
||||||
|
{
|
||||||
|
*w++ = ' ';
|
||||||
|
}
|
||||||
if (Editable)
|
if (Editable)
|
||||||
{
|
{
|
||||||
n = nav::value(at + 1);
|
n = nav::value(at + 1);
|
||||||
@@ -738,69 +984,6 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void newline(std::size_t level)
|
|
||||||
{
|
|
||||||
if (m_style.pretty)
|
|
||||||
{
|
|
||||||
m_out.put('\n');
|
|
||||||
m_out.put_repeated(m_style.indent_char, level * m_style.indent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void write_scalar(const node& n)
|
|
||||||
{
|
|
||||||
switch (static_cast<value_t>(n.kind))
|
|
||||||
{
|
|
||||||
case value_t::null:
|
|
||||||
m_out.put("null");
|
|
||||||
break;
|
|
||||||
case value_t::boolean:
|
|
||||||
if ((n.flags & node_flags::is_true) != 0)
|
|
||||||
{
|
|
||||||
m_out.put("true");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_out.put("false");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case value_t::string:
|
|
||||||
write_string(n);
|
|
||||||
break;
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
{
|
|
||||||
const char* const token = m_doc.str(n);
|
|
||||||
const std::uint32_t len = number_length(n);
|
|
||||||
if (!m_style.source_numbers && len == 2 && token[0] == '-' && token[1] == '0')
|
|
||||||
{
|
|
||||||
m_out.put('0'); // parse() reads -0 as the integer 0
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_out.put(token, len);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case value_t::number_float:
|
|
||||||
if (m_style.source_numbers && (n.flags & node_flags::storage) != node_flags::edited)
|
|
||||||
{
|
|
||||||
m_out.put(m_doc.str(n), n.len); // (a float set by an edit is written as with shortest)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_float_node(n);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case value_t::object: // LCOV_EXCL_LINE (containers are written by dump())
|
|
||||||
case value_t::array: // LCOV_EXCL_LINE
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// a float node as dump() writes it
|
/// a float node as dump() writes it
|
||||||
void write_float_node(const node& n)
|
void write_float_node(const node& n)
|
||||||
{
|
{
|
||||||
@@ -911,72 +1094,6 @@ class view_serializer
|
|||||||
m_out.put(s.data(), s.size());
|
m_out.put(s.data(), s.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_string(const node& n)
|
|
||||||
{
|
|
||||||
const char* const s = m_doc.str(n);
|
|
||||||
m_out.put('"');
|
|
||||||
if ((n.flags & node_flags::storage) == 0 && (!m_style.ensure_ascii || is_ascii_without_delete(s, n.len)))
|
|
||||||
{
|
|
||||||
// a string of the source without escape sequences has nothing to
|
|
||||||
// escape (with ensure_ascii, if it is printable ASCII)
|
|
||||||
m_out.put(s, n.len);
|
|
||||||
}
|
|
||||||
else if (m_style.ensure_ascii)
|
|
||||||
{
|
|
||||||
write_escaped<true>(reinterpret_cast<const unsigned char*>(s), n.len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_escaped<false>(reinterpret_cast<const unsigned char*>(s), n.len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
}
|
|
||||||
m_out.put('"');
|
|
||||||
}
|
|
||||||
|
|
||||||
/// as serializer::dump_escaped(); 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
|
|
||||||
template<bool EnsureAscii>
|
|
||||||
void write_escaped(const unsigned char* s, std::size_t n)
|
|
||||||
{
|
|
||||||
std::size_t i = 0;
|
|
||||||
while (i < n)
|
|
||||||
{
|
|
||||||
std::size_t run = 0;
|
|
||||||
if (!EnsureAscii)
|
|
||||||
{
|
|
||||||
run = string_bulk_run(s + i, n - i);
|
|
||||||
}
|
|
||||||
else if (is_ascii_copyable(s[i]))
|
|
||||||
{
|
|
||||||
run = find_ascii_copyable_run(s + i, n - i);
|
|
||||||
}
|
|
||||||
if (run != 0)
|
|
||||||
{
|
|
||||||
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
i += run;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
std::uint32_t codepoint = s[i];
|
|
||||||
std::size_t len = 1;
|
|
||||||
if (codepoint >= 0x80)
|
|
||||||
{
|
|
||||||
len = validate_one_utf8(s + i, n - i);
|
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(len == 0))
|
|
||||||
{
|
|
||||||
invalid_utf8(s, n);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
codepoint &= 0xFFu >> (len + 1);
|
|
||||||
for (std::size_t k = 1; k < len; ++k)
|
|
||||||
{
|
|
||||||
codepoint = (codepoint << 6u) | (s[i + k] & 0x3Fu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
write_codepoint<EnsureAscii>(codepoint, s + i, len);
|
|
||||||
i += len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// throw what basic_json::dump() throws for a string that is not valid UTF-8
|
/// 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)
|
NLOHMANN_VIEW_NOINLINE static void invalid_utf8(const unsigned char* s, std::size_t n)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -158,10 +158,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
friend class ::nlohmann::detail::binary_writer;
|
friend class ::nlohmann::detail::binary_writer;
|
||||||
template<typename BasicJsonType, typename InputType, typename SAX>
|
template<typename BasicJsonType, typename InputType, typename SAX>
|
||||||
friend class ::nlohmann::detail::binary_reader;
|
friend class ::nlohmann::detail::binary_reader;
|
||||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect>
|
|
||||||
friend class ::nlohmann::detail::json_sax_dom_parser;
|
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
friend class ::nlohmann::detail::json_sax_dom_binary_parser;
|
friend class ::nlohmann::detail::json_sax_dom_parser;
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
friend class ::nlohmann::detail::json_sax_dom_callback_parser;
|
friend class ::nlohmann::detail::json_sax_dom_callback_parser;
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
@@ -6289,7 +6287,7 @@ public:
|
|||||||
from_ubjson/from_bjdata/from_bon8/from_bson overloads
|
from_ubjson/from_bjdata/from_bon8/from_bson overloads
|
||||||
|
|
||||||
Building the @ref binary_reader as a named local, rather than as a
|
Building the @ref binary_reader as a named local, rather than as a
|
||||||
temporary that @ref detail::json_sax_dom_binary_parser::parse is called on in
|
temporary that @ref detail::json_sax_dom_parser::parse is called on in
|
||||||
the same expression, avoids a false-positive cppcheck accessMoved
|
the same expression, avoids a false-positive cppcheck accessMoved
|
||||||
warning in each of the 16 callers.
|
warning in each of the 16 callers.
|
||||||
*/
|
*/
|
||||||
@@ -6300,7 +6298,7 @@ public:
|
|||||||
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
|
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
|
||||||
{
|
{
|
||||||
basic_json result;
|
basic_json result;
|
||||||
detail::json_sax_dom_binary_parser<basic_json, InputAdapterType> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, InputAdapterType> sdp(result, allow_exceptions);
|
||||||
binary_reader<InputAdapterType> reader(std::move(ia), format, error_handler);
|
binary_reader<InputAdapterType> reader(std::move(ia), format, error_handler);
|
||||||
if (!reader.sax_parse(&sdp, strict, tag_handler))
|
if (!reader.sax_parse(&sdp, strict, tag_handler))
|
||||||
{
|
{
|
||||||
|
|||||||
Generated
+70
-1135
File diff suppressed because it is too large.
Load diff
Generated
+366
-248
@@ -5770,7 +5770,7 @@ template<typename BasicJsonType, bool Editable>
|
|||||||
BasicJsonType materialize(const document_data& d, const node* n)
|
BasicJsonType materialize(const document_data& d, const node* n)
|
||||||
{
|
{
|
||||||
using string_t = typename BasicJsonType::string_t;
|
using string_t = typename BasicJsonType::string_t;
|
||||||
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>, true>;
|
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>>;
|
||||||
using nav = navigation<Editable>;
|
using nav = navigation<Editable>;
|
||||||
|
|
||||||
// the open containers: their next element (or key of the next member),
|
// the open containers: their next element (or key of the next member),
|
||||||
@@ -6089,6 +6089,8 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
|
|
||||||
// #include <nlohmann/detail/view/number.hpp>
|
// #include <nlohmann/detail/view/number.hpp>
|
||||||
|
|
||||||
|
// #include <nlohmann/detail/view/simd.hpp>
|
||||||
|
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -6146,13 +6148,6 @@ class output_buffer
|
|||||||
put(s, N - 1);
|
put(s, N - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void put_repeated(char c, std::size_t n)
|
|
||||||
{
|
|
||||||
reserve(n);
|
|
||||||
std::memset(m_pos, c, n);
|
|
||||||
m_pos += n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the write position and the end of the writable space, for a writer
|
/// 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)
|
/// that keeps the position in a local variable (set_cursor() hands it back)
|
||||||
char* cursor() const noexcept
|
char* cursor() const noexcept
|
||||||
@@ -6209,9 +6204,27 @@ class output_buffer
|
|||||||
/// image_check::bounds can have others, which are then written unchanged).
|
/// 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
|
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 ones = 0x0101010101010101ull;
|
||||||
constexpr std::uint64_t high = 0x8080808080808080ull;
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||||
std::size_t i = 0;
|
|
||||||
for (; i + 8 <= n; i += 8)
|
for (; i + 8 <= n; i += 8)
|
||||||
{
|
{
|
||||||
const std::uint64_t v = read_eight_bytes(s + i);
|
const std::uint64_t v = read_eight_bytes(s + i);
|
||||||
@@ -6262,6 +6275,70 @@ inline bool is_ascii_without_delete(const char* s, std::size_t n) noexcept
|
|||||||
return true;
|
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
|
/// 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
|
/// 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
|
/// shallow documents need no allocation. The top is a pointer, as in
|
||||||
@@ -6341,6 +6418,39 @@ NLOHMANN_VIEW_ALWAYS_INLINE char* put_literal(char* w, const char (&s)[N]) noexc
|
|||||||
return w + 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
|
/// how the view's dump() writes a value
|
||||||
struct dump_style
|
struct dump_style
|
||||||
{
|
{
|
||||||
@@ -6377,139 +6487,95 @@ class view_serializer
|
|||||||
|
|
||||||
void dump(const node* root)
|
void dump(const node* root)
|
||||||
{
|
{
|
||||||
if (!m_style.pretty)
|
if (m_style.ensure_ascii)
|
||||||
{
|
{
|
||||||
if (m_style.ensure_ascii)
|
dump_ascii(root);
|
||||||
{
|
|
||||||
dump_compact_ascii(root);
|
|
||||||
}
|
|
||||||
else if (m_style.source_numbers)
|
|
||||||
{
|
|
||||||
dump_compact<true, false>(root);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dump_compact<false, false>(root);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
struct frame
|
else if (m_style.pretty)
|
||||||
{
|
{
|
||||||
const node* pos; ///< next element, or key of the next member
|
dump_pretty(root);
|
||||||
const node* end;
|
}
|
||||||
bool object;
|
else if (m_style.source_numbers)
|
||||||
bool first; ///< nothing written yet
|
|
||||||
};
|
|
||||||
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 node* n = root;
|
|
||||||
for (;;)
|
|
||||||
{
|
{
|
||||||
// write the value at n
|
dump_text<true, false, false>(root);
|
||||||
if (is_container(*n))
|
}
|
||||||
{
|
else
|
||||||
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
{
|
||||||
if (n->len == 0)
|
dump_text<false, false, false>(root);
|
||||||
{
|
|
||||||
m_out.put(object ? "{}" : "[]", 2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_out.put(object ? '{' : '[');
|
|
||||||
stack.push_back(frame{nav::first(m_doc, n), nav::end(m_doc, n), object, true});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_scalar(*n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// go to the next value: close finished containers, then separate
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
if (stack.empty())
|
|
||||||
{
|
|
||||||
m_out.finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (stack.back().pos == stack.back().end)
|
|
||||||
{
|
|
||||||
const bool object = stack.back().object;
|
|
||||||
stack.pop_back();
|
|
||||||
newline(stack.size());
|
|
||||||
m_out.put(object ? '}' : ']');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
frame& f = stack.back();
|
|
||||||
if (!f.first)
|
|
||||||
{
|
|
||||||
m_out.put(',');
|
|
||||||
}
|
|
||||||
f.first = false;
|
|
||||||
newline(stack.size());
|
|
||||||
if (f.object)
|
|
||||||
{
|
|
||||||
write_string(*f.pos);
|
|
||||||
if (m_style.pretty)
|
|
||||||
{
|
|
||||||
m_out.put(": ");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_out.put(':');
|
|
||||||
}
|
|
||||||
n = nav::value(f.pos + 1);
|
|
||||||
f.pos = document_data::after(f.pos + 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
n = nav::value(f.pos);
|
|
||||||
f.pos = document_data::after(f.pos);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// a string that is not a plain string of the source (decoded, or written
|
/// 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
|
/// by an edit), without ensure_ascii: runs without characters to escape
|
||||||
/// are copied
|
/// are copied. If validate is set, the string must be valid UTF-8: the
|
||||||
NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n)
|
/// 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)
|
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
m_out.put('"');
|
m_out.put('"');
|
||||||
for (std::size_t i = 0; i < n.len;)
|
for (std::size_t i = 0; i < n.len;)
|
||||||
{
|
{
|
||||||
const std::size_t run = plain_output_run(s + i, n.len - i);
|
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)
|
if (run != 0)
|
||||||
{
|
{
|
||||||
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
i += run;
|
i += run;
|
||||||
continue;
|
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
|
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
m_out.put('"');
|
m_out.put('"');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// dump_compact() with ensure_ascii, out of line: the walk without it is
|
/// dump_text() with indentation, out of line: the compact walk is faster
|
||||||
/// faster when it is not merged with this one into dump()
|
/// when it is not merged with the indented ones into dump()
|
||||||
NLOHMANN_VIEW_NOINLINE void dump_compact_ascii(const node* root)
|
NLOHMANN_VIEW_NOINLINE void dump_pretty(const node* root)
|
||||||
{
|
{
|
||||||
if (m_style.source_numbers)
|
if (m_style.source_numbers)
|
||||||
{
|
{
|
||||||
dump_compact<true, true>(root);
|
dump_text<true, true, false>(root);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dump_compact<false, true>(root);
|
dump_text<false, true, false>(root);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// whether dump_compact() copies a string or key from the source: one of
|
/// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
/// the source, and with ensure_ascii one without bytes >= 0x7F (an overload
|
||||||
/// each, so that the walk without ensure_ascii is compiled exactly as
|
/// each, so that the walk without ensure_ascii is compiled exactly as
|
||||||
/// before: with GCC, a condition that only folds away later changed its
|
/// before: with GCC, a condition that only folds away later changed its
|
||||||
@@ -6524,13 +6590,155 @@ class view_serializer
|
|||||||
return (n.flags & node_flags::storage) == 0 && is_ascii_without_delete(src + n.off, n.len);
|
return (n.flags & node_flags::storage) == 0 && is_ascii_without_delete(src + n.off, n.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// a string with ensure_ascii (dump_compact()), out of line
|
/*!
|
||||||
NLOHMANN_VIEW_NOINLINE void write_ascii_string(const node& n)
|
@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)
|
||||||
{
|
{
|
||||||
write_string(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_compact() that are not fixed-size moves (long
|
/// 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
|
/// 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
|
/// 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
|
NLOHMANN_VIEW_NOINLINE static void copy_long(char* to, const char* from, std::size_t n) noexcept
|
||||||
@@ -6547,7 +6755,7 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief the compact output (the default dump(), or with ensure_ascii)
|
@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
|
The same walk as dump(), with the write position in a local variable
|
||||||
(stores through char pointers would otherwise force a reload of the
|
(stores through char pointers would otherwise force a reload of the
|
||||||
@@ -6555,11 +6763,11 @@ class view_serializer
|
|||||||
the source copied by fixed-size moves of 32 bytes where the source has
|
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
|
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
|
keeps 64 bytes of slack for the overshoot. With ensure_ascii (Ascii), a
|
||||||
string of the source is copied only if it has no byte >= 0x7F (see
|
string of the source is copied only if it has nothing to escape (see
|
||||||
is_ascii_without_delete()); any other string is written by write_string().
|
copied()); any other string is written by write_ascii().
|
||||||
*/
|
*/
|
||||||
template<bool SourceNumbers, bool Ascii>
|
template<bool SourceNumbers, bool Pretty, bool Ascii>
|
||||||
void dump_compact(const node* root)
|
void dump_text(const node* root)
|
||||||
{
|
{
|
||||||
struct frame
|
struct frame
|
||||||
{
|
{
|
||||||
@@ -6574,6 +6782,16 @@ class view_serializer
|
|||||||
const char* const src_end = src + m_doc.size;
|
const char* const src_end = src + m_doc.size;
|
||||||
char* w = m_out.cursor();
|
char* w = m_out.cursor();
|
||||||
char* lim = m_out.limit();
|
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
|
// room for n bytes and the slack
|
||||||
const auto room = [&](std::size_t n)
|
const auto room = [&](std::size_t n)
|
||||||
{
|
{
|
||||||
@@ -6605,6 +6823,22 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
w += 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
|
// a string that is not a plain string of the source (out of line, so
|
||||||
// that the cursor stays in a register here)
|
// that the cursor stays in a register here)
|
||||||
const auto escaped = [&](const node & n)
|
const auto escaped = [&](const node & n)
|
||||||
@@ -6612,11 +6846,11 @@ class view_serializer
|
|||||||
m_out.set_cursor(w);
|
m_out.set_cursor(w);
|
||||||
if (enabled(Ascii))
|
if (enabled(Ascii))
|
||||||
{
|
{
|
||||||
write_ascii_string(n);
|
write_ascii(n);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
write_decoded(n);
|
write_decoded(n, Pretty);
|
||||||
}
|
}
|
||||||
w = m_out.cursor();
|
w = m_out.cursor();
|
||||||
lim = m_out.limit();
|
lim = m_out.limit();
|
||||||
@@ -6717,6 +6951,7 @@ class view_serializer
|
|||||||
{
|
{
|
||||||
*w++ = object ? '{' : '[';
|
*w++ = object ? '{' : '[';
|
||||||
stack.push_back(cur);
|
stack.push_back(cur);
|
||||||
|
level.enter();
|
||||||
if (Editable)
|
if (Editable)
|
||||||
{
|
{
|
||||||
cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object};
|
cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object};
|
||||||
@@ -6759,6 +6994,10 @@ class view_serializer
|
|||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (Pretty)
|
||||||
|
{
|
||||||
|
newline(level.leave());
|
||||||
|
}
|
||||||
room(1);
|
room(1);
|
||||||
*w++ = cur.object ? '}' : ']';
|
*w++ = cur.object ? '}' : ']';
|
||||||
cur = stack.back();
|
cur = stack.back();
|
||||||
@@ -6767,13 +7006,17 @@ class view_serializer
|
|||||||
room(1);
|
room(1);
|
||||||
*w++ = ',';
|
*w++ = ',';
|
||||||
}
|
}
|
||||||
|
if (Pretty)
|
||||||
|
{
|
||||||
|
newline(level.get());
|
||||||
|
}
|
||||||
const node* const at = Editable ? cur.pos : n;
|
const node* const at = Editable ? cur.pos : n;
|
||||||
if (cur.object)
|
if (cur.object)
|
||||||
{
|
{
|
||||||
const node& key = *at;
|
const node& key = *at;
|
||||||
if (copied(key, src, std::integral_constant<bool, Ascii>()))
|
if (copied(key, src, std::integral_constant<bool, Ascii>()))
|
||||||
{
|
{
|
||||||
room(key.len + 3);
|
room(key.len + (Pretty ? 4 : 3));
|
||||||
*w++ = '"';
|
*w++ = '"';
|
||||||
copy(src + key.off, key.len);
|
copy(src + key.off, key.len);
|
||||||
w[0] = '"';
|
w[0] = '"';
|
||||||
@@ -6783,9 +7026,13 @@ class view_serializer
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
escaped(key);
|
escaped(key);
|
||||||
room(1);
|
room(Pretty ? 2 : 1);
|
||||||
*w++ = ':';
|
*w++ = ':';
|
||||||
}
|
}
|
||||||
|
if (Pretty)
|
||||||
|
{
|
||||||
|
*w++ = ' ';
|
||||||
|
}
|
||||||
if (Editable)
|
if (Editable)
|
||||||
{
|
{
|
||||||
n = nav::value(at + 1);
|
n = nav::value(at + 1);
|
||||||
@@ -6804,69 +7051,6 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void newline(std::size_t level)
|
|
||||||
{
|
|
||||||
if (m_style.pretty)
|
|
||||||
{
|
|
||||||
m_out.put('\n');
|
|
||||||
m_out.put_repeated(m_style.indent_char, level * m_style.indent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void write_scalar(const node& n)
|
|
||||||
{
|
|
||||||
switch (static_cast<value_t>(n.kind))
|
|
||||||
{
|
|
||||||
case value_t::null:
|
|
||||||
m_out.put("null");
|
|
||||||
break;
|
|
||||||
case value_t::boolean:
|
|
||||||
if ((n.flags & node_flags::is_true) != 0)
|
|
||||||
{
|
|
||||||
m_out.put("true");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_out.put("false");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case value_t::string:
|
|
||||||
write_string(n);
|
|
||||||
break;
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
{
|
|
||||||
const char* const token = m_doc.str(n);
|
|
||||||
const std::uint32_t len = number_length(n);
|
|
||||||
if (!m_style.source_numbers && len == 2 && token[0] == '-' && token[1] == '0')
|
|
||||||
{
|
|
||||||
m_out.put('0'); // parse() reads -0 as the integer 0
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_out.put(token, len);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case value_t::number_float:
|
|
||||||
if (m_style.source_numbers && (n.flags & node_flags::storage) != node_flags::edited)
|
|
||||||
{
|
|
||||||
m_out.put(m_doc.str(n), n.len); // (a float set by an edit is written as with shortest)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_float_node(n);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case value_t::object: // LCOV_EXCL_LINE (containers are written by dump())
|
|
||||||
case value_t::array: // LCOV_EXCL_LINE
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// a float node as dump() writes it
|
/// a float node as dump() writes it
|
||||||
void write_float_node(const node& n)
|
void write_float_node(const node& n)
|
||||||
{
|
{
|
||||||
@@ -6977,72 +7161,6 @@ class view_serializer
|
|||||||
m_out.put(s.data(), s.size());
|
m_out.put(s.data(), s.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_string(const node& n)
|
|
||||||
{
|
|
||||||
const char* const s = m_doc.str(n);
|
|
||||||
m_out.put('"');
|
|
||||||
if ((n.flags & node_flags::storage) == 0 && (!m_style.ensure_ascii || is_ascii_without_delete(s, n.len)))
|
|
||||||
{
|
|
||||||
// a string of the source without escape sequences has nothing to
|
|
||||||
// escape (with ensure_ascii, if it is printable ASCII)
|
|
||||||
m_out.put(s, n.len);
|
|
||||||
}
|
|
||||||
else if (m_style.ensure_ascii)
|
|
||||||
{
|
|
||||||
write_escaped<true>(reinterpret_cast<const unsigned char*>(s), n.len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_escaped<false>(reinterpret_cast<const unsigned char*>(s), n.len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
}
|
|
||||||
m_out.put('"');
|
|
||||||
}
|
|
||||||
|
|
||||||
/// as serializer::dump_escaped(); 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
|
|
||||||
template<bool EnsureAscii>
|
|
||||||
void write_escaped(const unsigned char* s, std::size_t n)
|
|
||||||
{
|
|
||||||
std::size_t i = 0;
|
|
||||||
while (i < n)
|
|
||||||
{
|
|
||||||
std::size_t run = 0;
|
|
||||||
if (!EnsureAscii)
|
|
||||||
{
|
|
||||||
run = string_bulk_run(s + i, n - i);
|
|
||||||
}
|
|
||||||
else if (is_ascii_copyable(s[i]))
|
|
||||||
{
|
|
||||||
run = find_ascii_copyable_run(s + i, n - i);
|
|
||||||
}
|
|
||||||
if (run != 0)
|
|
||||||
{
|
|
||||||
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
i += run;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
std::uint32_t codepoint = s[i];
|
|
||||||
std::size_t len = 1;
|
|
||||||
if (codepoint >= 0x80)
|
|
||||||
{
|
|
||||||
len = validate_one_utf8(s + i, n - i);
|
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(len == 0))
|
|
||||||
{
|
|
||||||
invalid_utf8(s, n);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
codepoint &= 0xFFu >> (len + 1);
|
|
||||||
for (std::size_t k = 1; k < len; ++k)
|
|
||||||
{
|
|
||||||
codepoint = (codepoint << 6u) | (s[i + k] & 0x3Fu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
write_codepoint<EnsureAscii>(codepoint, s + i, len);
|
|
||||||
i += len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// throw what basic_json::dump() throws for a string that is not valid UTF-8
|
/// 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)
|
NLOHMANN_VIEW_NOINLINE static void invalid_utf8(const unsigned char* s, std::size_t n)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2952,81 +2952,3 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
TEST_CASE("accept() agrees with parse() on floats around the overflow limit")
|
|
||||||
{
|
|
||||||
// accept() does not convert a float that cannot overflow; the tokens
|
|
||||||
// around the limit (and beyond it) must still be rejected exactly as
|
|
||||||
// parse() rejects them
|
|
||||||
const char* const numbers[] =
|
|
||||||
{
|
|
||||||
"1e0", "123.456e-5", "1e-400", "-1e-400", "1e307", "1e308", "1e309", "-1e309",
|
|
||||||
"10e307", "100e307", "0.1e309", "0.1e310", "1E+308", "1E+309",
|
|
||||||
"1.7976931348623157e308", "1.7976931348623159e308", "1.8e308", "-1.7976931348623157e308",
|
|
||||||
"12345678901234567e291", "12345678901234567e292", "123456789012345678e290", "123456789012345678e291",
|
|
||||||
"1234567890123456789e289", "1234567890123456789e290", "9999999999999999999e289", "9999999999999999999e290",
|
|
||||||
"0.00000000000000000001e328", "0.00000000000000000001e329"
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const char* number : numbers)
|
|
||||||
{
|
|
||||||
for (const std::string& text :
|
|
||||||
{
|
|
||||||
std::string(number), "[" + std::string(number) + "]", "{\"a\":" + std::string(number) + "}"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
INFO(text);
|
|
||||||
const bool parses = !json::parse(text, nullptr, false).is_discarded();
|
|
||||||
CHECK(json::accept(text) == parses);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// a DOM parser that reports parse errors without throwing, as in the sax_no_exception example
|
|
||||||
template<typename BasicJsonType>
|
|
||||||
class quiet_dom_parser : public nlohmann::detail::json_sax_dom_parser<BasicJsonType, nlohmann::detail::string_input_adapter_type>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit quiet_dom_parser(BasicJsonType& j)
|
|
||||||
: nlohmann::detail::json_sax_dom_parser<BasicJsonType, nlohmann::detail::string_input_adapter_type>(j, false)
|
|
||||||
{}
|
|
||||||
|
|
||||||
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const nlohmann::detail::exception& /*unused*/)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename BasicJsonType>
|
|
||||||
std::string read_before_error(const std::string& text)
|
|
||||||
{
|
|
||||||
BasicJsonType j;
|
|
||||||
quiet_dom_parser<BasicJsonType> sax(j);
|
|
||||||
CHECK(!BasicJsonType::sax_parse(text, &sax));
|
|
||||||
return j.dump();
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("a json_sax_dom_parser keeps the values read before a parse error")
|
|
||||||
{
|
|
||||||
// a json_sax_dom_parser that a user constructs stores every value in place, so a
|
|
||||||
// handler that does not throw on a parse error sees what was read before it
|
|
||||||
const std::vector<std::pair<std::string, std::string>> cases =
|
|
||||||
{
|
|
||||||
{"[1,2,3,]", "[1,2,3]"},
|
|
||||||
{"[[1,[2,3],4],[5,", "[[1,[2,3],4],[5]]"},
|
|
||||||
{R"({"a":1,"b":[1,2,{"c":)", R"({"a":1,"b":[1,2,{"c":null}]})"},
|
|
||||||
{R"({"a":1,"a":)", R"({"a":1})"},
|
|
||||||
{"[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,]", "[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]"},
|
|
||||||
{R"({"k0":0,"k1":1,"k2":{"x":[true,)", R"({"k0":0,"k1":1,"k2":{"x":[true]}})"},
|
|
||||||
{R"([{},[],{"a":[]},x)", R"([{},[],{"a":[]}])"},
|
|
||||||
};
|
|
||||||
for (const auto& c : cases)
|
|
||||||
{
|
|
||||||
CAPTURE(c.first)
|
|
||||||
CHECK(read_before_error<json>(c.first) == c.second);
|
|
||||||
CHECK(read_before_error<nlohmann::ordered_json>(c.first) == c.second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -186,6 +186,133 @@ TEST_CASE("json_view dump")
|
|||||||
CHECK(deep_document.root().dump() == deep);
|
CHECK(deep_document.root().dump() == deep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("ensure_ascii: a character that needs an escape at each position of a string")
|
||||||
|
{
|
||||||
|
// string lengths around the width of the vector steps, in a string value, a key, and a value near the end of the text
|
||||||
|
for (const char* const special :
|
||||||
|
{
|
||||||
|
"\xC3\xA9", "\xE4\xB8\xAD", "\xF0\x9F\x98\x80", "\x7F", "\\n", "\\u0001", "\\\"", "\\\\"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
for (std::size_t length = 0; length <= 40; ++length)
|
||||||
|
{
|
||||||
|
for (std::size_t position = 0; position <= length; ++position)
|
||||||
|
{
|
||||||
|
std::string content(length, 'a');
|
||||||
|
content.insert(position, special);
|
||||||
|
const std::array<std::string, 3> texts =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"\"" + content + "\"",
|
||||||
|
"[\"" + content + "\"]",
|
||||||
|
"{\"" + content + "\":1}",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (const std::string& text : texts)
|
||||||
|
{
|
||||||
|
CAPTURE(text)
|
||||||
|
const ordered_json_document d = ordered_json_document::parse(text);
|
||||||
|
const ordered_json j = ordered_json::parse(text);
|
||||||
|
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
||||||
|
CHECK(d.root().dump(2, ' ', true) == j.dump(2, ' ', true));
|
||||||
|
CHECK(d.root().dump() == j.dump());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ensure_ascii: every character of the Basic Multilingual Plane and some beyond")
|
||||||
|
{
|
||||||
|
// (the two- and three-byte sequences, including the ranges that need a check: E0, ED, EF BF BF)
|
||||||
|
std::string text = "[";
|
||||||
|
std::size_t in_chunk = 0;
|
||||||
|
const auto append = [&](std::uint32_t cp)
|
||||||
|
{
|
||||||
|
if (cp < 0x80)
|
||||||
|
{
|
||||||
|
if (cp < 0x20 || cp == '"' || cp == '\\')
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
text += static_cast<char>(cp);
|
||||||
|
}
|
||||||
|
else if (cp < 0x800)
|
||||||
|
{
|
||||||
|
text += static_cast<char>(0xC0 | (cp >> 6));
|
||||||
|
text += static_cast<char>(0x80 | (cp & 0x3F));
|
||||||
|
}
|
||||||
|
else if (cp < 0x10000)
|
||||||
|
{
|
||||||
|
text += static_cast<char>(0xE0 | (cp >> 12));
|
||||||
|
text += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
|
||||||
|
text += static_cast<char>(0x80 | (cp & 0x3F));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
text += static_cast<char>(0xF0 | (cp >> 18));
|
||||||
|
text += static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
|
||||||
|
text += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
|
||||||
|
text += static_cast<char>(0x80 | (cp & 0x3F));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (std::uint32_t cp = 0x20; cp < 0x10400; ++cp)
|
||||||
|
{
|
||||||
|
if (cp >= 0xD800 && cp < 0xE000)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (in_chunk == 0)
|
||||||
|
{
|
||||||
|
text += text.size() > 1 ? ",\"" : "\"";
|
||||||
|
}
|
||||||
|
append(cp);
|
||||||
|
if (++in_chunk == 37)
|
||||||
|
{
|
||||||
|
text += "\"";
|
||||||
|
in_chunk = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text += in_chunk != 0 ? "\"]" : "]";
|
||||||
|
const ordered_json_document d = ordered_json_document::parse(text);
|
||||||
|
const ordered_json j = ordered_json::parse(text);
|
||||||
|
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
||||||
|
CHECK(d.root().dump(2, ' ', true) == j.dump(2, ' ', true));
|
||||||
|
CHECK(d.root().dump() == j.dump());
|
||||||
|
CHECK(d.root().dump(2) == j.dump(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("deep nesting, indented")
|
||||||
|
{
|
||||||
|
// indentations of up to and beyond the width of the writer's fixed-size copy
|
||||||
|
for (const int depth :
|
||||||
|
{
|
||||||
|
1, 5, 20, 70, 300
|
||||||
|
})
|
||||||
|
{
|
||||||
|
std::string text;
|
||||||
|
for (int i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
text += i % 2 == 0 ? "[1," : R"({"k":2,"\u00e9":)";
|
||||||
|
}
|
||||||
|
text += "[]";
|
||||||
|
for (int i = depth - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
text += i % 2 == 0 ? "]" : "}";
|
||||||
|
}
|
||||||
|
const ordered_json_document d = ordered_json_document::parse(text);
|
||||||
|
const ordered_json j = ordered_json::parse(text);
|
||||||
|
for (const int indent :
|
||||||
|
{
|
||||||
|
0, 1, 2, 3, 8
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CHECK(d.root().dump(indent) == j.dump(indent));
|
||||||
|
CHECK(d.root().dump(indent, '\t') == j.dump(indent, '\t'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("the output buffer of a small value is small")
|
SECTION("the output buffer of a small value is small")
|
||||||
{
|
{
|
||||||
// an escaped key after the value: its node lies in the arena, so the
|
// an escaped key after the value: its node lies in the arena, so the
|
||||||
|
|||||||
@@ -682,6 +682,26 @@ TEST_CASE("json_view images: ownership")
|
|||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
TEST_CASE("json_view images: errors")
|
TEST_CASE("json_view images: errors")
|
||||||
{
|
{
|
||||||
|
SECTION("invalid UTF-8 of a decoded string: the compact dump writes it, the others throw")
|
||||||
|
{
|
||||||
|
const json_document d = json_document::parse(R"({"k\n":"\u00e9x"})");
|
||||||
|
std::vector<std::uint8_t> image = d.save();
|
||||||
|
const std::size_t arena = text_at(image) + header_size_field(image, 16) + 1;
|
||||||
|
image[arena + 3] = 0x28; // the key is "k\n"; the value starts at 3: 0xC3 0x28 is invalid
|
||||||
|
const json_document loaded = json_document::load(image, image_check::bounds);
|
||||||
|
CHECK(loaded.root().dump().size() == 13);
|
||||||
|
for (const int indent :
|
||||||
|
{
|
||||||
|
0, 2
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CHECK_THROWS_AS(loaded.root().dump(indent), json::type_error);
|
||||||
|
CHECK_THROWS_AS(loaded.root().dump(indent, ' ', true), json::type_error);
|
||||||
|
}
|
||||||
|
CHECK_THROWS_AS(loaded.root().dump(-1, ' ', true), json::type_error);
|
||||||
|
CHECK_THROWS_WITH_AS(loaded.root().dump(2), "[json.exception.type_error.316] invalid UTF-8 byte at index 1: 0x28", json::type_error&);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("a literal as the root: dump() after loading")
|
SECTION("a literal as the root: dump() after loading")
|
||||||
{
|
{
|
||||||
for (const char* text :
|
for (const char* text :
|
||||||
|
|||||||
@@ -237,274 +237,3 @@ TEST_CASE("regression test for issue #5673 - ordered_json::emplace with a non-rv
|
|||||||
CHECK(oj.dump() == "{\"a\":1}");
|
CHECK(oj.dump() == "{\"a\":1}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// the sizes around which the DOM parser changes how it stores the elements of
|
|
||||||
// an array (16) and the members of an object in a vector (32, and 64 for the
|
|
||||||
// key index)
|
|
||||||
const std::size_t container_sizes[] = {0, 1, 2, 3, 15, 16, 17, 18, 31, 32, 33, 34, 63, 64, 65, 66, 200};
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
std::string array_text(const std::size_t n, const std::size_t depth)
|
|
||||||
{
|
|
||||||
std::string text = "[";
|
|
||||||
for (std::size_t i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
text += (i == 0 ? "" : ",");
|
|
||||||
// alternate scalars, empty containers and nested containers
|
|
||||||
switch (i % 4)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
text += std::to_string(i);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
text += "\"s" + std::to_string(i) + "\"";
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
text += depth == 0 ? "[]" : array_text<Json>(n / 2, depth - 1);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
text += depth == 0 ? "{}" : "{\"a\":" + array_text<Json>(n / 3, depth - 1) + ",\"b\":null}";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return text + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
std::string object_text(const std::size_t n, const std::size_t depth, const bool duplicates)
|
|
||||||
{
|
|
||||||
std::string text = "{";
|
|
||||||
for (std::size_t i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
text += (i == 0 ? "" : ",");
|
|
||||||
text += "\"key" + std::to_string(i) + "\":";
|
|
||||||
switch (i % 4)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
text += std::to_string(i);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
text += depth == 0 ? "[]" : array_text<Json>(n / 2, depth - 1);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
text += depth == 0 ? "{}" : object_text<Json>(n / 2, depth - 1, duplicates);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
text += "\"value\"";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (duplicates)
|
|
||||||
{
|
|
||||||
// keys that are there already, the first and the last one, with other values
|
|
||||||
text += n == 0 ? "" : ",";
|
|
||||||
text += "\"key0\":[1,2],\"key" + std::to_string(n - 1) + "\":{\"again\":true}";
|
|
||||||
}
|
|
||||||
return text + "}";
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
void check_same_dump(const std::string& text)
|
|
||||||
{
|
|
||||||
// the text is what dump() writes for the parsed value, and the value
|
|
||||||
// survives a second round trip: arrays keep their elements in order, an
|
|
||||||
// ordered_json keeps its members in order of appearance
|
|
||||||
const Json parsed = Json::parse(text);
|
|
||||||
const Json again = Json::parse(parsed.dump());
|
|
||||||
CHECK(parsed == again);
|
|
||||||
CHECK(parsed.dump() == again.dump());
|
|
||||||
CHECK(Json::accept(text));
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("parsing containers of sizes around the limits of the DOM parser")
|
|
||||||
{
|
|
||||||
SECTION("arrays")
|
|
||||||
{
|
|
||||||
for (const std::size_t n : container_sizes)
|
|
||||||
{
|
|
||||||
const std::string text = array_text<json>(n, 2);
|
|
||||||
INFO("size ", n);
|
|
||||||
|
|
||||||
const json parsed = json::parse(text);
|
|
||||||
CHECK(parsed.is_array());
|
|
||||||
CHECK(parsed.size() == n);
|
|
||||||
for (std::size_t i = 0; i < n; i += 4)
|
|
||||||
{
|
|
||||||
CHECK(parsed[i] == i);
|
|
||||||
}
|
|
||||||
check_same_dump<json>(text);
|
|
||||||
check_same_dump<ordered_json>(text);
|
|
||||||
CHECK(ordered_json::parse(text).dump() == parsed.dump());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("objects")
|
|
||||||
{
|
|
||||||
for (const bool duplicates :
|
|
||||||
{
|
|
||||||
false, true
|
|
||||||
})
|
|
||||||
{
|
|
||||||
for (const std::size_t n : container_sizes)
|
|
||||||
{
|
|
||||||
const std::string text = object_text<json>(n, 2, duplicates);
|
|
||||||
INFO("size ", n, " duplicates ", duplicates);
|
|
||||||
|
|
||||||
const json parsed = json::parse(text);
|
|
||||||
const ordered_json ordered = ordered_json::parse(text);
|
|
||||||
CHECK(parsed.is_object());
|
|
||||||
CHECK(ordered.is_object());
|
|
||||||
CHECK(parsed.size() == ordered.size());
|
|
||||||
// the same members, in the order of their first appearance
|
|
||||||
std::size_t i = 0;
|
|
||||||
for (auto it = ordered.begin(); it != ordered.end(); ++it, ++i)
|
|
||||||
{
|
|
||||||
if (i < n)
|
|
||||||
{
|
|
||||||
CHECK(it.key() == "key" + std::to_string(i));
|
|
||||||
}
|
|
||||||
CHECK(parsed.at(it.key()) == json(it.value()));
|
|
||||||
}
|
|
||||||
if (duplicates && n > 1)
|
|
||||||
{
|
|
||||||
// the later value replaced the earlier one in its place
|
|
||||||
CHECK(ordered.at("key0") == ordered_json::parse("[1,2]"));
|
|
||||||
CHECK(ordered.at("key" + std::to_string(n - 1)) == ordered_json::parse("{\"again\":true}"));
|
|
||||||
CHECK(ordered.begin().key() == "key0");
|
|
||||||
CHECK(ordered.size() == n);
|
|
||||||
}
|
|
||||||
check_same_dump<json>(text);
|
|
||||||
check_same_dump<ordered_json>(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("objects with many different and repeated keys")
|
|
||||||
{
|
|
||||||
// 300 members of which a third repeat an earlier key
|
|
||||||
std::string text = "{";
|
|
||||||
for (std::size_t i = 0; i < 300; ++i)
|
|
||||||
{
|
|
||||||
text += (i == 0 ? "" : ",");
|
|
||||||
text += "\"k" + std::to_string(i % 200) + "\":" + std::to_string(i);
|
|
||||||
}
|
|
||||||
text += "}";
|
|
||||||
|
|
||||||
const ordered_json ordered = ordered_json::parse(text);
|
|
||||||
const json parsed = json::parse(text);
|
|
||||||
CHECK(ordered.size() == 200);
|
|
||||||
CHECK(parsed.size() == 200);
|
|
||||||
std::size_t i = 0;
|
|
||||||
for (auto it = ordered.begin(); it != ordered.end(); ++it, ++i)
|
|
||||||
{
|
|
||||||
CHECK(it.key() == "k" + std::to_string(i));
|
|
||||||
// the last value written
|
|
||||||
CHECK(it.value() == (i < 100 ? i + 200 : i));
|
|
||||||
CHECK(parsed.at(it.key()) == json(it.value()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// a deterministic source of structure for parsing tests: builds a JSON text
|
|
||||||
// and, at the same time, the value it has to parse to
|
|
||||||
class tree_generator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit tree_generator(std::uint32_t seed) : state(seed) {}
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
Json make(std::string& text, const std::size_t depth)
|
|
||||||
{
|
|
||||||
switch (next(depth == 0 ? 5 : 9))
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
text += "null";
|
|
||||||
return Json(nullptr);
|
|
||||||
case 1:
|
|
||||||
text += "true";
|
|
||||||
return Json(true);
|
|
||||||
case 2:
|
|
||||||
{
|
|
||||||
const int n = static_cast<int>(next(2000)) - 1000;
|
|
||||||
text += std::to_string(n);
|
|
||||||
return Json(n);
|
|
||||||
}
|
|
||||||
case 3:
|
|
||||||
text += "\"text\"";
|
|
||||||
return Json("text");
|
|
||||||
case 4:
|
|
||||||
text += "2.5";
|
|
||||||
return Json(2.5);
|
|
||||||
case 5:
|
|
||||||
case 6:
|
|
||||||
case 7:
|
|
||||||
{
|
|
||||||
Json array = Json::array();
|
|
||||||
text += "[";
|
|
||||||
const std::size_t n = size();
|
|
||||||
for (std::size_t i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
text += (i == 0 ? "" : ",");
|
|
||||||
array.push_back(make<Json>(text, depth - 1));
|
|
||||||
}
|
|
||||||
text += "]";
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
Json object = Json::object();
|
|
||||||
text += "{";
|
|
||||||
const std::size_t n = size();
|
|
||||||
for (std::size_t i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
const std::string key = "k" + std::to_string(i);
|
|
||||||
text += (i == 0 ? "\"" : ",\"") + key + "\":";
|
|
||||||
object[key] = make<Json>(text, depth - 1);
|
|
||||||
}
|
|
||||||
text += "}";
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::uint32_t next(const std::uint32_t bound)
|
|
||||||
{
|
|
||||||
state = state * 1664525U + 1013904223U;
|
|
||||||
return (state >> 8) % bound;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t size()
|
|
||||||
{
|
|
||||||
return container_sizes[next(sizeof(container_sizes) / sizeof(*container_sizes))];
|
|
||||||
}
|
|
||||||
|
|
||||||
std::uint32_t state;
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("parsing random trees of arrays and objects")
|
|
||||||
{
|
|
||||||
for (std::uint32_t seed = 1; seed <= 150; ++seed)
|
|
||||||
{
|
|
||||||
INFO("seed ", seed);
|
|
||||||
|
|
||||||
std::string text;
|
|
||||||
std::string ordered_text;
|
|
||||||
const json expected = tree_generator(seed).make<json>(text, 3);
|
|
||||||
const ordered_json ordered_expected = tree_generator(seed).make<ordered_json>(ordered_text, 3);
|
|
||||||
CHECK(text == ordered_text);
|
|
||||||
|
|
||||||
CHECK(json::parse(text) == expected);
|
|
||||||
CHECK(json::accept(text));
|
|
||||||
const ordered_json ordered = ordered_json::parse(text);
|
|
||||||
CHECK(ordered == ordered_expected);
|
|
||||||
CHECK(ordered.dump() == ordered_expected.dump());
|
|
||||||
CHECK(json::parse(text).dump() == expected.dump());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user