mirror of
https://github.com/nlohmann/json.git
synced 2026-10-16 03:07:12 +00:00
Merge branch 'develop' into custom-number-type-fixes
Resolve the out_of_range.407 entry in exceptions.md: keep the list of formats and ranges from this branch and the "Former occurrences" note from develop. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
88 files changed
+7155
-1440
No files matched your search
@@ -9,11 +9,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint> // uint64_t
|
||||
#if !defined(__SIZEOF_INT128__) && defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64))
|
||||
#include <intrin0.h> // __umulh, _umul128
|
||||
#include <cstring> // memcpy
|
||||
#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) && (!defined(__SIZEOF_INT128__) || (!defined(__GNUC__) && !defined(__clang__)))
|
||||
#include <intrin0.h> // __umulh, _umul128, _BitScanForward64, _BitScanReverse64
|
||||
#endif
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp> // JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
|
||||
// Portable bit-level helpers for the number and string scanners. They use
|
||||
// compiler builtins or platform-specific intrinsics where available and plain
|
||||
@@ -28,6 +29,10 @@ inline int count_leading_zeros(std::uint64_t x) noexcept
|
||||
{
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
return __builtin_clzll(x);
|
||||
#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64))
|
||||
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
||||
_BitScanReverse64(&index, x);
|
||||
return 63 - static_cast<int>(index);
|
||||
#else
|
||||
int n = 0;
|
||||
for (int shift = 32; shift != 0; shift >>= 1)
|
||||
@@ -42,6 +47,29 @@ inline int count_leading_zeros(std::uint64_t x) noexcept
|
||||
#endif
|
||||
}
|
||||
|
||||
/// number of trailing zero bits of x (x != 0)
|
||||
inline int count_trailing_zeros(std::uint64_t x) noexcept
|
||||
{
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
return __builtin_ctzll(x);
|
||||
#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64))
|
||||
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
||||
_BitScanForward64(&index, x);
|
||||
return static_cast<int>(index);
|
||||
#else
|
||||
int n = 0;
|
||||
for (int shift = 32; shift != 0; shift >>= 1)
|
||||
{
|
||||
if ((x << (64 - shift)) == 0)
|
||||
{
|
||||
n += shift;
|
||||
x >>= shift;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// the 128-bit product of two 64-bit numbers
|
||||
struct uint128_parts
|
||||
{
|
||||
@@ -75,15 +103,27 @@ inline uint128_parts full_multiplication(std::uint64_t a, std::uint64_t b) noexc
|
||||
#endif
|
||||
}
|
||||
|
||||
/// eight bytes as a little-endian word (compilers fold this into one load on
|
||||
/// little-endian targets)
|
||||
inline std::uint64_t read_eight_bytes(const char* p) noexcept
|
||||
/// eight bytes as a little-endian word (a single load on little-endian
|
||||
/// targets; always inlined, as GCC otherwise calls it in the number loops)
|
||||
JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const unsigned char* b) noexcept
|
||||
{
|
||||
const auto* b = reinterpret_cast<const unsigned char*>(p); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
#if defined(_MSC_VER) || defined(__x86_64__) || defined(__i386__) || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
||||
// the byte order already matches (all MSVC targets are little-endian)
|
||||
std::uint64_t result = 0;
|
||||
std::memcpy(&result, b, sizeof(result));
|
||||
return result;
|
||||
#else
|
||||
return static_cast<std::uint64_t>(b[0]) | (static_cast<std::uint64_t>(b[1]) << 8u)
|
||||
| (static_cast<std::uint64_t>(b[2]) << 16u) | (static_cast<std::uint64_t>(b[3]) << 24u)
|
||||
| (static_cast<std::uint64_t>(b[4]) << 32u) | (static_cast<std::uint64_t>(b[5]) << 40u)
|
||||
| (static_cast<std::uint64_t>(b[6]) << 48u) | (static_cast<std::uint64_t>(b[7]) << 56u);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// eight bytes as a little-endian word
|
||||
JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const char* p) noexcept
|
||||
{
|
||||
return read_eight_bytes(reinterpret_cast<const unsigned char*>(p)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2009 Florian Loitsch <https://florian.loitsch.com/>
|
||||
// SPDX-FileCopyrightText: 2025 Victor Zverovich <https://github.com/vitaut/zmij>
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
@@ -11,11 +12,32 @@
|
||||
|
||||
#include <array> // array
|
||||
#include <cmath> // signbit, isfinite
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // intN_t, uintN_t
|
||||
#include <cstring> // memcpy, memmove
|
||||
#include <limits> // numeric_limits
|
||||
#include <type_traits> // conditional
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <cstdlib> // _byteswap_uint64
|
||||
#endif
|
||||
|
||||
// SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16
|
||||
// digits of a double at once
|
||||
#if defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC))
|
||||
#include <emmintrin.h>
|
||||
#define JSON_DTOA_SSE2 1
|
||||
#define JSON_DTOA_NEON 0
|
||||
#elif (defined(__aarch64__) || defined(_M_ARM64)) && !defined(_M_ARM64EC) && !defined(__ARM_BIG_ENDIAN)
|
||||
#include <arm_neon.h>
|
||||
#define JSON_DTOA_SSE2 0
|
||||
#define JSON_DTOA_NEON 1
|
||||
#else
|
||||
#define JSON_DTOA_SSE2 0
|
||||
#define JSON_DTOA_NEON 0
|
||||
#endif
|
||||
|
||||
#include <nlohmann/detail/conversions/zmij.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
@@ -1047,6 +1069,381 @@ inline char* format_buffer(char* buf, int len, int decimal_exponent,
|
||||
return append_exponent(buf, n - 1);
|
||||
}
|
||||
|
||||
/// eight decimal digits (a value below 10^8) as bytes 0..9, the first digit
|
||||
/// in the most significant byte: three steps that divide all lanes at once
|
||||
/// by a multiplication (the conversion of Xiang JunBo, as in Zmij)
|
||||
inline std::uint64_t eight_digit_bytes(std::uint64_t abcdefgh) noexcept
|
||||
{
|
||||
const std::uint64_t abcd_efgh = abcdefgh + (((std::uint64_t{1} << 32u) - 10000u) * ((abcdefgh * (((std::uint64_t{1} << 40u) / 10000u) + 1u)) >> 40u));
|
||||
const std::uint64_t ab_cd_ef_gh = abcd_efgh + (((std::uint64_t{1} << 16u) - 100u) * (((abcd_efgh * (((std::uint64_t{1} << 19u) / 100u) + 1u)) >> 19u) & 0x7F0000007Fu));
|
||||
return ab_cd_ef_gh + (((std::uint64_t{1} << 8u) - 10u) * (((ab_cd_ef_gh * (((std::uint64_t{1} << 10u) / 10u) + 1u)) >> 10u) & 0x000F000F000F000Fu));
|
||||
}
|
||||
|
||||
/// store the bytes of v, the most significant one first (one byte swap and
|
||||
/// one store where the byte order is known: compilers do not reliably merge
|
||||
/// the byte stores once this is inlined)
|
||||
inline void store_msb_first(char* p, std::uint64_t v) noexcept
|
||||
{
|
||||
#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
v = __builtin_bswap64(v);
|
||||
std::memcpy(p, &v, sizeof(v));
|
||||
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
std::memcpy(p, &v, sizeof(v));
|
||||
#elif defined(_MSC_VER) // (little-endian on all its targets)
|
||||
v = _byteswap_uint64(v);
|
||||
std::memcpy(p, &v, sizeof(v));
|
||||
#else
|
||||
for (unsigned i = 0; i < 8; ++i)
|
||||
{
|
||||
p[i] = static_cast<char>(v >> (56u - (8u * i)));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief digits * 10^exp for a double, in the layout of format_buffer()
|
||||
|
||||
The layout is that of format_buffer() with min_exp -4 and max_exp 15 (the
|
||||
digits10 of double). The digits are converted eight at a time and placed
|
||||
with fixed-size moves instead of per-digit loops and moves of the buffer.
|
||||
|
||||
@param[in] digits the digits (not 0, at most 17 digits; trailing zeros allowed)
|
||||
@param[in] exp the decimal exponent of the last digit
|
||||
@return a pointer past the text; up to 41 bytes at @a first are written
|
||||
(some beyond the returned end)
|
||||
*/
|
||||
JSON_HEDLEY_NON_NULL(1)
|
||||
JSON_HEDLEY_RETURNS_NON_NULL
|
||||
inline char* write_decimal(char* first, std::uint64_t digits, int exp) noexcept
|
||||
{
|
||||
JSON_ASSERT(digits != 0 && digits < 100000000000000000u);
|
||||
const std::uint64_t upper = digits / 100000000u;
|
||||
const std::uint64_t b0 = upper / 100000000u; // (one digit: it is its own byte)
|
||||
const std::uint64_t b1 = eight_digit_bytes(upper % 100000000u);
|
||||
const std::uint64_t b2 = eight_digit_bytes(digits % 100000000u);
|
||||
// leading and trailing zero digits: zero bytes, counted without division
|
||||
int leading = 16;
|
||||
int zeros = 16;
|
||||
if (b0 != 0)
|
||||
{
|
||||
leading = count_leading_zeros(b0) / 8;
|
||||
}
|
||||
else if (b1 != 0)
|
||||
{
|
||||
leading = 8 + (count_leading_zeros(b1) / 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
leading += count_leading_zeros(b2) / 8;
|
||||
}
|
||||
if (b2 != 0)
|
||||
{
|
||||
zeros = count_trailing_zeros(b2) / 8;
|
||||
}
|
||||
else if (b1 != 0)
|
||||
{
|
||||
zeros = 8 + (count_trailing_zeros(b1) / 8);
|
||||
}
|
||||
// (else: 16, b0 is the one digit that is not 0)
|
||||
// the digits as text at text + leading, then '0's, so that fixed-size
|
||||
// moves need not check how many digits there are
|
||||
std::array<char, 64> text; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
store_msb_first(text.data(), b0 + 0x3030303030303030u);
|
||||
store_msb_first(text.data() + 8, b1 + 0x3030303030303030u);
|
||||
store_msb_first(text.data() + 16, b2 + 0x3030303030303030u);
|
||||
std::memset(text.data() + 24, '0', 40);
|
||||
const int k = 24 - leading - zeros; // significant digits
|
||||
const int n = k + exp + zeros; // position of the decimal point after the first digit
|
||||
const char* const s0 = text.data() + leading;
|
||||
|
||||
if (-4 < n && n <= 15)
|
||||
{
|
||||
// "0.[000]digits" (n <= 0) is the digits after 1 - n leading '0's
|
||||
// with the point after the first; "digits[000].0" (n >= k) and
|
||||
// "dig.its" put the point after n characters
|
||||
const int pad = n <= 0 ? 1 - n : 0;
|
||||
const char* const s = s0 - pad;
|
||||
const int len = k + pad;
|
||||
const int point = n + pad;
|
||||
std::memcpy(first, s, 16);
|
||||
std::memcpy(first + point + 1, s + point, 24);
|
||||
first[point] = '.';
|
||||
return first + (point >= len ? point + 2 : len + 1);
|
||||
}
|
||||
|
||||
// d.igitse+XX, with at least two exponent digits (as append_exponent())
|
||||
std::memcpy(first, s0, 16);
|
||||
std::memcpy(first + 2, s0 + 1, 16);
|
||||
first[1] = '.';
|
||||
char* const end = first + (k == 1 ? 1 : k + 1);
|
||||
const int e = n - 1;
|
||||
const auto ea = e < 0 ? 0u - static_cast<unsigned>(e) : static_cast<unsigned>(e); // (unsigned: no signed overflow to assume)
|
||||
const bool three = ea >= 100;
|
||||
end[0] = 'e';
|
||||
end[1] = e < 0 ? '-' : '+';
|
||||
end[2] = static_cast<char>('0' + (three ? ea / 100 : (ea / 10) % 10));
|
||||
end[3] = static_cast<char>('0' + (three ? (ea / 10) % 10 : ea % 10));
|
||||
end[4] = static_cast<char>('0' + (ea % 10));
|
||||
return end + (three ? 5 : 4);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the shortest decimal of a positive double (Zmij), as write_decimal()
|
||||
writes it
|
||||
|
||||
For a normal double, the shorter candidate has 15 or 16 digits: they are
|
||||
converted at once (two halves of eight digits) and followed by the digit
|
||||
after them, if there is one, without the multiplication and division by 10
|
||||
that counting the digits of one number would take. The fixed layouts move
|
||||
the digits after the point by one byte.
|
||||
|
||||
@return a pointer past the text; up to 41 bytes at @a first are written
|
||||
(some beyond the returned end)
|
||||
*/
|
||||
JSON_HEDLEY_NON_NULL(1)
|
||||
JSON_HEDLEY_RETURNS_NON_NULL
|
||||
inline char* write_shortest(char* first, const zmij::shortest_decimal d) noexcept
|
||||
{
|
||||
const std::uint64_t sig = d.integral;
|
||||
if (JSON_HEDLEY_UNLIKELY(sig < 100000000000000u || sig >= 10000000000000000u))
|
||||
{
|
||||
// (subnormals)
|
||||
return d.has_digit ? write_decimal(first, (sig * 10) + d.digit, d.exponent) : write_decimal(first, sig, d.exponent + 1);
|
||||
}
|
||||
const bool sixteen = sig >= 1000000000000000u; // (else 15 digits)
|
||||
const int last = d.has_digit ? d.digit : 0;
|
||||
const std::uint64_t upper = sig / 100000000u;
|
||||
#if JSON_DTOA_SSE2
|
||||
// NOLINTBEGIN(portability-simd-intrinsics)
|
||||
// the two halves in the 64-bit lanes, each as abcd * 2^32 + efgh, then as
|
||||
// bytes (as eight_digit_bytes(), one lane each)
|
||||
const __m128i x = _mm_set_epi64x(static_cast<long long>(sig - (upper * 100000000u)), static_cast<long long>(upper)); // NOLINT(runtime/int)
|
||||
const __m128i abcd = _mm_srli_epi64(_mm_mul_epu32(x, _mm_set1_epi64x(109951163)), 40); // 2^40 / 10000 + 1
|
||||
const __m128i abcd_efgh = _mm_add_epi64(x, _mm_mul_epu32(abcd, _mm_set1_epi64x(4294957296))); // 2^32 - 10000
|
||||
// 32-bit lanes in the order of the text: abcd, efgh of both halves
|
||||
const __m128i fours = _mm_shuffle_epi32(abcd_efgh, _MM_SHUFFLE(2, 3, 0, 1));
|
||||
const __m128i ab = _mm_srli_epi16(_mm_mulhi_epu16(fours, _mm_set1_epi32(5243)), 3);
|
||||
const __m128i ab_cd = _mm_or_si128(_mm_slli_epi32(_mm_sub_epi16(fours, _mm_mullo_epi16(ab, _mm_set1_epi32(100))), 16), ab);
|
||||
// 16-bit lanes ab (< 100) -> bytes a, b: 256 * ab - 2559 * (ab / 10)
|
||||
const __m128i bytes = _mm_sub_epi16(_mm_slli_epi16(ab_cd, 8), _mm_mullo_epi16(_mm_set1_epi16(2559), _mm_mulhi_epu16(ab_cd, _mm_set1_epi16(6554))));
|
||||
// the last digit that is not 0 (sig is not 0)
|
||||
const auto nonzero = static_cast<std::uint64_t>(_mm_movemask_epi8(_mm_cmpgt_epi8(bytes, _mm_setzero_si128())));
|
||||
const int digits = 63 - count_leading_zeros(nonzero) + (sixteen ? 1 : 0); // without trailing zeros
|
||||
const __m128i chars = _mm_add_epi8(bytes, _mm_set1_epi8('0'));
|
||||
// the 16 characters from the first digit
|
||||
const __m128i s = sixteen ? chars : _mm_or_si128(_mm_srli_si128(chars, 1), _mm_slli_si128(_mm_cvtsi32_si128('0' + last), 15));
|
||||
const char s16 = static_cast<char>(sixteen ? '0' + last : '0'); // the 17th
|
||||
const auto store_16 = [&s](char* p) noexcept
|
||||
{
|
||||
std::memcpy(p, &s, 16);
|
||||
};
|
||||
const char first_digit = static_cast<char>(_mm_cvtsi128_si32(s));
|
||||
// NOLINTEND(portability-simd-intrinsics)
|
||||
#elif JSON_DTOA_NEON
|
||||
// as with SSE2: the halves in 32-bit lanes, then abcd, efgh of both
|
||||
const uint32x2_t halves = vcreate_u32(upper | ((sig - (upper * 100000000u)) << 32u));
|
||||
const uint32x2_t abcd = vmovn_u64(vshrq_n_u64(vmull_n_u32(halves, static_cast<std::uint32_t>(((std::uint64_t{1} << 40u) / 10000u) + 1u)), 40));
|
||||
const uint32x2_t efgh = vmls_n_u32(halves, abcd, 10000u);
|
||||
const uint32x4_t fours = vcombine_u32(vzip1_u32(abcd, efgh), vzip2_u32(abcd, efgh));
|
||||
const uint32x4_t ab = vshrq_n_u32(vmulq_n_u32(fours, 5243u), 19);
|
||||
const uint16x8_t ab_cd = vreinterpretq_u16_u32(vorrq_u32(ab, vshlq_n_u32(vmlsq_n_u32(fours, ab, 100u), 16)));
|
||||
const uint16x8_t tens = vshrq_n_u16(vmulq_n_u16(ab_cd, 103u), 10);
|
||||
const uint8x16_t bytes = vreinterpretq_u8_u16(vorrq_u16(tens, vshlq_n_u16(vmlsq_n_u16(ab_cd, tens, 10u), 8)));
|
||||
// the last digit that is not 0 (sig is not 0): a nibble per byte
|
||||
const std::uint64_t nonzero = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(vtstq_u8(bytes, bytes)), 4)), 0);
|
||||
const int digits = ((63 - count_leading_zeros(nonzero)) / 4) + (sixteen ? 1 : 0); // without trailing zeros
|
||||
const uint8x16_t chars = vaddq_u8(bytes, vdupq_n_u8('0'));
|
||||
// the 16 characters from the first digit
|
||||
const uint8x16_t s = sixteen ? chars : vextq_u8(chars, vdupq_n_u8(static_cast<std::uint8_t>('0' + last)), 1);
|
||||
const char s16 = static_cast<char>(sixteen ? '0' + last : '0'); // the 17th
|
||||
const auto store_16 = [&s](char* p) noexcept
|
||||
{
|
||||
vst1q_u8(reinterpret_cast<std::uint8_t*>(p), s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
};
|
||||
const auto first_digit = static_cast<char>(vgetq_lane_u8(s, 0));
|
||||
#else
|
||||
const std::uint64_t hi = eight_digit_bytes(upper);
|
||||
const std::uint64_t lo = eight_digit_bytes(sig - (upper * 100000000u));
|
||||
// trailing zero digits: zero bytes (sig is not 0)
|
||||
const int zeros = lo != 0 ? count_trailing_zeros(lo) / 8 : 8 + (count_trailing_zeros(hi) / 8);
|
||||
const int digits = 15 - zeros + (sixteen ? 1 : 0); // without trailing zeros
|
||||
// the 16 characters from the first digit
|
||||
const std::uint64_t s_hi = (sixteen ? hi : (hi << 8u) | (lo >> 56u)) + 0x3030303030303030u;
|
||||
const std::uint64_t s_lo = (sixteen ? lo : (lo << 8u) | static_cast<std::uint64_t>(last)) + 0x3030303030303030u;
|
||||
const char s16 = static_cast<char>(sixteen ? '0' + last : '0'); // the 17th
|
||||
const auto store_16 = [s_hi, s_lo](char* p) noexcept
|
||||
{
|
||||
store_msb_first(p, s_hi);
|
||||
store_msb_first(p + 8, s_lo);
|
||||
};
|
||||
const auto first_digit = static_cast<char>(s_hi >> 56u);
|
||||
#endif
|
||||
const int len = d.has_digit ? 16 + (sixteen ? 1 : 0) : digits; // significant digits
|
||||
const int n = 16 + (sixteen ? 1 : 0) + d.exponent; // digits before the point
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(n >= 1 && n <= 15))
|
||||
{
|
||||
// "dig.its" and "digits[000].0": the digits after the point move by
|
||||
// one byte ('0's follow the digits)
|
||||
#if JSON_DTOA_SSE2
|
||||
// NOLINTBEGIN(portability-simd-intrinsics)
|
||||
// (in the register: reading the digits back from memory right after
|
||||
// storing them waits until the stores are done)
|
||||
const __m128i at = _mm_set1_epi8(static_cast<char>(n));
|
||||
const __m128i index = _mm_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
|
||||
const __m128i before = _mm_cmpgt_epi8(at, index);
|
||||
const __m128i after = _mm_cmpgt_epi8(index, at);
|
||||
const __m128i text = _mm_or_si128(_mm_or_si128(_mm_and_si128(s, before), _mm_and_si128(_mm_slli_si128(s, 1), after)),
|
||||
_mm_andnot_si128(_mm_or_si128(before, after), _mm_set1_epi8('.')));
|
||||
std::memcpy(first, &text, 16);
|
||||
first[16] = static_cast<char>(_mm_extract_epi16(s, 7) >> 8);
|
||||
first[17] = s16;
|
||||
// NOLINTEND(portability-simd-intrinsics)
|
||||
#elif JSON_DTOA_NEON
|
||||
const uint8x16_t index = vcombine_u8(vcreate_u8(0x0706050403020100u), vcreate_u8(0x0F0E0D0C0B0A0908u));
|
||||
const uint8x16_t at = vdupq_n_u8(static_cast<std::uint8_t>(n));
|
||||
const uint8x16_t after_point = vbslq_u8(vcgtq_u8(index, at), vextq_u8(vdupq_n_u8(0), s, 15), vdupq_n_u8('.'));
|
||||
vst1q_u8(reinterpret_cast<std::uint8_t*>(first), vbslq_u8(vcltq_u8(index, at), s, after_point)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
first[16] = static_cast<char>(vgetq_lane_u8(s, 15));
|
||||
first[17] = s16;
|
||||
#else
|
||||
store_16(first);
|
||||
first[16] = s16;
|
||||
std::uint64_t after_point[2]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::memcpy(after_point, first + n, 16);
|
||||
std::memcpy(first + n + 1, after_point, 16);
|
||||
first[n] = '.';
|
||||
#endif
|
||||
return first + (n >= len ? n + 2 : len + 1);
|
||||
}
|
||||
if (n <= 0 && n > -4)
|
||||
{
|
||||
// "0.[000]digits"
|
||||
std::memset(first, '0', 8);
|
||||
first[1] = '.';
|
||||
store_16(first + 2 - n);
|
||||
first[18 - n] = s16;
|
||||
return first + 2 - n + len;
|
||||
}
|
||||
// d.igitse+XX, with at least two exponent digits (as append_exponent())
|
||||
store_16(first + 1);
|
||||
first[17] = s16;
|
||||
first[0] = first_digit;
|
||||
first[1] = '.';
|
||||
char* const end = first + (len == 1 ? 1 : len + 1);
|
||||
const int e = n - 1;
|
||||
const auto ea = e < 0 ? 0u - static_cast<unsigned>(e) : static_cast<unsigned>(e); // (unsigned: no signed overflow to assume)
|
||||
const bool three = ea >= 100;
|
||||
end[0] = 'e';
|
||||
end[1] = e < 0 ? '-' : '+';
|
||||
end[2] = static_cast<char>('0' + (three ? ea / 100 : (ea / 10) % 10));
|
||||
end[3] = static_cast<char>('0' + (three ? (ea / 10) % 10 : ea % 10));
|
||||
end[4] = static_cast<char>('0' + (ea % 10));
|
||||
return end + (three ? 5 : 4);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether FloatType is an IEEE 754 binary64 type (a double, or a long double
|
||||
that has the same format, as with MSVC and on Apple's Arm CPUs)
|
||||
|
||||
These are the types the conversion of Zmij (see zmij.hpp) is used for; all
|
||||
others (binary32, or a format the library does not know) use Grisu2.
|
||||
*/
|
||||
template<typename FloatType>
|
||||
constexpr bool has_binary64_format() noexcept
|
||||
{
|
||||
return std::numeric_limits<FloatType>::is_iec559
|
||||
&& std::numeric_limits<FloatType>::digits == 53
|
||||
&& std::numeric_limits<FloatType>::max_exponent == 1024
|
||||
&& sizeof(FloatType) == sizeof(std::uint64_t);
|
||||
}
|
||||
|
||||
template<typename FloatType>
|
||||
struct is_binary64 : std::integral_constant<bool, has_binary64_format<FloatType>()> {};
|
||||
|
||||
/// a positive finite float (other than binary64): Grisu2 and format_buffer()
|
||||
template<typename FloatType>
|
||||
JSON_HEDLEY_NON_NULL(1, 2)
|
||||
JSON_HEDLEY_RETURNS_NON_NULL
|
||||
char* write_positive_grisu2(char* first, const char* last, FloatType value)
|
||||
{
|
||||
JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10);
|
||||
static_cast<void>(last); // (only used in the assertion)
|
||||
|
||||
// Compute v = buffer * 10^decimal_exponent.
|
||||
// The decimal digits are stored in the buffer, which needs to be interpreted
|
||||
// as an unsigned decimal integer.
|
||||
// len is the length of the buffer, i.e., the number of decimal digits.
|
||||
int len = 0;
|
||||
int decimal_exponent = 0;
|
||||
grisu2(first, len, decimal_exponent, value);
|
||||
|
||||
JSON_ASSERT(len <= std::numeric_limits<FloatType>::max_digits10);
|
||||
|
||||
// Format the buffer like printf("%.*g", prec, value)
|
||||
constexpr int kMinExp = -4;
|
||||
// Use digits10 here to increase compatibility with version 2.
|
||||
constexpr int kMaxExp = std::numeric_limits<FloatType>::digits10;
|
||||
|
||||
JSON_ASSERT(last - first >= kMaxExp + 2);
|
||||
JSON_ASSERT(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits<FloatType>::max_digits10);
|
||||
JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10 + 6);
|
||||
|
||||
return format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp);
|
||||
}
|
||||
|
||||
/// a positive finite binary64 number: the shortest digits (Zmij), laid out by
|
||||
/// write_shortest() (through a local buffer if [first, last) is shorter than
|
||||
/// the 41 bytes it may write)
|
||||
template<typename FloatType>
|
||||
JSON_HEDLEY_NON_NULL(1, 2)
|
||||
JSON_HEDLEY_RETURNS_NON_NULL
|
||||
char* write_positive_zmij(char* first, const char* last, FloatType value)
|
||||
{
|
||||
static_assert(is_binary64<FloatType>::value,
|
||||
"internal error: the conversion of Zmij needs IEEE 754 binary64 numbers");
|
||||
std::uint64_t bits = 0;
|
||||
std::memcpy(&bits, &value, sizeof(bits));
|
||||
const zmij::shortest_decimal d = zmij::to_shortest(bits);
|
||||
if (JSON_HEDLEY_LIKELY(last - first >= 41))
|
||||
{
|
||||
return write_shortest(first, d);
|
||||
}
|
||||
std::array<char, 64> buf; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
const auto len = static_cast<std::size_t>(write_shortest(buf.data(), d) - buf.data());
|
||||
JSON_ASSERT(last - first >= static_cast<std::ptrdiff_t>(len));
|
||||
std::memcpy(first, buf.data(), len);
|
||||
return first + len;
|
||||
}
|
||||
|
||||
/// a positive finite binary64 number: Zmij (as a long double has the format of
|
||||
/// a double here, its bits are those of the double of the same value)
|
||||
template<typename FloatType>
|
||||
JSON_HEDLEY_NON_NULL(1, 2)
|
||||
JSON_HEDLEY_RETURNS_NON_NULL
|
||||
char* write_positive(char* first, const char* last, FloatType value, std::true_type /*is_binary64*/)
|
||||
{
|
||||
return write_positive_zmij(first, last, value);
|
||||
}
|
||||
|
||||
/// a positive finite float of any other format: Grisu2
|
||||
template<typename FloatType>
|
||||
JSON_HEDLEY_NON_NULL(1, 2)
|
||||
JSON_HEDLEY_RETURNS_NON_NULL
|
||||
char* write_positive(char* first, const char* last, FloatType value, std::false_type /*is_binary64*/)
|
||||
{
|
||||
return write_positive_grisu2(first, last, value);
|
||||
}
|
||||
|
||||
/// a positive finite float: Zmij for binary64 numbers, Grisu2 otherwise
|
||||
template<typename FloatType>
|
||||
JSON_HEDLEY_NON_NULL(1, 2)
|
||||
JSON_HEDLEY_RETURNS_NON_NULL
|
||||
char* write_positive(char* first, const char* last, FloatType value)
|
||||
{
|
||||
return write_positive(first, last, value, is_binary64<FloatType> {});
|
||||
}
|
||||
|
||||
} // namespace dtoa_impl
|
||||
|
||||
/*!
|
||||
@@ -1064,7 +1461,6 @@ JSON_HEDLEY_NON_NULL(1, 2)
|
||||
JSON_HEDLEY_RETURNS_NON_NULL
|
||||
char* to_chars(char* first, const char* last, FloatType value)
|
||||
{
|
||||
static_cast<void>(last); // maybe unused - fix warning
|
||||
JSON_ASSERT(std::isfinite(value));
|
||||
|
||||
// Use signbit(value) instead of (value < 0) since signbit works for -0.
|
||||
@@ -1090,28 +1486,7 @@ char* to_chars(char* first, const char* last, FloatType value)
|
||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||
#endif
|
||||
|
||||
JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10);
|
||||
|
||||
// Compute v = buffer * 10^decimal_exponent.
|
||||
// The decimal digits are stored in the buffer, which needs to be interpreted
|
||||
// as an unsigned decimal integer.
|
||||
// len is the length of the buffer, i.e., the number of decimal digits.
|
||||
int len = 0;
|
||||
int decimal_exponent = 0;
|
||||
dtoa_impl::grisu2(first, len, decimal_exponent, value);
|
||||
|
||||
JSON_ASSERT(len <= std::numeric_limits<FloatType>::max_digits10);
|
||||
|
||||
// Format the buffer like printf("%.*g", prec, value)
|
||||
constexpr int kMinExp = -4;
|
||||
// Use digits10 here to increase compatibility with version 2.
|
||||
constexpr int kMaxExp = std::numeric_limits<FloatType>::digits10;
|
||||
|
||||
JSON_ASSERT(last - first >= kMaxExp + 2);
|
||||
JSON_ASSERT(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits<FloatType>::max_digits10);
|
||||
JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10 + 6);
|
||||
|
||||
return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp);
|
||||
return dtoa_impl::write_positive(first, last, value);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2025 Victor Zverovich <https://github.com/vitaut/zmij>
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint32_t, uint64_t
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/bit_ops.hpp>
|
||||
#include <nlohmann/detail/input/pow5_table.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
|
||||
/*!
|
||||
@brief the shortest decimal representation of a double
|
||||
|
||||
A C++11 port of the conversion of Zmij by Victor Zverovich
|
||||
(https://github.com/vitaut/zmij, MIT license): the shortest decimal in the
|
||||
rounding interval of a double, the closest one if there are several. Zmij
|
||||
credits Xiang JunBo (producing the shorter candidate without a division) and
|
||||
Dougall Johnson (the compressed powers of ten). The powers of ten are taken
|
||||
from the table for number parsing (pow5_table.hpp) where it holds them, and
|
||||
computed from the compressed tables of Zmij beyond it.
|
||||
*/
|
||||
namespace zmij
|
||||
{
|
||||
|
||||
/// the compressed powers of ten of Zmij
|
||||
inline const std::array<std::uint64_t, 28>& pow10_minor() noexcept
|
||||
{
|
||||
static const std::array<std::uint64_t, 28> table =
|
||||
{
|
||||
{
|
||||
0x8000000000000000u, 0xa000000000000000u, 0xc800000000000000u, 0xfa00000000000000u, 0x9c40000000000000u,
|
||||
0xc350000000000000u, 0xf424000000000000u, 0x9896800000000000u, 0xbebc200000000000u, 0xee6b280000000000u,
|
||||
0x9502f90000000000u, 0xba43b74000000000u, 0xe8d4a51000000000u, 0x9184e72a00000000u, 0xb5e620f480000000u,
|
||||
0xe35fa931a0000000u, 0x8e1bc9bf04000000u, 0xb1a2bc2ec5000000u, 0xde0b6b3a76400000u, 0x8ac7230489e80000u,
|
||||
0xad78ebc5ac620000u, 0xd8d726b7177a8000u, 0x878678326eac9000u, 0xa968163f0a57b400u, 0xd3c21bcecceda100u,
|
||||
0x84595161401484a0u, 0xa56fa5b99019a5c8u, 0xcecb8f27f4200f3au
|
||||
}
|
||||
};
|
||||
return table;
|
||||
}
|
||||
|
||||
/// (high, low) pairs
|
||||
inline const std::array<std::uint64_t, 50>& pow10_major() noexcept
|
||||
{
|
||||
static const std::array<std::uint64_t, 50> table =
|
||||
{
|
||||
{
|
||||
0xaddcb9e83c6b1793u, 0xdf4abe242a1bbf3eu, 0xaf8e5410288e1b6fu, 0x07ecf0ae5ee44ddau, 0xb1442798f49ffb4au, 0x99cd11cfdf41779du,
|
||||
0xb2fe3f0b8599ef07u, 0x861fa7e6dcb4aa15u, 0xb4bca50b065abe63u, 0x0fed077a756b53aau, 0xb67f6455292cbf08u, 0x1a3bc84c17b1d543u,
|
||||
0xb84687c269ef3bfbu, 0x3d5d514f40eea742u, 0xba121a4650e4ddebu, 0x92f34d62616ce413u, 0xbbe226efb628afeau, 0x890489f70a55368cu,
|
||||
0xbdb6b8e905cb600fu, 0x5400e987bbc1c921u, 0xbf8fdb78849a5f96u, 0xde98520472bdd034u, 0xc16d9a0095928a27u, 0x75b7053c0f178294u,
|
||||
0xc350000000000000u, 0x0000000000000000u, 0xc5371912364ce305u, 0x6c28000000000000u, 0xc722f0ef9d80aad6u, 0x424d3ad2b7b97ef6u,
|
||||
0xc913936dd571c84cu, 0x03bc3a19cd1e38eau, 0xcb090c8001ab551cu, 0x5cadf5bfd3072cc6u, 0xcd036837130890a1u, 0x36dba887c37a8c10u,
|
||||
0xcf02b2c21207ef2eu, 0x94f967e45e03f4bcu, 0xd106f86e69d785c7u, 0xe13336d701beba52u, 0xd31045a8341ca07cu, 0x1ede48111209a051u,
|
||||
0xd51ea6fa85785631u, 0x552a74227f3ea566u, 0xd732290fbacaf133u, 0xa97c177947ad4096u, 0xd94ad8b1c7380874u, 0x18375281ae7822bdu,
|
||||
0xdb68c2ca82ed2a05u, 0xa67398db9f6820e1u
|
||||
}
|
||||
};
|
||||
return table;
|
||||
}
|
||||
|
||||
/// one bit per power: whether the computed value is one unit too large
|
||||
inline const std::array<std::uint32_t, 21>& pow10_fixups() noexcept
|
||||
{
|
||||
static const std::array<std::uint32_t, 21> table =
|
||||
{
|
||||
{
|
||||
0x8d8fc810u, 0x06100293u, 0x19000000u, 0x00100000u, 0x00000908u, 0x00000000u, 0x04e00300u, 0x3807e0b2u, 0x3d83d793u, 0x0006f5ccu,
|
||||
0x00000000u, 0xffff0000u, 0x8076337du, 0x4ff45ba0u, 0x09405033u, 0x034376d9u, 0x09000000u, 0x4e100501u, 0x076d14dcu, 0xf964f45eu,
|
||||
0x0000003du
|
||||
}
|
||||
};
|
||||
return table;
|
||||
}
|
||||
|
||||
/// the 128-bit significand of 10^k, rounded down, for k in [-307, 341]
|
||||
/// (compute_pow10 of Zmij)
|
||||
inline uint128_parts compute_pow10(int k) noexcept
|
||||
{
|
||||
const auto i = static_cast<unsigned>(k + 307);
|
||||
const std::uint64_t m = pow10_minor()[(i + 24) % 28];
|
||||
const std::size_t j = 2 * static_cast<std::size_t>((i + 24) / 28);
|
||||
const std::uint64_t h_hi = pow10_major()[j];
|
||||
const std::uint64_t h_lo = pow10_major()[j + 1];
|
||||
const std::uint64_t h1 = full_multiplication(h_lo, m).high;
|
||||
const std::uint64_t c0 = h_lo * m;
|
||||
const std::uint64_t c1 = h1 + (h_hi * m);
|
||||
const std::uint64_t c2 = (c1 < h1 ? 1u : 0u) + full_multiplication(h_hi, m).high;
|
||||
uint128_parts r{};
|
||||
if ((c2 >> 63u) != 0)
|
||||
{
|
||||
r.high = c2;
|
||||
r.low = c1;
|
||||
}
|
||||
else
|
||||
{
|
||||
r.high = (c2 << 1u) | (c1 >> 63u);
|
||||
r.low = (c1 << 1u) | (c0 >> 63u);
|
||||
}
|
||||
r.low -= (pow10_fixups()[i >> 5u] >> (i & 31u)) & 1u;
|
||||
return r;
|
||||
}
|
||||
|
||||
/// The 128-bit significand of 10^k, rounded down, for k in [-342, 341].
|
||||
/// Up to 10^308, the table for number parsing holds the same significands
|
||||
/// (those of 5^k), except for k in [-27, -1], where it holds them one unit
|
||||
/// larger (as the Eisel-Lemire algorithm needs them).
|
||||
inline uint128_parts pow10(int k) noexcept
|
||||
{
|
||||
if (k > pow5_128_largest_power)
|
||||
{
|
||||
return compute_pow10(k); // (only for the smallest doubles)
|
||||
}
|
||||
const auto i = 2 * static_cast<std::size_t>(k - pow5_128_smallest_power);
|
||||
uint128_parts r{pow5_128()[i + 1], pow5_128()[i]};
|
||||
const std::uint64_t adjust = static_cast<unsigned>(k + 27) < 27u ? 1u : 0u;
|
||||
r.high -= r.low < adjust ? 1u : 0u;
|
||||
r.low -= adjust;
|
||||
return r;
|
||||
}
|
||||
|
||||
/// (x_hi * 2^64 + x_lo) * y >> 64, as 128 bits
|
||||
inline uint128_parts umul192_hi128(std::uint64_t x_hi, std::uint64_t x_lo, std::uint64_t y) noexcept
|
||||
{
|
||||
const uint128_parts p = full_multiplication(x_hi, y);
|
||||
uint128_parts r{};
|
||||
r.low = p.low + full_multiplication(x_lo, y).high;
|
||||
r.high = p.high + (r.low < p.low ? 1u : 0u);
|
||||
return r;
|
||||
}
|
||||
|
||||
/// (x * y + c) >> 64
|
||||
inline std::uint64_t umul128_add_hi64(std::uint64_t x, std::uint64_t y, std::uint64_t c) noexcept
|
||||
{
|
||||
const uint128_parts p = full_multiplication(x, y);
|
||||
return p.high + (p.low + c < p.low ? 1u : 0u);
|
||||
}
|
||||
|
||||
/// the result of Zmij: the shorter candidate and, if that is outside the
|
||||
/// rounding interval, the digit after it (16 bytes: returned in registers)
|
||||
struct shortest_decimal
|
||||
{
|
||||
std::uint64_t integral; ///< the shorter candidate (15 or 16 digits for normal doubles)
|
||||
int exponent; ///< the decimal exponent of the digit after it
|
||||
unsigned char digit; ///< the digit after it (if has_digit)
|
||||
bool has_digit; ///< whether the shortest decimal is integral * 10 + digit
|
||||
};
|
||||
|
||||
/// The shortest decimal in the rounding interval of a positive finite double
|
||||
/// given by its bits, the closest one if there are several (to_decimal of
|
||||
/// Zmij, which keeps the last digit apart: the 15 or 16 digits before it can be
|
||||
/// converted without a multiplication by 10 first). Always inlined: GCC
|
||||
/// otherwise calls it, and its result goes through memory.
|
||||
JSON_HEDLEY_ALWAYS_INLINE shortest_decimal to_shortest(std::uint64_t bits) noexcept
|
||||
{
|
||||
constexpr int extra_shift = 9;
|
||||
const auto raw_exp = static_cast<int>((bits >> 52u) & 0x7FFu);
|
||||
std::uint64_t bin_sig = bits & ((std::uint64_t{1} << 52u) - 1);
|
||||
// a power of two has a narrower interval below (except the smallest normal)
|
||||
const bool regular = bin_sig != 0 || raw_exp <= 1;
|
||||
const int bin_exp = (raw_exp == 0 ? 1 : raw_exp) - 1075;
|
||||
if (raw_exp != 0)
|
||||
{
|
||||
bin_sig |= std::uint64_t{1} << 52u;
|
||||
}
|
||||
// floor(log10(2^bin_exp)), or floor(log10(3/4 * 2^bin_exp)) for the irregular case
|
||||
const int dec_exp = ((bin_exp * 315653) - (regular ? 0 : 131072)) >> 20;
|
||||
// scaled by 10^(-dec_exp - 1): the integral part is the shorter candidate
|
||||
const int shift = bin_exp + ((-(dec_exp + 1) * 217707) >> 16) + 1 + extra_shift;
|
||||
const uint128_parts p10 = pow10(-dec_exp - 1);
|
||||
const uint128_parts p = umul192_hi128(p10.high, p10.low, bin_sig << static_cast<unsigned>(shift));
|
||||
std::uint64_t integral = p.high >> static_cast<unsigned>(extra_shift);
|
||||
const std::uint64_t fractional = (p.high << static_cast<unsigned>(64 - extra_shift)) | (p.low >> static_cast<unsigned>(extra_shift));
|
||||
std::uint64_t digit = 0;
|
||||
bool round_up = false;
|
||||
bool round_down = false;
|
||||
if (JSON_HEDLEY_LIKELY(regular))
|
||||
{
|
||||
const std::uint64_t half_ulp = (p10.high >> static_cast<unsigned>(extra_shift + 1 - shift)) + (1 - (bin_sig & 1u));
|
||||
round_up = fractional + half_ulp < fractional;
|
||||
round_down = half_ulp > fractional;
|
||||
// the last digit of the longer candidate, rounded to nearest
|
||||
digit = umul128_add_hi64(fractional, 10, (std::uint64_t{1} << 63u) + 6);
|
||||
if (fractional == (std::uint64_t{1} << 62u))
|
||||
{
|
||||
digit = 2; // 2.5 rounds to 2
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::uint64_t half_ulp = p10.high >> static_cast<unsigned>(extra_shift + 1 - shift);
|
||||
round_up = half_ulp > ~std::uint64_t{0} - fractional;
|
||||
round_down = (half_ulp >> 1u) > fractional;
|
||||
digit = umul128_add_hi64(fractional, 10, (std::uint64_t{1} << 63u) - 1);
|
||||
const std::uint64_t lowest = umul128_add_hi64(fractional - (half_ulp >> 1u), 10, ~std::uint64_t{0});
|
||||
digit = digit < lowest ? lowest : digit;
|
||||
}
|
||||
integral += round_up ? 1u : 0u;
|
||||
// if the shorter candidate is outside the rounding interval: one digit more
|
||||
return shortest_decimal{integral, dec_exp, static_cast<unsigned char>(digit), !round_up && !round_down};
|
||||
}
|
||||
|
||||
} // namespace zmij
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // min
|
||||
#include <algorithm> // count, find, min
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint32_t
|
||||
#include <cstring> // strlen
|
||||
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next
|
||||
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator
|
||||
#include <streambuf> // streambuf
|
||||
#include <string> // string, char_traits
|
||||
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
|
||||
@@ -297,6 +297,7 @@ class iterator_input_adapter
|
||||
return result;
|
||||
}
|
||||
|
||||
++eof_reads;
|
||||
return char_traits<char_type>::eof();
|
||||
}
|
||||
|
||||
@@ -306,6 +307,26 @@ class iterator_input_adapter
|
||||
return static_cast<std::size_t>(std::distance(begin, current));
|
||||
}
|
||||
|
||||
// number of get_character() calls so far, including those that returned
|
||||
// EOF; the lexer derives its position from it (see lexer::lazy_position)
|
||||
std::size_t get_read_count() const
|
||||
{
|
||||
return get_consumed_count() + eof_reads;
|
||||
}
|
||||
|
||||
// number of newlines in the consumed range [0, last_index); @a line_start
|
||||
// is set to the index after the last of them (0 if there is none); only
|
||||
// valid when supports_seek
|
||||
std::size_t count_consumed_lines(std::size_t last_index, std::size_t& line_start) const
|
||||
{
|
||||
const auto to = std::next(begin, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(last_index));
|
||||
const auto newline = char_traits<char_type>::to_char_type('\n');
|
||||
using reverse_it = std::reverse_iterator<IteratorType>;
|
||||
const auto last_newline = std::find(reverse_it(to), reverse_it(begin), newline);
|
||||
line_start = static_cast<std::size_t>(std::distance(begin, last_newline.base()));
|
||||
return static_cast<std::size_t>(std::count(begin, last_newline.base(), newline));
|
||||
}
|
||||
|
||||
// append the already-consumed characters in the half-open range
|
||||
// [first_index, last_index) to @a out; only valid when supports_seek
|
||||
template<typename ContainerType>
|
||||
@@ -422,6 +443,8 @@ class iterator_input_adapter
|
||||
IteratorType begin;
|
||||
IteratorType current;
|
||||
SentinelType end;
|
||||
/// number of get_character() calls that returned EOF
|
||||
std::size_t eof_reads = 0;
|
||||
|
||||
template<typename BaseInputAdapter, size_t T>
|
||||
friend struct wide_string_input_helper;
|
||||
|
||||
@@ -217,7 +217,7 @@ struct diagnostic_positions
|
||||
{
|
||||
// Lexer has read the first character of the container, so
|
||||
// subtract 1 from the position to get the correct start position.
|
||||
v.start_position = lexer->get_position() - 1;
|
||||
v.start_position = lexer->get_chars_read_total() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ struct diagnostic_positions
|
||||
if (lexer)
|
||||
{
|
||||
// Lexer's position is past the closing brace or bracket, so set that as the end position.
|
||||
v.end_position = lexer->get_position();
|
||||
v.end_position = lexer->get_chars_read_total();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ struct diagnostic_positions
|
||||
// Lexer has read past the current field value, so set the end position to the current position.
|
||||
// The start position will be set below based on the length of the string representation
|
||||
// of the value.
|
||||
v.end_position = lexer->get_position();
|
||||
v.end_position = lexer->get_chars_read_total();
|
||||
|
||||
switch (v.type())
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <utility> // move
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/detail/bit_ops.hpp>
|
||||
#include <nlohmann/detail/input/input_adapters.hpp>
|
||||
#include <nlohmann/detail/input/number_parse.hpp>
|
||||
#include <nlohmann/detail/input/position_t.hpp>
|
||||
@@ -186,6 +187,15 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
static constexpr bool lazy_token_string =
|
||||
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
||||
|
||||
/// whether the read position is derived from the input adapter on demand
|
||||
/// instead of being counted on every scanned character. Seekable adapters
|
||||
/// know how much input they consumed and can revisit it, so
|
||||
/// chars_read_total is the adapter's read count (minus a pending unget),
|
||||
/// and line and column are recomputed from the consumed input only when
|
||||
/// get_position() is called (errors). Counting per character kept two
|
||||
/// increments and a newline check in the hot path of every get().
|
||||
static constexpr bool lazy_position = lazy_token_string;
|
||||
|
||||
/// whether a simulated unget can be passed on to the input adapter, which
|
||||
/// then leaves the character in the input; see
|
||||
/// input_adapter_supports_lookahead
|
||||
@@ -195,7 +205,9 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
/// whether string scanning may bulk-consume runs of ordinary characters
|
||||
/// directly from a contiguous input buffer (SWAR fast path). This requires
|
||||
/// the token to be reconstructible lazily (lazy_token_string), so bypassing
|
||||
/// the per-character capture in get() cannot lose error diagnostics.
|
||||
/// the per-character capture in get() cannot lose error diagnostics. It
|
||||
/// also implies lazy_position, so the bulk paths need not advance the
|
||||
/// position counters.
|
||||
static constexpr bool bulk_scan =
|
||||
lazy_token_string
|
||||
&& input_adapter_supports_bulk_scan<InputAdapterType>(is_detected<detect_supports_bulk_scan, InputAdapterType> {});
|
||||
@@ -221,6 +233,41 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
// scan functions
|
||||
/////////////////////
|
||||
|
||||
/// contiguous input: try to decode the 4 hex digits following `\\u`
|
||||
/// directly from the input buffer via hex_codepoint(), instead of 4 calls
|
||||
/// to get(). On success, advances the adapter exactly as those 4 get()
|
||||
/// calls would (bulk_scan implies lazy_position, so there are no position
|
||||
/// counters to advance) and leaves @a current holding the last of
|
||||
/// the 4 digits, just as the last such get() would; the codepoint is
|
||||
/// written to @a out. Makes no state change and returns false for a
|
||||
/// pending unget, fewer than 4 remaining bytes, or any of the 4 bytes not
|
||||
/// being a hex digit, so the caller falls back unchanged to the
|
||||
/// per-character loop, which then reports the same diagnostic (stopping
|
||||
/// at the first invalid digit) as before this optimization.
|
||||
bool get_codepoint_bulk(std::true_type /*bulk*/, int& out)
|
||||
{
|
||||
if (next_unget || ia.bulk_remaining() < 4)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const char_type* const raw = ia.bulk_data();
|
||||
const int codepoint = hex_codepoint(reinterpret_cast<const unsigned char*>(raw));
|
||||
if (codepoint < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ia.bulk_skip(4);
|
||||
current = char_traits<char_type>::to_int_type(raw[3]);
|
||||
out = codepoint;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// streaming input: no bulk fast path
|
||||
bool get_codepoint_bulk(std::false_type /*bulk*/, int& /*out*/) const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief get codepoint from 4 hex characters following `\\u`
|
||||
|
||||
@@ -240,6 +287,14 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
{
|
||||
// this function only makes sense after reading `\u`
|
||||
JSON_ASSERT(current == 'u');
|
||||
|
||||
// contiguous input: decode all 4 hex digits directly from the buffer
|
||||
int fast_codepoint = 0;
|
||||
if (get_codepoint_bulk(std::integral_constant<bool, bulk_scan> {}, fast_codepoint))
|
||||
{
|
||||
return fast_codepoint;
|
||||
}
|
||||
|
||||
int codepoint = 0;
|
||||
|
||||
const auto factors = { 12u, 8u, 4u, 0u };
|
||||
@@ -331,10 +386,6 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
}
|
||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
||||
ia.bulk_skip(pos);
|
||||
// the run contains no newline (all bytes < 0x20 are treated as special),
|
||||
// so only the flat character counters advance
|
||||
position.chars_read_total += pos;
|
||||
position.chars_read_current_line += pos;
|
||||
}
|
||||
|
||||
/// streaming input: no bulk fast path
|
||||
@@ -1044,9 +1095,11 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
token_type::parse_error otherwise
|
||||
|
||||
@note The scanner is independent of the current locale: token_buffer
|
||||
always holds `.`. Only the std::strtod fallback of convert_number()
|
||||
depends on the locale, and it looks up the decimal point right
|
||||
before converting (see detail::convert_float_locale_aware()).
|
||||
always holds `.`. The conversion of float and double does not use
|
||||
the locale either. Only the std::strtold fallback of
|
||||
convert_number() for long double formats other than binary64
|
||||
depends on it, and it looks up the decimal point right before
|
||||
converting (see detail::convert_float_locale_aware()).
|
||||
*/
|
||||
token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated.
|
||||
{
|
||||
@@ -1059,7 +1112,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
|
||||
// offset just past the last mantissa byte in token_buffer (i.e. the
|
||||
// index of 'e'/'E', or the whole token when there is no exponent).
|
||||
// convert_number() uses it to count significant digits; npos means
|
||||
// convert_number() uses it to split the token; npos means
|
||||
// "not seen an exponent yet" and is resolved at scan_number_done
|
||||
std::size_t mantissa_end = std::string::npos;
|
||||
|
||||
@@ -1389,8 +1442,8 @@ scan_number_done:
|
||||
@param[in] mantissa_end offset just past the last mantissa byte in
|
||||
token_buffer (the index of 'e'/'E', or
|
||||
token_buffer.size() when there is no exponent);
|
||||
used to skip Clinger's fast path when it cannot
|
||||
possibly succeed - see detail::mantissa_fits_clinger()
|
||||
with decimal_point_position, it locates the parts
|
||||
of a float token without scanning it again
|
||||
*/
|
||||
token_type convert_number(token_type number_type, std::size_t mantissa_end)
|
||||
{
|
||||
@@ -1444,10 +1497,11 @@ scan_number_done:
|
||||
}
|
||||
|
||||
// this code is reached if we parse a floating-point number or if an
|
||||
// integer conversion above overflowed. Prefer std::from_chars
|
||||
// (Eisel-Lemire, locale-independent, correctly rounded) when available;
|
||||
// otherwise the exact Clinger fast path (double only); otherwise the
|
||||
// locale-aware strtof/strtod/strtold.
|
||||
// integer conversion above overflowed. float and double (and long
|
||||
// double where it is binary64) are converted by the library itself,
|
||||
// correctly rounded and independent of the locale; other long double
|
||||
// formats use std::from_chars when available, otherwise the
|
||||
// locale-aware strtold.
|
||||
if (convert_float_fast(num_begin, num_end, decimal_point_position, mantissa_end, value_float))
|
||||
{
|
||||
return token_type::value_float;
|
||||
@@ -1567,8 +1621,6 @@ scan_number_done:
|
||||
if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized))
|
||||
{
|
||||
ia.bulk_skip(len - 1);
|
||||
position.chars_read_total += (len - 1);
|
||||
position.chars_read_current_line += (len - 1);
|
||||
return integer_result;
|
||||
}
|
||||
// The value does not fit an integer, so this token converts as a
|
||||
@@ -1585,8 +1637,6 @@ scan_number_done:
|
||||
decimal_point_position = dot_index;
|
||||
|
||||
ia.bulk_skip(len - 1);
|
||||
position.chars_read_total += (len - 1);
|
||||
position.chars_read_current_line += (len - 1);
|
||||
|
||||
return convert_number(number_type, mantissa_end);
|
||||
}
|
||||
@@ -1637,7 +1687,7 @@ scan_number_done:
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
// the first character of the token has already been read, hence the -1
|
||||
token_start_position = position.chars_read_total - 1;
|
||||
token_start_position = get_chars_read_total() - 1;
|
||||
#endif
|
||||
|
||||
note_token_start(std::integral_constant<bool, lazy_token_string> {});
|
||||
@@ -1690,6 +1740,15 @@ scan_number_done:
|
||||
/// per-character position counters (line-count-on-'\n' bookkeeping is
|
||||
/// handled afterwards, in track_after_read(), once `current` is known)
|
||||
void advance_position() noexcept
|
||||
{
|
||||
advance_position_impl(std::integral_constant<bool, lazy_position> {});
|
||||
}
|
||||
|
||||
/// seekable adapter: the position is derived from the adapter on demand
|
||||
void advance_position_impl(std::true_type /*lazy*/) const noexcept {}
|
||||
|
||||
/// streaming adapter: count the character
|
||||
void advance_position_impl(std::false_type /*lazy*/) noexcept
|
||||
{
|
||||
++position.chars_read_total;
|
||||
++position.chars_read_current_line;
|
||||
@@ -1703,7 +1762,16 @@ scan_number_done:
|
||||
// seekable adapters reconstruct the token lazily on error (see
|
||||
// get_token_string), so the eager per-character copy is skipped
|
||||
capture_char(std::integral_constant<bool, lazy_token_string> {});
|
||||
track_newline(std::integral_constant<bool, lazy_position> {});
|
||||
return current;
|
||||
}
|
||||
|
||||
/// seekable adapter: lines are counted on demand in get_position()
|
||||
void track_newline(std::true_type /*lazy*/) const noexcept {}
|
||||
|
||||
/// streaming adapter: start a new line after reading a newline
|
||||
void track_newline(std::false_type /*lazy*/) noexcept
|
||||
{
|
||||
if (current == '\n')
|
||||
{
|
||||
++position.lines_read;
|
||||
@@ -1712,8 +1780,6 @@ scan_number_done:
|
||||
chars_read_before_newline = position.chars_read_current_line;
|
||||
position.chars_read_current_line = 0;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1757,12 +1823,22 @@ scan_number_done:
|
||||
We implement unget by setting variable next_unget to true. The input is not
|
||||
changed - we just simulate ungetting by modifying chars_read_total,
|
||||
chars_read_current_line, and token_string. The next call to get() will
|
||||
behave as if the unget character is read again.
|
||||
behave as if the unget character is read again. With lazy_position, the
|
||||
pending next_unget is all get_chars_read_total() needs.
|
||||
*/
|
||||
void unget()
|
||||
{
|
||||
next_unget = true;
|
||||
unget_position(std::integral_constant<bool, lazy_position> {});
|
||||
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
||||
}
|
||||
|
||||
/// seekable adapter: get_chars_read_total() accounts for next_unget
|
||||
void unget_position(std::true_type /*lazy*/) const noexcept {}
|
||||
|
||||
/// streaming adapter: step the counters back over the ungotten character
|
||||
void unget_position(std::false_type /*lazy*/) noexcept
|
||||
{
|
||||
--position.chars_read_total;
|
||||
|
||||
// in case we "unget" a newline, we have to also decrement the lines_read
|
||||
@@ -1785,8 +1861,6 @@ scan_number_done:
|
||||
{
|
||||
--position.chars_read_current_line;
|
||||
}
|
||||
|
||||
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
||||
}
|
||||
|
||||
/// adapter without lookahead: nothing to do (see release_lookahead)
|
||||
@@ -1857,10 +1931,21 @@ scan_number_done:
|
||||
// diagnostics
|
||||
/////////////////////
|
||||
|
||||
/// return position of last read token
|
||||
constexpr position_t get_position() const noexcept
|
||||
/*!
|
||||
@brief return the number of characters read so far
|
||||
|
||||
Unlike get_position(), this is O(1) for all adapters, so it may be called
|
||||
for every value (JSON_DIAGNOSTIC_POSITIONS).
|
||||
*/
|
||||
std::size_t get_chars_read_total() const noexcept
|
||||
{
|
||||
return position;
|
||||
return get_chars_read_total_impl(std::integral_constant<bool, lazy_position> {});
|
||||
}
|
||||
|
||||
/// return position of last read token
|
||||
position_t get_position() const
|
||||
{
|
||||
return get_position_impl(std::integral_constant<bool, lazy_position> {});
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1897,6 +1982,43 @@ scan_number_done:
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
/// seekable adapter: every get() either read from the adapter (including
|
||||
/// reads at EOF, which get() counts as characters as well) or replayed an
|
||||
/// ungotten character, so the count is the adapter's read count minus a
|
||||
/// still pending unget
|
||||
std::size_t get_chars_read_total_impl(std::true_type /*lazy*/) const noexcept
|
||||
{
|
||||
return ia.get_read_count() - (next_unget ? 1u : 0u);
|
||||
}
|
||||
|
||||
/// streaming adapter: the counter is maintained by get() and unget()
|
||||
std::size_t get_chars_read_total_impl(std::false_type /*lazy*/) const noexcept
|
||||
{
|
||||
return position.chars_read_total;
|
||||
}
|
||||
|
||||
/// seekable adapter: count the lines in the consumed input; O(input read
|
||||
/// so far), but only called on errors
|
||||
position_t get_position_impl(std::true_type /*lazy*/) const
|
||||
{
|
||||
position_t result;
|
||||
result.chars_read_total = get_chars_read_total();
|
||||
// reads at EOF count as characters but are not part of the input
|
||||
const std::size_t consumed = (std::min)(result.chars_read_total, ia.get_consumed_count());
|
||||
std::size_t line_start = 0;
|
||||
result.lines_read = ia.count_consumed_lines(consumed, line_start);
|
||||
result.chars_read_current_line = result.chars_read_total - line_start;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// streaming adapter: the position is maintained by get() and unget()
|
||||
position_t get_position_impl(std::false_type /*lazy*/) const noexcept
|
||||
{
|
||||
return position;
|
||||
}
|
||||
|
||||
public:
|
||||
/// seekable adapter: rebuild the last read token from the input on demand
|
||||
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const
|
||||
{
|
||||
@@ -2004,15 +2126,57 @@ scan_number_done:
|
||||
// into one that reloads the pointer from memory every character
|
||||
do
|
||||
{
|
||||
if (current == '\n')
|
||||
{
|
||||
skip_indentation(std::integral_constant<bool, bulk_scan> {});
|
||||
}
|
||||
get_ignoring_pending_unget();
|
||||
}
|
||||
while (current_is_whitespace());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief contiguous input: skip the space indentation that follows a newline
|
||||
8 bytes at a time
|
||||
|
||||
Pretty-printed JSON indents every line with a run of spaces, which the loop
|
||||
in skip_whitespace() would otherwise read one character at a time. The
|
||||
spaces are skipped 8 bytes at a time; the last 7 bytes of the input and any
|
||||
other whitespace are left to that loop. Skipping bypasses get(), which is
|
||||
fine because bulk_scan implies lazy_position: the position is derived from
|
||||
the adapter, not counted per character.
|
||||
*/
|
||||
void skip_indentation(std::true_type /*bulk*/)
|
||||
{
|
||||
JSON_ASSERT(!next_unget);
|
||||
const std::size_t remaining = ia.bulk_remaining();
|
||||
if (remaining < 8)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const char* const data = reinterpret_cast<const char*>(ia.bulk_data());
|
||||
std::size_t pos = 0;
|
||||
for (; pos + 8 <= remaining; pos += 8)
|
||||
{
|
||||
// little-endian, so the first byte is the lowest
|
||||
const std::uint64_t non_space = read_eight_bytes(data + pos) ^ 0x2020202020202020ull;
|
||||
if (non_space != 0)
|
||||
{
|
||||
// skip the spaces before the first other byte of this word
|
||||
pos += static_cast<std::size_t>(count_trailing_zeros(non_space)) / 8;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ia.bulk_skip(pos);
|
||||
}
|
||||
|
||||
/// streaming input: no bulk fast path
|
||||
void skip_indentation(std::false_type /*bulk*/) const noexcept {}
|
||||
|
||||
token_type scan()
|
||||
{
|
||||
// initially, skip the BOM
|
||||
if (position.chars_read_total == 0 && !skip_bom())
|
||||
if (get_chars_read_total() == 0 && !skip_bom())
|
||||
{
|
||||
error_message = "invalid BOM; must be 0xEF 0xBB 0xBF if given";
|
||||
return token_type::parse_error;
|
||||
@@ -2039,7 +2203,7 @@ scan_number_done:
|
||||
token_type scan_expecting(token_type expected_type)
|
||||
{
|
||||
JSON_ASSERT(expected_type == token_type::name_separator || expected_type == token_type::value_separator);
|
||||
JSON_ASSERT(position.chars_read_total > 0);
|
||||
JSON_ASSERT(get_chars_read_total() > 0);
|
||||
const char_int_type expected_char = static_cast<unsigned char>((expected_type == token_type::name_separator) ? ':' : ',');
|
||||
skip_whitespace();
|
||||
if (JSON_HEDLEY_LIKELY(current == expected_char))
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -370,7 +370,7 @@ class parser
|
||||
}
|
||||
case token_type::end_of_input:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_position().chars_read_total == 1))
|
||||
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_chars_read_total() == 1))
|
||||
{
|
||||
return syntax_error(*sax, "attempting to parse an empty input; check that your input string or stream contains the expected JSON");
|
||||
}
|
||||
@@ -520,8 +520,10 @@ class parser
|
||||
{
|
||||
// MSVC 2015 reports C4100 (unreferenced parameter) when SAX::parse_error is static
|
||||
static_cast<void>(sax);
|
||||
return sax.parse_error(m_lexer.get_position(), m_lexer.get_token_string(),
|
||||
parse_error::create(exception_id::syntax_error, m_lexer.get_position(), message, nullptr));
|
||||
// computing the position is O(input read so far) for seekable input
|
||||
const position_t position = m_lexer.get_position();
|
||||
return sax.parse_error(position, m_lexer.get_token_string(),
|
||||
parse_error::create(exception_id::syntax_error, position, message, nullptr));
|
||||
}
|
||||
|
||||
std::string exception_message(const token_type expected, const std::string& context)
|
||||
|
||||
@@ -22,6 +22,10 @@ namespace detail
|
||||
constexpr std::int64_t pow5_128_smallest_power = -342;
|
||||
constexpr std::int64_t pow5_128_largest_power = 308;
|
||||
|
||||
// every entry of pow5_128() holds two 64-bit halves of 5^q, one per covered power of 5
|
||||
static_assert((pow5_128_largest_power - pow5_128_smallest_power + 1) * 2 == 1302,
|
||||
"pow5_128_smallest_power/pow5_128_largest_power must match the size of the pow5_128() table");
|
||||
|
||||
/*!
|
||||
@brief 128-bit approximations of 5^q for q in [-342, 308]
|
||||
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint64_t
|
||||
#include <cstdint> // uint64_t, uint8_t
|
||||
#include <cstring> // memcpy
|
||||
|
||||
#include <nlohmann/detail/bit_ops.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
// Optional SIMD backend for bulk UTF-8 validation. This is an opt-in external
|
||||
@@ -69,18 +71,12 @@ inline std::size_t find_string_special(const unsigned char* data, std::size_t n)
|
||||
std::size_t i = 0;
|
||||
for (; i + 8 <= n; i += 8)
|
||||
{
|
||||
std::uint64_t word = 0;
|
||||
std::memcpy(&word, data + i, sizeof(word));
|
||||
if (swar_string_special(word) != 0)
|
||||
const std::uint64_t special = swar_string_special(read_eight_bytes(data + i));
|
||||
if (special != 0)
|
||||
{
|
||||
// a special byte is in this word; locate it (endian-agnostic)
|
||||
for (std::size_t j = 0; j < 8; ++j)
|
||||
{
|
||||
if (is_string_special(data[i + j]))
|
||||
{
|
||||
return i + j;
|
||||
}
|
||||
}
|
||||
// the lowest flagged byte is the first special one: the borrows of
|
||||
// the subtractions can only flag bytes above a true hit
|
||||
return i + (static_cast<std::size_t>(count_trailing_zeros(special)) / 8);
|
||||
}
|
||||
}
|
||||
for (; i < n; ++i)
|
||||
@@ -114,8 +110,7 @@ inline std::size_t find_ascii_copyable_run(const unsigned char* data, std::size_
|
||||
std::size_t i = 0;
|
||||
for (; i + 8 <= n; i += 8)
|
||||
{
|
||||
std::uint64_t v = 0;
|
||||
std::memcpy(&v, data + i, sizeof(v));
|
||||
const std::uint64_t v = read_eight_bytes(data + i);
|
||||
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"' (0x22)
|
||||
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\' (0x5C)
|
||||
const std::uint64_t d = v ^ 0x7F7F7F7F7F7F7F7Full; // DEL (0x7F)
|
||||
@@ -126,7 +121,9 @@ inline std::size_t find_ascii_copyable_run(const unsigned char* data, std::size_
|
||||
| (v & high); // >= 0x80
|
||||
if (stop != 0)
|
||||
{
|
||||
break;
|
||||
// the lowest flagged byte is the first one to stop at (see
|
||||
// find_string_special())
|
||||
return i + (static_cast<std::size_t>(count_trailing_zeros(stop)) / 8);
|
||||
}
|
||||
}
|
||||
for (; i < n; ++i)
|
||||
@@ -253,12 +250,18 @@ inline std::size_t scalar_string_bulk_run(const unsigned char* data, std::size_t
|
||||
{
|
||||
break; // end of buffer, or a quote/escape/control byte
|
||||
}
|
||||
const std::size_t seq = validate_one_utf8(data + pos, n - pos);
|
||||
if (seq == 0)
|
||||
// a run of multi-byte sequences (e.g. CJK text) is validated sequence
|
||||
// by sequence without searching for the next special byte in between
|
||||
do
|
||||
{
|
||||
break; // ill-formed or truncated: let the byte path diagnose it
|
||||
const std::size_t seq = validate_one_utf8(data + pos, n - pos);
|
||||
if (seq == 0)
|
||||
{
|
||||
return pos; // ill-formed or truncated: let the byte path diagnose it
|
||||
}
|
||||
pos += seq;
|
||||
}
|
||||
pos += seq;
|
||||
while (pos < n && data[pos] >= 0x80u);
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
@@ -273,8 +276,7 @@ inline std::size_t find_string_delimiter(const unsigned char* data, std::size_t
|
||||
std::size_t i = 0;
|
||||
for (; i + 8 <= n; i += 8)
|
||||
{
|
||||
std::uint64_t v = 0;
|
||||
std::memcpy(&v, data + i, sizeof(v));
|
||||
const std::uint64_t v = read_eight_bytes(data + i);
|
||||
const std::uint64_t q = v ^ 0x2222222222222222ull;
|
||||
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull;
|
||||
const std::uint64_t hit = ((q - ones) & ~q & high)
|
||||
@@ -282,14 +284,8 @@ inline std::size_t find_string_delimiter(const unsigned char* data, std::size_t
|
||||
| ((v - 0x2020202020202020ull) & ~v & high);
|
||||
if (hit != 0)
|
||||
{
|
||||
for (std::size_t j = 0; j < 8; ++j)
|
||||
{
|
||||
const unsigned char c = data[i + j];
|
||||
if (c == '\"' || c == '\\' || c < 0x20u)
|
||||
{
|
||||
return i + j;
|
||||
}
|
||||
}
|
||||
// the lowest flagged byte is the first delimiter (see find_string_special())
|
||||
return i + (static_cast<std::size_t>(count_trailing_zeros(hit)) / 8);
|
||||
}
|
||||
}
|
||||
for (; i < n; ++i)
|
||||
@@ -320,5 +316,50 @@ inline std::size_t string_bulk_run(const unsigned char* data, std::size_t n) noe
|
||||
return scalar_string_bulk_run(data, n);
|
||||
}
|
||||
|
||||
// Decode the 4 hex digits at [data, data+4) - the digits following a `\u`
|
||||
// escape - into a codepoint 0x0000..0xFFFF via one table lookup per byte
|
||||
// (after yyjson's read_hex_u16), or return -1 if any of the 4 bytes is not a
|
||||
// hex digit ('0'..'9', 'A'..'F', 'a'..'f'). The caller must already have
|
||||
// checked that 4 bytes are available; used by lexer::get_codepoint()'s
|
||||
// contiguous fast path. On -1 it falls back to the byte-at-a-time loop, which
|
||||
// stops at the first invalid digit, so the reported error and position are
|
||||
// unaffected by this fast path.
|
||||
inline int hex_codepoint(const unsigned char* data) noexcept
|
||||
{
|
||||
static const std::array<std::uint8_t, 256> hex_digit_table = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
{
|
||||
{
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 00..0F
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 10..1F
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 20..2F
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 30..3F ('0'..'9')
|
||||
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 40..4F ('A'..'F')
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 50..5F
|
||||
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 60..6F ('a'..'f')
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 70..7F
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 80..8F
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 90..9F
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // A0..AF
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // B0..BF
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // C0..CF
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // D0..DF
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // E0..EF
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // F0..FF
|
||||
}
|
||||
};
|
||||
|
||||
const std::uint8_t d0 = hex_digit_table[data[0]];
|
||||
const std::uint8_t d1 = hex_digit_table[data[1]];
|
||||
const std::uint8_t d2 = hex_digit_table[data[2]];
|
||||
const std::uint8_t d3 = hex_digit_table[data[3]];
|
||||
// every valid digit is <= 0xF; the combined OR only exceeds it if at
|
||||
// least one of the four bytes was not a hex digit (looked up as 0xFF)
|
||||
if ((d0 | d1 | d2 | d3) > 0x0F)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return (d0 << 12) | (d1 << 8) | (d2 << 4) | d3;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -21,6 +21,8 @@
|
||||
#undef JSON_NO_UNIQUE_ADDRESS
|
||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#undef JSON_DTOA_SSE2
|
||||
#undef JSON_DTOA_NEON
|
||||
|
||||
#ifndef JSON_TEST_KEEP_MACROS
|
||||
#undef JSON_CATCH
|
||||
|
||||
@@ -1366,8 +1366,9 @@ class serializer
|
||||
/*!
|
||||
@brief dump an integer
|
||||
|
||||
Dump a given integer, appending it to @ref write_buffer. Works internally with
|
||||
@a number_buffer.
|
||||
Dump a given integer, appending it to @ref write_buffer (directly: copying
|
||||
the digits from another buffer right after writing them waits until the
|
||||
stores are done).
|
||||
|
||||
@param[in] x integer number (signed or unsigned) to dump
|
||||
@tparam NumberType either @a number_integer_t or @a number_unsigned_t
|
||||
@@ -1402,33 +1403,57 @@ class serializer
|
||||
return;
|
||||
}
|
||||
|
||||
// use a pointer to fill the buffer
|
||||
auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto)
|
||||
// use a pointer to fill the buffer (room for as much as number_buffer holds)
|
||||
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + number_buffer.size() > write_buffer.size()))
|
||||
{
|
||||
flush();
|
||||
}
|
||||
auto* buffer_ptr = write_buffer.data() + write_buffer_pos;
|
||||
|
||||
number_unsigned_t abs_value;
|
||||
|
||||
unsigned int n_chars{};
|
||||
// one byte for the minus sign
|
||||
unsigned int n_chars = 0;
|
||||
|
||||
if (is_negative_number(x))
|
||||
{
|
||||
*buffer_ptr = '-';
|
||||
abs_value = remove_sign(static_cast<number_integer_t>(x));
|
||||
|
||||
// account one more byte for the minus sign
|
||||
n_chars = 1 + count_digits(abs_value);
|
||||
n_chars = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
abs_value = static_cast<number_unsigned_t>(x);
|
||||
n_chars = count_digits(abs_value);
|
||||
}
|
||||
|
||||
// up to 16 digits: eight at a time (as the digits of floats), written
|
||||
// without leading zeros
|
||||
if (abs_value < 10000000000000000u)
|
||||
{
|
||||
const std::uint64_t value = abs_value;
|
||||
const std::uint64_t upper = value / 100000000u;
|
||||
const std::uint64_t first = dtoa_impl::eight_digit_bytes(upper != 0 ? upper : value);
|
||||
const auto leading = static_cast<unsigned>(count_leading_zeros(first) / 8); // (first is not 0)
|
||||
char* const p = buffer_ptr + n_chars;
|
||||
dtoa_impl::store_msb_first(p, (first << (8 * leading)) + 0x3030303030303030u);
|
||||
n_chars += 8 - leading;
|
||||
if (upper != 0)
|
||||
{
|
||||
dtoa_impl::store_msb_first(p + 8 - leading, dtoa_impl::eight_digit_bytes(value - (upper * 100000000u)) + 0x3030303030303030u);
|
||||
n_chars += 8;
|
||||
}
|
||||
write_buffer_pos += n_chars;
|
||||
return;
|
||||
}
|
||||
|
||||
n_chars += count_digits(abs_value);
|
||||
|
||||
// spare 1 byte for '\0'
|
||||
JSON_ASSERT(n_chars < number_buffer.size() - 1);
|
||||
|
||||
// jump to the end to generate the string from backward,
|
||||
// so we later avoid reversing the result
|
||||
buffer_ptr += static_cast<typename decltype(number_buffer)::difference_type>(n_chars);
|
||||
buffer_ptr += n_chars;
|
||||
|
||||
// Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu
|
||||
// See: https://www.youtube.com/watch?v=o4-CwDo2zpg
|
||||
@@ -1451,14 +1476,13 @@ class serializer
|
||||
*(--buffer_ptr) = static_cast<char>('0' + abs_value);
|
||||
}
|
||||
|
||||
put_buffer(number_buffer, n_chars);
|
||||
write_buffer_pos += n_chars;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief dump a floating-point number
|
||||
|
||||
Dump a given floating-point number, appending it to @ref write_buffer. Works internally
|
||||
with @a number_buffer.
|
||||
Dump a given floating-point number, appending it to @ref write_buffer.
|
||||
|
||||
@param[in] x floating-point number to dump
|
||||
*/
|
||||
@@ -1485,10 +1509,15 @@ class serializer
|
||||
|
||||
void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
|
||||
{
|
||||
auto* begin = number_buffer.data();
|
||||
// directly into the write buffer: copying the text from number_buffer
|
||||
// right after to_chars() wrote it waits until its stores are done
|
||||
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + number_buffer.size() > write_buffer.size()))
|
||||
{
|
||||
flush();
|
||||
}
|
||||
auto* begin = write_buffer.data() + write_buffer_pos;
|
||||
auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x);
|
||||
|
||||
put_buffer(number_buffer, static_cast<std::size_t>(end - begin));
|
||||
write_buffer_pos += static_cast<std::size_t>(end - begin);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NON_NULL(1)
|
||||
|
||||
+181
-6
@@ -42,9 +42,14 @@
|
||||
// each of these, not only the forward declarations from json_fwd.hpp, so IWYU's suggestion to
|
||||
// drop them (nothing in this file otherwise names the type) would break every downstream
|
||||
// translation unit that relies on basic_json<>'s defaults actually being usable.
|
||||
#include <nlohmann/adl_serializer.hpp> // IWYU pragma: keep
|
||||
// export: client code names what these headers declare (nlohmann::json and ordered_json from
|
||||
// json_fwd.hpp, adl_serializer for specializations, json_ref behind brace initialization, the
|
||||
// NLOHMANN_DEFINE_* and NLOHMANN_JSON_NAMESPACE_* macros, and the literals at the end of this file), and include analyzers must
|
||||
// attribute it to this header, not tell clients to include the internal ones (issue #5812).
|
||||
// tests/iwyu_consumer checks this.
|
||||
#include <nlohmann/adl_serializer.hpp> // IWYU pragma: export
|
||||
#include <nlohmann/byte_container_with_subtype.hpp>
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/abi_macros.hpp> // IWYU pragma: export
|
||||
#include <nlohmann/detail/conversions/from_json.hpp> // IWYU pragma: keep
|
||||
#include <nlohmann/detail/conversions/to_json.hpp> // IWYU pragma: keep
|
||||
#include <nlohmann/detail/exceptions.hpp>
|
||||
@@ -61,8 +66,8 @@
|
||||
#include <nlohmann/detail/iterators/primitive_iterator.hpp>
|
||||
#include <nlohmann/detail/json_custom_base_class.hpp>
|
||||
#include <nlohmann/detail/json_pointer.hpp>
|
||||
#include <nlohmann/detail/json_ref.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/json_ref.hpp> // IWYU pragma: export
|
||||
#include <nlohmann/detail/macro_scope.hpp> // IWYU pragma: export
|
||||
#include <nlohmann/detail/meta/detected.hpp>
|
||||
#include <nlohmann/detail/string_concat.hpp>
|
||||
#include <nlohmann/detail/string_escape.hpp>
|
||||
@@ -74,7 +79,7 @@
|
||||
#include <nlohmann/detail/output/serializer.hpp>
|
||||
#include <nlohmann/detail/recursion_depth_limit.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
#include <nlohmann/json_fwd.hpp> // IWYU pragma: export
|
||||
#include <nlohmann/ordered_map.hpp> // IWYU pragma: keep
|
||||
#include <nlohmann/thirdparty/hedley/hedley.hpp>
|
||||
|
||||
@@ -7449,6 +7454,176 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
/// @brief creates a diff as a JSON Merge Patch
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/merge_diff/
|
||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||
static basic_json merge_diff(const basic_json& source, const basic_json& target)
|
||||
{
|
||||
// unless both are objects, the patch is the target itself
|
||||
if (!source.is_object() || !target.is_object())
|
||||
{
|
||||
return target;
|
||||
}
|
||||
|
||||
return merge_diff_recursively(source, target, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
/// @brief two objects @ref merge_diff_iteratively is diffing
|
||||
struct merge_diff_frame
|
||||
{
|
||||
merge_diff_frame(const basic_json* source_, const basic_json* target_)
|
||||
: source(source_), target(target_), member(source_->cbegin()), patch(value_t::object)
|
||||
{}
|
||||
|
||||
// declared for GCC's -Weffc++, as in diff_frame
|
||||
merge_diff_frame(const merge_diff_frame&) = default;
|
||||
merge_diff_frame(merge_diff_frame&&) = default;
|
||||
merge_diff_frame& operator=(const merge_diff_frame&) = default;
|
||||
merge_diff_frame& operator=(merge_diff_frame&&) = default;
|
||||
~merge_diff_frame() = default;
|
||||
|
||||
/// the objects being diffed
|
||||
const basic_json* source;
|
||||
const basic_json* target;
|
||||
/// the member of source being diffed; advanced once it is done
|
||||
const_iterator member;
|
||||
/// the patch for the members of source diffed so far
|
||||
basic_json patch;
|
||||
};
|
||||
|
||||
/// @brief add the members of object @a target that @a source does not
|
||||
/// have to the merge patch @a result
|
||||
static void merge_diff_added_members(basic_json& result, const basic_json& source, const basic_json& target)
|
||||
{
|
||||
for (auto it = target.begin(); it != target.end(); ++it)
|
||||
{
|
||||
auto itf = source.find(it.key());
|
||||
if (itf == source.end())
|
||||
{
|
||||
result[it.key()] = it.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief @ref merge_diff of two objects at nesting level @a depth
|
||||
|
||||
Diffing two nested objects calls this function again, once per nesting
|
||||
level, so values nested deeply enough would exhaust the call stack and
|
||||
terminate the process. The descent is bounded here: once @ref
|
||||
detail::recursion_depth_limit levels have been entered, @ref
|
||||
merge_diff_iteratively diffs what is left without the call stack.
|
||||
*/
|
||||
static basic_json merge_diff_recursively(const basic_json& source, const basic_json& target, const std::size_t depth)
|
||||
{
|
||||
JSON_ASSERT(source.is_object() && target.is_object());
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||
{
|
||||
return merge_diff_iteratively(source, target);
|
||||
}
|
||||
|
||||
basic_json result(value_t::object);
|
||||
|
||||
for (auto it = source.begin(); it != source.end(); ++it)
|
||||
{
|
||||
auto itf = target.find(it.key());
|
||||
if (itf != target.end())
|
||||
{
|
||||
if (it.value().is_object() && itf.value().is_object())
|
||||
{
|
||||
auto diff = merge_diff_recursively(it.value(), itf.value(), depth + 1);
|
||||
if (!diff.empty())
|
||||
{
|
||||
result[it.key()] = std::move(diff);
|
||||
}
|
||||
}
|
||||
else if (it.value() != itf.value())
|
||||
{
|
||||
result[it.key()] = itf.value();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result[it.key()] = value_t::null;
|
||||
}
|
||||
}
|
||||
|
||||
merge_diff_added_members(result, source, target);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief @ref merge_diff of two objects without the call stack
|
||||
|
||||
Produces the same patch as @ref merge_diff_recursively, keeping the objects
|
||||
being diffed on an explicit stack instead of descending into them. Two
|
||||
nested objects are still diffed completely before the next member, so the
|
||||
patch has its members in the same order as in the recursive version. Only
|
||||
reached for objects nested more deeply than @ref
|
||||
detail::recursion_depth_limit.
|
||||
*/
|
||||
static basic_json merge_diff_iteratively(const basic_json& source, const basic_json& target)
|
||||
{
|
||||
// the patch of source and target, set once their frame is popped
|
||||
basic_json result;
|
||||
std::vector<merge_diff_frame> stack;
|
||||
stack.emplace_back(&source, &target);
|
||||
while (!stack.empty())
|
||||
{
|
||||
// the frame's members are copied out and it is changed through
|
||||
// stack.back(): pushing a frame for nested objects or popping
|
||||
// this one would invalidate a reference to it
|
||||
const basic_json* const s = stack.back().source;
|
||||
const basic_json* const t = stack.back().target;
|
||||
const const_iterator it = stack.back().member;
|
||||
|
||||
if (it != s->cend())
|
||||
{
|
||||
const const_iterator itf = t->find(it.key());
|
||||
if (itf != t->cend())
|
||||
{
|
||||
if (it.value().is_object() && itf.value().is_object())
|
||||
{
|
||||
// member is advanced once their patch is under its key
|
||||
stack.emplace_back(&it.value(), &itf.value());
|
||||
continue;
|
||||
}
|
||||
if (it.value() != itf.value())
|
||||
{
|
||||
stack.back().patch[it.key()] = itf.value();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.back().patch[it.key()] = value_t::null;
|
||||
}
|
||||
++stack.back().member;
|
||||
continue;
|
||||
}
|
||||
|
||||
merge_diff_added_members(stack.back().patch, *s, *t);
|
||||
|
||||
// these objects are done: continue with the ones they are in
|
||||
basic_json patch = std::move(stack.back().patch);
|
||||
stack.pop_back();
|
||||
if (stack.empty())
|
||||
{
|
||||
result = std::move(patch);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!patch.empty())
|
||||
{
|
||||
stack.back().patch[stack.back().member.key()] = std::move(patch);
|
||||
}
|
||||
++stack.back().member;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
@@ -7625,7 +7800,7 @@ struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-c
|
||||
// where needed.
|
||||
#ifndef JSON_NO_AUTOMATIC_UDLS
|
||||
// NOLINTNEXTLINE(misc-header-include-cycle): json_literals.hpp includes this header
|
||||
#include <nlohmann/json_literals.hpp>
|
||||
#include <nlohmann/json_literals.hpp> // IWYU pragma: export
|
||||
#endif
|
||||
|
||||
#endif // INCLUDE_NLOHMANN_JSON_HPP_
|
||||
Reference in new issue
Block a user