Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89bd5d63e1 | ||
|
|
3fb7d9c710 | ||
|
|
fa8134ae85 |
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -255,6 +255,82 @@ struct number_token
|
||||
bool is_float; ///< a fraction or an exponent
|
||||
};
|
||||
|
||||
/// advance over ASCII digits, eight bytes at a time
|
||||
inline const unsigned char* digit_run(const unsigned char* p, const unsigned char* e) noexcept
|
||||
{
|
||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||
while (e - p >= 8)
|
||||
{
|
||||
// (the digits become 0..9; a byte above 9, or with bit 7 set, is no digit. A carry
|
||||
// out of a byte with bit 7 set only affects the bytes above, after the first one
|
||||
// that count_trailing_zeros finds.)
|
||||
const std::uint64_t x = read_eight_bytes(p) ^ (ones * 0x30u);
|
||||
const std::uint64_t not_digit = ((x + (ones * 0x76u)) | x) & (ones * 0x80u);
|
||||
if (not_digit != 0)
|
||||
{
|
||||
return p + (static_cast<std::size_t>(count_trailing_zeros(not_digit)) / 8);
|
||||
}
|
||||
p += 8;
|
||||
}
|
||||
while (p != e && is_digit(*p))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/// whether the n bytes at p are ASCII digits (n > 0)
|
||||
inline bool all_digits(const unsigned char* p, std::size_t n) noexcept
|
||||
{
|
||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||
const auto digits = [](const unsigned char* q)
|
||||
{
|
||||
const std::uint64_t x = read_eight_bytes(q) ^ (ones * 0x30u);
|
||||
return (((x + (ones * 0x76u)) | x) & (ones * 0x80u)) == 0;
|
||||
};
|
||||
if (n >= 8)
|
||||
{
|
||||
std::size_t i = 0;
|
||||
for (; i + 8 < n; i += 8)
|
||||
{
|
||||
if (!digits(p + i))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return digits(p + n - 8); // (overlapping the bytes before: they are digits)
|
||||
}
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
if (!is_digit(p[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Whether a float token has the shape that the parser records for most of
|
||||
/// them: [-]digits.digits, with the numbers of digits of the node's layout
|
||||
/// (extra). Then the token is a number of that layout, and the general scan
|
||||
/// that follows is not needed.
|
||||
inline bool plain_float_token(const unsigned char* s, std::size_t len, std::uint16_t extra) noexcept
|
||||
{
|
||||
const std::size_t int_digits = extra & 0xFFu;
|
||||
const std::size_t frac_digits = extra >> 8u;
|
||||
if (int_digits == 0 || int_digits >= 255 || frac_digits == 0 || frac_digits >= 255 || len == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const std::size_t negative = *s == '-' ? 1 : 0;
|
||||
if (len != negative + int_digits + 1 + frac_digits)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const unsigned char* const p = s + negative;
|
||||
return !(int_digits > 1 && *p == '0') && p[int_digits] == '.' && all_digits(p, int_digits) && all_digits(p + int_digits + 1, frac_digits);
|
||||
}
|
||||
|
||||
/// whether [s, s + len) is a JSON number (the grammar the parser accepts)
|
||||
inline bool scan_number_token(const unsigned char* s, std::size_t len, number_token& t)
|
||||
{
|
||||
@@ -273,11 +349,7 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
|
||||
}
|
||||
else if (*p >= '1' && *p <= '9')
|
||||
{
|
||||
do
|
||||
{
|
||||
++p;
|
||||
}
|
||||
while (p != e && is_digit(*p));
|
||||
p = digit_run(p + 1, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -289,10 +361,7 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
|
||||
if (p != e && *p == '.')
|
||||
{
|
||||
const unsigned char* const f0 = ++p;
|
||||
while (p != e && is_digit(*p))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
p = digit_run(p, e);
|
||||
if (p == f0)
|
||||
{
|
||||
return false;
|
||||
@@ -403,10 +472,15 @@ struct float_range
|
||||
template<typename Check>
|
||||
bool check_distinct_ranges(std::vector<byte_range>& ranges, Check check)
|
||||
{
|
||||
std::sort(ranges.begin(), ranges.end(), [](const byte_range & a, const byte_range & b)
|
||||
// (save() writes the ranges in the order of the nodes, which is sorted already)
|
||||
const auto before = [](const byte_range & a, const byte_range & b)
|
||||
{
|
||||
return a.off != b.off ? a.off < b.off : a.len < b.len;
|
||||
});
|
||||
};
|
||||
if (!std::is_sorted(ranges.begin(), ranges.end(), before))
|
||||
{
|
||||
std::sort(ranges.begin(), ranges.end(), before);
|
||||
}
|
||||
std::size_t end = 0;
|
||||
for (std::size_t i = 0; i < ranges.size(); ++i)
|
||||
{
|
||||
@@ -428,7 +502,8 @@ bool check_distinct_ranges(std::vector<byte_range>& ranges, Check check)
|
||||
/// record layouts that match it), tokens must not overlap.
|
||||
inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned char* text)
|
||||
{
|
||||
std::sort(ranges.begin(), ranges.end(), [](const float_range & a, const float_range & b)
|
||||
// (save() writes the tokens in the order of the nodes, which is sorted already)
|
||||
const auto before = [](const float_range & a, const float_range & b)
|
||||
{
|
||||
if (a.off != b.off)
|
||||
{
|
||||
@@ -439,7 +514,11 @@ inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned
|
||||
return a.len < b.len;
|
||||
}
|
||||
return a.extra < b.extra;
|
||||
});
|
||||
};
|
||||
if (!std::is_sorted(ranges.begin(), ranges.end(), before))
|
||||
{
|
||||
std::sort(ranges.begin(), ranges.end(), before);
|
||||
}
|
||||
std::size_t end = 0;
|
||||
std::size_t i = 0;
|
||||
while (i < ranges.size())
|
||||
@@ -449,14 +528,18 @@ inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned
|
||||
{
|
||||
return false;
|
||||
}
|
||||
number_token t{};
|
||||
const unsigned char* const s = text + r.off;
|
||||
if (!scan_number_token(s, r.len, t) || !float_token_finite(s, r.len, t))
|
||||
std::uint16_t layout = r.extra;
|
||||
if (!plain_float_token(s, r.len, r.extra))
|
||||
{
|
||||
return false;
|
||||
number_token t{};
|
||||
if (!scan_number_token(s, r.len, t) || !float_token_finite(s, r.len, t))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// the digit layout the parser records (or "many", as compaction writes it)
|
||||
layout = float_layout(t);
|
||||
}
|
||||
// the digit layout the parser records (or "many", as compaction writes it)
|
||||
const std::uint16_t layout = float_layout(t);
|
||||
for (; i < ranges.size() && ranges[i].off == r.off && ranges[i].len == r.len; ++i)
|
||||
{
|
||||
if (ranges[i].extra != layout && ranges[i].extra != 0xFFFFu)
|
||||
|
||||
@@ -120,6 +120,74 @@ NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t number_length(const node& n) noexcept
|
||||
: (n.extra & 0xFFu) + (n.kind == static_cast<std::uint8_t>(value_t::number_integer) ? 1u : 0u);
|
||||
}
|
||||
|
||||
/// whether n bytes are equal; the short texts of JSON (keys, numbers, most
|
||||
/// strings) are compared by two overlapping loads instead of a library call
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE bool bytes_equal(const char* x, const char* y, std::size_t n) noexcept
|
||||
{
|
||||
const auto load8 = [](const char* p)
|
||||
{
|
||||
std::uint64_t v = 0;
|
||||
std::memcpy(&v, p, sizeof(v));
|
||||
return v;
|
||||
};
|
||||
const auto load4 = [](const char* p)
|
||||
{
|
||||
std::uint32_t v = 0;
|
||||
std::memcpy(&v, p, sizeof(v));
|
||||
return v;
|
||||
};
|
||||
if (n >= 8)
|
||||
{
|
||||
std::size_t i = 0;
|
||||
for (; i + 8 < n; i += 8)
|
||||
{
|
||||
if (load8(x + i) != load8(y + i))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return load8(x + n - 8) == load8(y + n - 8);
|
||||
}
|
||||
if (n >= 4)
|
||||
{
|
||||
return load4(x) == load4(y) && load4(x + n - 4) == load4(y + n - 4);
|
||||
}
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
if (x[i] != y[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// whether the n1 bytes at x are before the n2 bytes at y (memcmp() order, then the shorter first);
|
||||
/// the first difference is found eight bytes at a time
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE bool bytes_less(const char* x, std::size_t n1, const char* y, std::size_t n2) noexcept
|
||||
{
|
||||
const std::size_t n = n1 < n2 ? n1 : n2;
|
||||
std::size_t i = 0;
|
||||
for (; i + 8 <= n; i += 8)
|
||||
{
|
||||
const std::uint64_t u = read_eight_bytes(x + i);
|
||||
const std::uint64_t v = read_eight_bytes(y + i);
|
||||
if (u != v)
|
||||
{
|
||||
const auto shift = static_cast<unsigned>(count_trailing_zeros(u ^ v)) & ~7u; // (the first byte that differs)
|
||||
return ((u >> shift) & 0xFFu) < ((v >> shift) & 0xFFu);
|
||||
}
|
||||
}
|
||||
for (; i < n; ++i)
|
||||
{
|
||||
if (x[i] != y[i])
|
||||
{
|
||||
return static_cast<unsigned char>(x[i]) < static_cast<unsigned char>(y[i]);
|
||||
}
|
||||
}
|
||||
return n1 < n2;
|
||||
}
|
||||
|
||||
/// estimated number of nodes for an input of `size` bytes (one node per ~12
|
||||
/// bytes covers typical documents without regrowth)
|
||||
inline std::size_t estimate_nodes(std::size_t size) noexcept
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
#include <nlohmann/detail/view/number.hpp>
|
||||
#include <nlohmann/detail/view/simd.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -81,6 +80,13 @@ class output_buffer
|
||||
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
|
||||
/// that keeps the position in a local variable (set_cursor() hands it back)
|
||||
char* cursor() const noexcept
|
||||
@@ -137,25 +143,9 @@ class output_buffer
|
||||
/// image_check::bounds can have others, which are then written unchanged).
|
||||
inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexcept
|
||||
{
|
||||
std::size_t i = 0;
|
||||
#if NLOHMANN_VIEW_SSE2
|
||||
// (16 bytes per step; with NEON, the mask of a step costs more than it saves on the
|
||||
// short runs that are typical here)
|
||||
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));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||
std::size_t i = 0;
|
||||
for (; i + 8 <= n; i += 8)
|
||||
{
|
||||
const std::uint64_t v = read_eight_bytes(s + i);
|
||||
@@ -206,70 +196,6 @@ inline bool is_ascii_without_delete(const char* s, std::size_t n) noexcept
|
||||
return true;
|
||||
}
|
||||
|
||||
#if NLOHMANN_VIEW_VECTOR
|
||||
/// the bytes of the 16 at p that ensure_ascii cannot copy (a quote, a
|
||||
/// backslash, a control character, DEL, or not ASCII; a signed compare with
|
||||
/// 0x20 finds control characters and non-ASCII bytes at once), one bit per
|
||||
/// byte (x86) or one nibble per byte (NEON)
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t ascii_stop_mask(const unsigned char* p) noexcept
|
||||
{
|
||||
#if NLOHMANN_VIEW_NEON
|
||||
const uint8x16_t in = vld1q_u8(p);
|
||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
||||
vorrq_u8(vceqq_u8(in, vdupq_n_u8(0x7F)), vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20))));
|
||||
return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
||||
#else
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||
_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F)), _mm_cmplt_epi8(in, _mm_set1_epi8(0x20))));
|
||||
return static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
||||
#endif
|
||||
}
|
||||
|
||||
/// the width in bits of a byte in the mask of ascii_stop_mask()
|
||||
constexpr unsigned ascii_stop_bits() noexcept
|
||||
{
|
||||
return NLOHMANN_VIEW_NEON ? 4u : 1u;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// As find_ascii_copyable_run(), 16 bytes per step. end is the end of the
|
||||
/// buffer that holds the n bytes at p (a short run is read as 16 bytes if
|
||||
/// they are in the buffer).
|
||||
inline std::size_t ascii_copyable_run(const unsigned char* p, std::size_t n, const unsigned char* end) noexcept
|
||||
{
|
||||
#if NLOHMANN_VIEW_VECTOR
|
||||
if (n >= 16)
|
||||
{
|
||||
std::size_t i = 0;
|
||||
for (; i + 16 <= n; i += 16)
|
||||
{
|
||||
const std::uint64_t mask = ascii_stop_mask(p + i);
|
||||
if (mask != 0)
|
||||
{
|
||||
return i + (static_cast<std::size_t>(count_trailing_zeros(mask)) / ascii_stop_bits());
|
||||
}
|
||||
}
|
||||
if (i == n)
|
||||
{
|
||||
return n;
|
||||
}
|
||||
// the last 16 bytes: those before i are known to be copyable
|
||||
const std::uint64_t mask = ascii_stop_mask(p + n - 16);
|
||||
return mask != 0 ? n - 16 + (static_cast<std::size_t>(count_trailing_zeros(mask)) / ascii_stop_bits()) : n;
|
||||
}
|
||||
if (end - p >= 16)
|
||||
{
|
||||
// (the bytes after the run are masked out)
|
||||
const std::uint64_t mask = ascii_stop_mask(p) & ((std::uint64_t{1} << (n * ascii_stop_bits())) - 1u);
|
||||
return mask != 0 ? static_cast<std::size_t>(count_trailing_zeros(mask)) / ascii_stop_bits() : n;
|
||||
}
|
||||
#else
|
||||
static_cast<void>(end);
|
||||
#endif
|
||||
return find_ascii_copyable_run(p, n);
|
||||
}
|
||||
|
||||
/// A stack that starts in a buffer of the caller (a local array) and moves to
|
||||
/// the heap (a vector of the caller) only when that is full, so that dumps of
|
||||
/// shallow documents need no allocation. The top is a pointer, as in
|
||||
@@ -349,39 +275,6 @@ NLOHMANN_VIEW_ALWAYS_INLINE char* put_literal(char* w, const char (&s)[N]) noexc
|
||||
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
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
std::size_t get() const noexcept
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
/// how the view's dump() writes a value
|
||||
struct dump_style
|
||||
{
|
||||
@@ -418,95 +311,139 @@ class view_serializer
|
||||
|
||||
void dump(const node* root)
|
||||
{
|
||||
if (m_style.ensure_ascii)
|
||||
if (!m_style.pretty)
|
||||
{
|
||||
dump_ascii(root);
|
||||
if (m_style.ensure_ascii)
|
||||
{
|
||||
dump_compact_ascii(root);
|
||||
}
|
||||
else if (m_style.source_numbers)
|
||||
{
|
||||
dump_compact<true, false>(root);
|
||||
}
|
||||
else
|
||||
{
|
||||
dump_compact<false, false>(root);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (m_style.pretty)
|
||||
struct frame
|
||||
{
|
||||
dump_pretty(root);
|
||||
}
|
||||
else if (m_style.source_numbers)
|
||||
const node* pos; ///< next element, or key of the next member
|
||||
const node* end;
|
||||
bool object;
|
||||
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 (;;)
|
||||
{
|
||||
dump_text<true, false, false>(root);
|
||||
}
|
||||
else
|
||||
{
|
||||
dump_text<false, false, false>(root);
|
||||
// write the value at n
|
||||
if (is_container(*n))
|
||||
{
|
||||
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
||||
if (n->len == 0)
|
||||
{
|
||||
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:
|
||||
/// a string that is not a plain string of the source (decoded, or written
|
||||
/// by an edit), without ensure_ascii: runs without characters to escape
|
||||
/// are copied. If validate is set, the string must be valid UTF-8: the
|
||||
/// runs are found by the lexer's scan, which validates the sequences, and
|
||||
/// invalid UTF-8 throws what basic_json::dump() throws. (The compact output
|
||||
/// writes the bytes of a damaged image unchanged, and scans without
|
||||
/// validating them.)
|
||||
NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n, bool validate)
|
||||
/// are copied
|
||||
NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n)
|
||||
{
|
||||
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
m_out.put('"');
|
||||
for (std::size_t i = 0; i < n.len;)
|
||||
{
|
||||
const std::size_t run = validate ? string_bulk_run(s + i, n.len - i) : plain_output_run(s + i, n.len - i);
|
||||
const std::size_t run = plain_output_run(s + i, n.len - i);
|
||||
if (run != 0)
|
||||
{
|
||||
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
i += run;
|
||||
continue;
|
||||
}
|
||||
if (validate && s[i] >= 0x80)
|
||||
{
|
||||
invalid_utf8(s, n.len); // (the scan stopped at ill-formed UTF-8)
|
||||
}
|
||||
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
|
||||
++i;
|
||||
}
|
||||
m_out.put('"');
|
||||
}
|
||||
|
||||
/// dump_text() with indentation, out of line: the compact walk is faster
|
||||
/// when it is not merged with the indented ones into dump()
|
||||
NLOHMANN_VIEW_NOINLINE void dump_pretty(const node* root)
|
||||
/// dump_compact() 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_compact_ascii(const node* root)
|
||||
{
|
||||
if (m_style.source_numbers)
|
||||
{
|
||||
dump_text<true, true, false>(root);
|
||||
dump_compact<true, true>(root);
|
||||
}
|
||||
else
|
||||
{
|
||||
dump_text<false, true, false>(root);
|
||||
dump_compact<false, true>(root);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// whether dump_compact() copies a string or key from the source: one of
|
||||
/// the source, and with ensure_ascii one without bytes >= 0x7F (an overload
|
||||
/// each, so that the walk without ensure_ascii is compiled exactly as
|
||||
/// before: with GCC, a condition that only folds away later changed its
|
||||
@@ -521,154 +458,13 @@ class view_serializer
|
||||
return (n.flags & node_flags::storage) == 0 && is_ascii_without_delete(src + n.off, n.len);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief a string with ensure_ascii: the characters that are not printable ASCII as \\uXXXX
|
||||
(a character beyond U+FFFF as a surrogate pair); strings of a document
|
||||
are valid UTF-8, except in a damaged image loaded with image_check::bounds,
|
||||
for which this throws what basic_json::dump() throws for the string
|
||||
*/
|
||||
NLOHMANN_VIEW_NOINLINE void write_ascii(const node& n)
|
||||
/// a string with ensure_ascii (dump_compact()), out of line
|
||||
NLOHMANN_VIEW_NOINLINE void write_ascii_string(const node& n)
|
||||
{
|
||||
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const std::size_t size = n.len;
|
||||
char* w = m_out.cursor();
|
||||
char* lim = m_out.limit();
|
||||
// room for k bytes
|
||||
const auto room = [&](std::size_t k)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(lim - w) < 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);
|
||||
write_string(n);
|
||||
}
|
||||
|
||||
/// the copies of dump_text() that are not fixed-size moves (long
|
||||
/// the copies of dump_compact() that are not fixed-size moves (long
|
||||
/// strings, or near the end of the source); out of line, so that the
|
||||
/// compiler does not merge the fixed-size moves into this call
|
||||
NLOHMANN_VIEW_NOINLINE static void copy_long(char* to, const char* from, std::size_t n) noexcept
|
||||
@@ -685,7 +481,7 @@ class view_serializer
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the output, compact (the default dump()) or indented, with or without ensure_ascii
|
||||
@brief the compact output (the default dump(), or with ensure_ascii)
|
||||
|
||||
The same walk as dump(), with the write position in a local variable
|
||||
(stores through char pointers would otherwise force a reload of the
|
||||
@@ -693,11 +489,11 @@ class view_serializer
|
||||
the source copied by fixed-size moves of 32 bytes where the source has
|
||||
that many bytes left, instead of a library call per token. The buffer
|
||||
keeps 64 bytes of slack for the overshoot. With ensure_ascii (Ascii), a
|
||||
string of the source is copied only if it has nothing to escape (see
|
||||
copied()); any other string is written by write_ascii().
|
||||
string of the source is copied only if it has no byte >= 0x7F (see
|
||||
is_ascii_without_delete()); any other string is written by write_string().
|
||||
*/
|
||||
template<bool SourceNumbers, bool Pretty, bool Ascii>
|
||||
void dump_text(const node* root)
|
||||
template<bool SourceNumbers, bool Ascii>
|
||||
void dump_compact(const node* root)
|
||||
{
|
||||
struct frame
|
||||
{
|
||||
@@ -712,16 +508,6 @@ class view_serializer
|
||||
const char* const src_end = src + m_doc.size;
|
||||
char* w = m_out.cursor();
|
||||
char* lim = m_out.limit();
|
||||
// (indented output) a line break and 63 indentation characters, from
|
||||
// which the line break of a level of up to 63 characters is a copy
|
||||
std::array<char, 64> ruler; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled below if used
|
||||
if (Pretty)
|
||||
{
|
||||
ruler.fill(m_style.indent_char);
|
||||
ruler[0] = '\n';
|
||||
}
|
||||
const std::size_t indent = m_style.indent;
|
||||
depth_counter<Pretty> level; // depth of the innermost open container (indented output)
|
||||
// room for n bytes and the slack
|
||||
const auto room = [&](std::size_t n)
|
||||
{
|
||||
@@ -753,22 +539,6 @@ class view_serializer
|
||||
}
|
||||
w += n;
|
||||
};
|
||||
// (indented output) the line break and the indentation of a level
|
||||
const auto newline = [&](std::size_t depth)
|
||||
{
|
||||
const std::size_t count = 1 + (depth * indent);
|
||||
room(count);
|
||||
if (count <= ruler.size())
|
||||
{
|
||||
std::memcpy(w, ruler.data(), ruler.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
*w = '\n';
|
||||
std::memset(w + 1, m_style.indent_char, count - 1);
|
||||
}
|
||||
w += count;
|
||||
};
|
||||
// a string that is not a plain string of the source (out of line, so
|
||||
// that the cursor stays in a register here)
|
||||
const auto escaped = [&](const node & n)
|
||||
@@ -776,11 +546,11 @@ class view_serializer
|
||||
m_out.set_cursor(w);
|
||||
if (enabled(Ascii))
|
||||
{
|
||||
write_ascii(n);
|
||||
write_ascii_string(n);
|
||||
}
|
||||
else
|
||||
{
|
||||
write_decoded(n, Pretty);
|
||||
write_decoded(n);
|
||||
}
|
||||
w = m_out.cursor();
|
||||
lim = m_out.limit();
|
||||
@@ -881,7 +651,6 @@ class view_serializer
|
||||
{
|
||||
*w++ = object ? '{' : '[';
|
||||
stack.push_back(cur);
|
||||
level.enter();
|
||||
if (Editable)
|
||||
{
|
||||
cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object};
|
||||
@@ -924,10 +693,6 @@ class view_serializer
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (Pretty)
|
||||
{
|
||||
newline(level.leave());
|
||||
}
|
||||
room(1);
|
||||
*w++ = cur.object ? '}' : ']';
|
||||
cur = stack.back();
|
||||
@@ -936,17 +701,13 @@ class view_serializer
|
||||
room(1);
|
||||
*w++ = ',';
|
||||
}
|
||||
if (Pretty)
|
||||
{
|
||||
newline(level.get());
|
||||
}
|
||||
const node* const at = Editable ? cur.pos : n;
|
||||
if (cur.object)
|
||||
{
|
||||
const node& key = *at;
|
||||
if (copied(key, src, std::integral_constant<bool, Ascii>()))
|
||||
{
|
||||
room(key.len + (Pretty ? 4 : 3));
|
||||
room(key.len + 3);
|
||||
*w++ = '"';
|
||||
copy(src + key.off, key.len);
|
||||
w[0] = '"';
|
||||
@@ -956,13 +717,9 @@ class view_serializer
|
||||
else
|
||||
{
|
||||
escaped(key);
|
||||
room(Pretty ? 2 : 1);
|
||||
room(1);
|
||||
*w++ = ':';
|
||||
}
|
||||
if (Pretty)
|
||||
{
|
||||
*w++ = ' ';
|
||||
}
|
||||
if (Editable)
|
||||
{
|
||||
n = nav::value(at + 1);
|
||||
@@ -981,6 +738,69 @@ 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
|
||||
void write_float_node(const node& n)
|
||||
{
|
||||
@@ -1091,6 +911,72 @@ class view_serializer
|
||||
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
|
||||
NLOHMANN_VIEW_NOINLINE static void invalid_utf8(const unsigned char* s, std::size_t n)
|
||||
{
|
||||
|
||||
@@ -723,8 +723,12 @@ class basic_json_view
|
||||
template<typename, bool> friend class basic_json_document;
|
||||
template<typename, bool> friend class basic_json_view;
|
||||
template<typename, typename> friend class detail::view::editor;
|
||||
template<typename, typename> friend class detail::view::view_side;
|
||||
friend iterator;
|
||||
|
||||
/// whether the view is one of an editable document
|
||||
static constexpr bool is_editable = Editable;
|
||||
|
||||
basic_json_view(const document_data* d, const node* n) noexcept
|
||||
: m_doc(d), m_node(n)
|
||||
{}
|
||||
|
||||
Generated
+1570
-800
File diff suppressed because it is too large.
Load diff
+150
-125
@@ -186,131 +186,6 @@ TEST_CASE("json_view dump")
|
||||
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::string 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," : "{\"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")
|
||||
{
|
||||
// an escaped key after the value: its node lies in the arena, so the
|
||||
@@ -470,6 +345,33 @@ TEST_CASE("json_view comparison")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("objects whose first keys differ or repeat")
|
||||
{
|
||||
const char* const texts[] =
|
||||
{
|
||||
R"({"a":1,"b":2})", R"({"a":1,"c":2})", R"({"b":1,"a":2})", R"({"a":1,"a":2,"b":3})", R"({"a":2,"b":3})",
|
||||
R"({"a":1,"b":1,"a":2})", R"({"a":2,"b":1})", R"({"a":1,"a":2})", R"({"b":1})", R"({"a":1,"b":2,"c":3,"d":4,"e":5})",
|
||||
R"({"a":1,"b":2,"c":3,"d":4,"f":5})", R"({"a":1,"b":2,"c":3,"a":4,"e":5})", R"({"a":4,"b":2,"c":3,"e":5})",
|
||||
R"({"\u0061":1,"b":2})", R"({})", R"([{"a":1,"b":2}])", R"([{"a":1,"c":2}])"
|
||||
};
|
||||
for (const char* const x : texts)
|
||||
{
|
||||
for (const char* const y : texts)
|
||||
{
|
||||
CAPTURE(x)
|
||||
CAPTURE(y)
|
||||
const ordered_json_document dx = ordered_json_document::parse(x);
|
||||
const ordered_json_document dy = ordered_json_document::parse(y);
|
||||
const ordered_json_editable_document ex = ordered_json_editable_document::parse(x);
|
||||
const ordered_json jx = ordered_json::parse(x);
|
||||
const ordered_json jy = ordered_json::parse(y);
|
||||
CHECK((dx.root() == dy.root()) == (jx == jy));
|
||||
CHECK((dx.root() == jy) == (jx == jy));
|
||||
CHECK((ex.root() == dy.root()) == (jx == jy));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("numbers, duplicate keys, member order")
|
||||
{
|
||||
const auto same = [](const char* x, const char* y)
|
||||
@@ -510,6 +412,129 @@ TEST_CASE("json_view comparison")
|
||||
CHECK(!(null_document.root() == discarded));
|
||||
}
|
||||
|
||||
SECTION("values that are the same text, or the same value written differently, at any depth")
|
||||
{
|
||||
// nested arrays and objects around depths where the walk over equal text gives up
|
||||
for (const int depth :
|
||||
{
|
||||
1, 5, 47, 48, 49, 50, 60, 300
|
||||
})
|
||||
{
|
||||
CAPTURE(depth)
|
||||
std::string head;
|
||||
std::string tail;
|
||||
for (int i = 0; i < depth; ++i)
|
||||
{
|
||||
head += i % 2 == 0 ? R"({"k":[1,"s",{"x":true},)" : R"([null,2.5,{"y":)";
|
||||
tail = (i % 2 == 0 ? "]}" : "}]") + tail;
|
||||
}
|
||||
// the same text, one number changed, and one number written differently
|
||||
const std::string text = head + "7" + tail;
|
||||
const std::string changed = head + "8" + tail;
|
||||
const std::string spelled = head + "7.0" + tail;
|
||||
const json_document a = json_document::parse(text);
|
||||
const json_document b = json_document::parse(text);
|
||||
const json_document c = json_document::parse(changed);
|
||||
const json_document d = json_document::parse(spelled);
|
||||
CHECK(a.root() == b.root());
|
||||
CHECK(a.root() != c.root());
|
||||
CHECK(c.root() != a.root());
|
||||
CHECK(a.root() == d.root());
|
||||
CHECK(d.root() == a.root());
|
||||
CHECK(a.root() == json::parse(text));
|
||||
CHECK(a.root() != json::parse(changed));
|
||||
CHECK(json::parse(spelled) == a.root());
|
||||
const nlohmann::json_editable_document e = nlohmann::json_editable_document::parse(text);
|
||||
CHECK(a.root() == e.root());
|
||||
CHECK(e.root() == a.root());
|
||||
CHECK(c.root() != e.root());
|
||||
CHECK(e.root() == e.root());
|
||||
const ordered_json_document oa = ordered_json_document::parse(text);
|
||||
const ordered_json_document oc = ordered_json_document::parse(changed);
|
||||
CHECK(oa.root() != oc.root());
|
||||
CHECK(oa.root() == ordered_json::parse(text));
|
||||
CHECK(oc.root() != ordered_json::parse(text));
|
||||
}
|
||||
|
||||
// not the same text, but the same value
|
||||
const auto same = [](const char* x, const char* y)
|
||||
{
|
||||
const json_document dx = json_document::parse(x);
|
||||
const json_document dy = json_document::parse(y);
|
||||
const bool equal = dx.root() == dy.root();
|
||||
CHECK(equal == (json::parse(x) == json::parse(y)));
|
||||
CHECK(equal == (dx.root() == json::parse(y)));
|
||||
return equal;
|
||||
};
|
||||
CHECK(same(R"({"a":1,"a":2})", R"({"a":3,"a":2})"));
|
||||
CHECK(same(R"({"a":1,"b":[1,{"c":2,"c":3}]})", R"({"b":[1,{"c":9,"c":3}],"a":1})"));
|
||||
CHECK(!same(R"({"a":1,"a":2})", R"({"a":2,"a":3})"));
|
||||
CHECK(same("[1.0,-0,0,1e2]", "[1.00,0,-0,100.0]"));
|
||||
CHECK(same(R"(["\u00e9","\n"])", "[\"\xc3\xa9\",\"\\n\"]"));
|
||||
CHECK(!same(R"({"a":[1,2,{"b":null}]})", R"({"a":[1,2,{"b":false}]})"));
|
||||
CHECK(!same(R"({"a":[1,2,{"b":null}]})", R"({"a":[1,2,{"c":null}]})"));
|
||||
CHECK(!same(R"({"a":[1,2,{"b":null}]})", R"({"a":[1,2,{"b":null,"c":1}]})"));
|
||||
|
||||
// edited documents: moved sequences, links, and values of new nodes
|
||||
nlohmann::json_editable_document edited = nlohmann::json_editable_document::parse(R"({"a":[1,2,3],"b":{"c":"d"},"e":0.5})");
|
||||
const json_document plain = json_document::parse(R"({"a":[1,2,3,4],"b":{"c":"x","y":null},"e":0.5,"z":[]})");
|
||||
edited.push_back(edited.root()["a"], 4);
|
||||
edited.set(edited.root()["b"], "c", "x");
|
||||
edited.set(edited.root()["b"], "y", nullptr);
|
||||
edited.set(edited.root(), "z", json::array());
|
||||
CHECK(edited.root() == plain.root());
|
||||
CHECK(plain.root() == edited.root());
|
||||
edited.erase(edited.root()["a"], 0);
|
||||
CHECK(edited.root() != plain.root());
|
||||
CHECK(edited.root()["a"] == json::parse("[2,3,4]"));
|
||||
const json_document tail = json_document::parse("[2,3,4]");
|
||||
CHECK(edited.root()["a"] == tail.root());
|
||||
}
|
||||
|
||||
SECTION("large documents, with a difference at any place")
|
||||
{
|
||||
const auto make = [](std::size_t count, std::size_t changed, bool swap_members)
|
||||
{
|
||||
std::string text = R"({"b":1,"list":[)";
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
text += (i != 0 ? "," : "");
|
||||
const std::string id = std::to_string(i == changed ? i + 1 : i);
|
||||
text += swap_members && i % 3 == 0 ? R"({"v":[1.5,"s",{"k":)" + id + R"(}],"id":)" + id + "}" : R"({"id":)" + id + R"(,"v":[1.5,"s",{"k":)" + id + "}]}";
|
||||
}
|
||||
return text + R"(],"a":{"y":2,"x":3}})";
|
||||
};
|
||||
const std::size_t count = 3000;
|
||||
for (const std::size_t changed :
|
||||
{
|
||||
count, std::size_t{0}, std::size_t{1}, count / 2, count - 1
|
||||
})
|
||||
{
|
||||
for (const bool swap :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
CAPTURE(changed)
|
||||
CAPTURE(swap)
|
||||
const std::string text_a = make(count, count, false);
|
||||
const std::string text_b = make(count, changed, swap);
|
||||
const json_document a = json_document::parse(text_a);
|
||||
const json_document b = json_document::parse(text_b);
|
||||
const bool equal = changed == count;
|
||||
CHECK((a.root() == b.root()) == equal);
|
||||
CHECK((b.root() == a.root()) == equal);
|
||||
CHECK((a.root() == json::parse(text_b)) == equal);
|
||||
CHECK((json::parse(text_b) == a.root()) == equal);
|
||||
CHECK((a.root()["list"] == b.root()["list"]) == equal);
|
||||
const ordered_json_document oa = ordered_json_document::parse(text_a);
|
||||
const ordered_json_document ob = ordered_json_document::parse(text_b);
|
||||
CHECK((oa.root() == ob.root()) == (equal && !swap));
|
||||
CHECK((oa.root() == ordered_json::parse(text_b)) == (equal && !swap));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("deep nesting")
|
||||
{
|
||||
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
||||
|
||||
@@ -682,25 +682,65 @@ TEST_CASE("json_view images: ownership")
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
TEST_CASE("json_view images: errors")
|
||||
{
|
||||
SECTION("invalid UTF-8 of a decoded string: the compact dump writes it, the others throw")
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
SECTION("float tokens: a changed byte is accepted if the text is still a number with the digits the node records")
|
||||
{
|
||||
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 :
|
||||
for (const char* const token :
|
||||
{
|
||||
0, 2
|
||||
"-12.375", "0.5", "123456.0", "-0.0", "9999999999999999999.5", "0.000000000000000000000001", "1.25e10", "5E-3"
|
||||
})
|
||||
{
|
||||
CHECK_THROWS_AS(loaded.root().dump(indent), json::type_error);
|
||||
CHECK_THROWS_AS(loaded.root().dump(indent, ' ', true), json::type_error);
|
||||
CAPTURE(token)
|
||||
const std::string text = std::string("[") + token + "]";
|
||||
const json_document d = json_document::parse(text);
|
||||
const std::vector<std::uint8_t> image = d.save();
|
||||
const std::size_t at = text_at(image) + 1; // the first byte of the token
|
||||
const std::uint16_t recorded = node_at(image, 1).extra;
|
||||
CHECK(loaded_dump(image) == json::parse(text).dump());
|
||||
for (std::size_t pos = 0; token[pos] != '\0'; ++pos)
|
||||
{
|
||||
for (unsigned int byte = 0; byte < 256; ++byte)
|
||||
{
|
||||
std::vector<std::uint8_t> changed = image;
|
||||
changed[at + pos] = static_cast<std::uint8_t>(byte);
|
||||
std::string t = token;
|
||||
t[pos] = static_cast<char>(byte);
|
||||
// the digits of the integer part and of the fraction of the changed token
|
||||
std::size_t integer = 0;
|
||||
std::size_t fraction = 0;
|
||||
std::size_t k = (t[0] == '-') ? 1 : 0;
|
||||
for (; k < t.size() && t[k] >= '0' && t[k] <= '9'; ++k)
|
||||
{
|
||||
++integer;
|
||||
}
|
||||
if (k < t.size() && t[k] == '.')
|
||||
{
|
||||
for (++k; k < t.size() && t[k] >= '0' && t[k] <= '9'; ++k)
|
||||
{
|
||||
++fraction;
|
||||
}
|
||||
}
|
||||
// (as a JSON text, the token may have white space around it)
|
||||
const bool float_token = json::accept(t) && t.find_first_of(std::string(" \t\r\n\0", 5)) == std::string::npos && (t.find('.') != std::string::npos || t.find_first_of("eE") != std::string::npos)
|
||||
&& ((integer | (fraction << 8U)) == recorded);
|
||||
CAPTURE(pos)
|
||||
CAPTURE(byte)
|
||||
std::string result;
|
||||
try
|
||||
{
|
||||
result = loaded_dump(changed);
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
CHECK(std::string(e.what()) == check_failed);
|
||||
result = "rejected";
|
||||
}
|
||||
CHECK((result != "rejected") == float_token);
|
||||
}
|
||||
}
|
||||
}
|
||||
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&);
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("a literal as the root: dump() after loading")
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user