mirror of
https://github.com/nlohmann/json.git
synced 2026-10-29 00:55:44 +00:00
* json_view: source positions in materialize() with JSON_DIAGNOSTIC_POSITIONS With JSON_DIAGNOSTIC_POSITIONS, materialize() now gives every value the start_pos()/end_pos() that basic_json::parse() would give it, so exceptions about materialized values name the same bytes (#5864). - json_sax_dom_parser takes the lexer type as a defaulted template parameter; materialize() passes position_source, which reports the positions it derives from the index. diagnostic_positions treats npos as "no position" and keeps ranges all-or-nothing. - Strings with escapes keep the offset of their opening quote plus one in `next` (only with the macro); materialize() scans from there to the closing quote. The end of an array or object is found by scanning from the end of its last element over whitespace, comments and a trailing comma. - In editable documents, values written by edits, arrays and objects whose elements changed, and containers whose last element has no position get none. Images keep the flag is_new, and load() checks that `next` of a string is within the text. Nothing changes on the parse path without the macro. The view's own exceptions still carry no byte range, so the tests that compare them with those of materialized values skip it under the macro, as they skip the path under JSON_DIAGNOSTICS. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Indent comment in unit-json_view_from_json.cpp Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Address review: clearer position code and docs - position_source gets clear(); set_scalar() returns early for new values, switches on node_kind, and uses no local copies; the one position_source per materialize() call is documented - no npos + 1 for containers without a position; a comment on why the end of a container is reported as its closing bracket - less duplication under #if in materialize() and the builder - diagnostic_positions sets npos explicitly - docs: positions of basic_json values after changes, positions in images depend on how the document was parsed, when next is set, and the cost of positions while parsing Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Share the comment scanners of the view builder with materialize() skip_line_comment() and find_block_comment_end() in scan.hpp are used by the builder's comment() and by position_source::container_end(), which had its own copy. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Address review: if/else for container positions, clearer next field - the opening bracket of a container sets positions with an if/else instead of a ternary, and the frame takes its start from positions - the closing bracket of a container is found in nested if/else; the parent's last_end is taken from positions, so it is npos whenever no range is set - architecture.md: the next field reads "or 0 if it was not; all other values: 0" instead of "else 0; otherwise 0" Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix GCC unused-result and clang-tidy raw-string findings in unit-json_view_positions.cpp Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
247 lines
8.4 KiB
C++
247 lines
8.4 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-FileCopyrightText: 2020 YaoYuan <https://github.com/ibireme/yyjson>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#pragma once
|
|
|
|
#include <array> // array
|
|
#include <cstddef> // size_t
|
|
#include <cstdint> // uint8_t, uint16_t, uint64_t
|
|
#include <cstring> // memcpy
|
|
|
|
#include <nlohmann/json.hpp>
|
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
|
#include <nlohmann/detail/view/simd.hpp>
|
|
|
|
// Scanning primitives of the view's parser. The unrolled checks at fixed
|
|
// offsets follow yyjson (https://github.com/ibireme/yyjson, MIT license): the
|
|
// loads do not depend on each other, so the CPU can run ahead. Words are read
|
|
// with read_eight_bytes(), so nothing here depends on the byte order.
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
namespace detail
|
|
{
|
|
namespace view
|
|
{
|
|
|
|
/// 1 for bytes that may appear verbatim in a string: 0x20..0x7F except '"' and '\\'
|
|
inline const std::uint8_t* string_plain() noexcept
|
|
{
|
|
static const std::array<std::uint8_t, 256> table =
|
|
{
|
|
{
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00..0x1F
|
|
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20..0x3F ('"')
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 0x40..0x5F ('\\')
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60..0x7F
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80..0x9F
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xA0..0xBF
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xC0..0xDF
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xE0..0xFF
|
|
}
|
|
};
|
|
return table.data();
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE bool is_digit(unsigned char c) noexcept
|
|
{
|
|
return static_cast<unsigned char>(c - '0') <= 9;
|
|
}
|
|
|
|
/// two bytes as they are in memory (only compared with byte-symmetric patterns)
|
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint16_t load16(const unsigned char* p) noexcept
|
|
{
|
|
std::uint16_t w = 0;
|
|
std::memcpy(&w, p, 2);
|
|
return w;
|
|
}
|
|
|
|
/// Advance over plain string bytes and well-formed UTF-8. Stops at a quote,
|
|
/// a backslash, a control character, ill-formed UTF-8, or the end. The first
|
|
/// bytes are checked one by one, so that the position advances by constants
|
|
/// in predicted branches: 16 for keys, whose lengths repeat from record to
|
|
/// record, and 8 for string values (Value) where a vector loop follows, as
|
|
/// their lengths vary more. Longer runs continue 16 bytes at a time with NEON
|
|
/// or SSE2, else eight bytes at a time. With SSE2, the run is checked 16 bytes
|
|
/// at a time from its first byte instead: on x86-64, one compare that finds
|
|
/// the end of most keys and short values is faster than a branch per byte (on
|
|
/// AArch64, where a NEON mask costs more and branches predict well, slower).
|
|
template<bool Value = false>
|
|
NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* scan_string_run(const unsigned char* p, const unsigned char* e) noexcept
|
|
{
|
|
const std::uint8_t* plain = string_plain();
|
|
for (;;)
|
|
{
|
|
#if NLOHMANN_VIEW_SSE2
|
|
p = vector_plain_run(p, e);
|
|
#else
|
|
if (e - p >= 16)
|
|
{
|
|
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { p += (i); goto stop; }
|
|
NLOHMANN_VIEW_REPEAT_FIRST_8(NLOHMANN_VIEW_STEP)
|
|
#if NLOHMANN_VIEW_VECTOR
|
|
if (!Value) // (a plain `!Value || !NLOHMANN_VIEW_VECTOR` is a constant condition for MSVC, C4127)
|
|
#endif
|
|
{
|
|
NLOHMANN_VIEW_REPEAT_SECOND_8(NLOHMANN_VIEW_STEP)
|
|
p += 8;
|
|
}
|
|
#undef NLOHMANN_VIEW_STEP
|
|
p += 8;
|
|
#if NLOHMANN_VIEW_VECTOR
|
|
p = vector_plain_run(p, e);
|
|
if (p != e && plain[*p] == 0)
|
|
{
|
|
goto stop;
|
|
}
|
|
#else
|
|
while (e - p >= 8)
|
|
{
|
|
const std::uint64_t special = swar_string_special(read_eight_bytes(p));
|
|
if (special != 0)
|
|
{
|
|
p += count_trailing_zeros(special) / 8;
|
|
goto stop;
|
|
}
|
|
p += 8;
|
|
}
|
|
#endif
|
|
continue;
|
|
}
|
|
#endif
|
|
while (p != e && plain[*p] != 0)
|
|
{
|
|
++p;
|
|
}
|
|
if (p == e)
|
|
{
|
|
return p;
|
|
}
|
|
#if !NLOHMANN_VIEW_SSE2
|
|
stop:
|
|
#endif
|
|
if (*p < 0x80)
|
|
{
|
|
return p; // quote, backslash, or control character
|
|
}
|
|
#if NLOHMANN_VIEW_VECTOR_UTF8
|
|
#if NLOHMANN_VIEW_SSSE3_DISPATCH
|
|
if (NLOHMANN_VIEW_LIKELY(cpu_has_ssse3()))
|
|
#endif
|
|
{
|
|
// non-ASCII: the vector check, out of line
|
|
return scan_string_vector(p, e, plain);
|
|
}
|
|
#endif
|
|
#if !NLOHMANN_VIEW_VECTOR_UTF8 || NLOHMANN_VIEW_SSSE3_DISPATCH
|
|
// non-ASCII: a run of well-formed sequences (the library's check, so
|
|
// that exactly what json::parse accepts is accepted)
|
|
do
|
|
{
|
|
const std::size_t n = validate_one_utf8(p, static_cast<std::size_t>(e - p));
|
|
if (n == 0)
|
|
{
|
|
return p;
|
|
}
|
|
p += n;
|
|
}
|
|
while (p != e && *p >= 0x80);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/// advance over ASCII digits
|
|
NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_digits(const unsigned char* p, const unsigned char* e) noexcept
|
|
{
|
|
while (e - p >= 16)
|
|
{
|
|
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(is_digit(p[i]))) {} else { return p + (i); }
|
|
NLOHMANN_VIEW_REPEAT16(NLOHMANN_VIEW_STEP)
|
|
#undef NLOHMANN_VIEW_STEP
|
|
p += 16;
|
|
}
|
|
while (p != e && is_digit(*p))
|
|
{
|
|
++p;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/// advance over the text of a line comment (after its "//") to the line
|
|
/// break or the end (with NulIsEnd also a null byte, which is left for the
|
|
/// caller to see, as in parse())
|
|
template<bool NulIsEnd = false>
|
|
inline const unsigned char* skip_line_comment(const unsigned char* p, const unsigned char* e) noexcept
|
|
{
|
|
while (p != e && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0))
|
|
{
|
|
++p;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/// the "*/" that ends a block comment whose text starts at p (after its "/*");
|
|
/// if it is unterminated, the end (with NulIsEnd also a null byte), which is
|
|
/// where the caller reports the error
|
|
template<bool NulIsEnd = false>
|
|
inline const unsigned char* find_block_comment_end(const unsigned char* p, const unsigned char* e) noexcept
|
|
{
|
|
while (p != e && !(NulIsEnd && *p == 0) && !(*p == '*' && p + 1 != e && p[1] == '/'))
|
|
{
|
|
++p;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/// powers of ten up to 10^19 as integers
|
|
inline std::uint64_t int_pow10(unsigned k) noexcept
|
|
{
|
|
return ::nlohmann::detail::dtoa_impl::powers_of_ten()[k];
|
|
}
|
|
|
|
/// value of 0 < k < 8 digits at p in one step if [p, p + 8) lies below
|
|
/// limit, else one digit at a time (whole blocks of eight digits are read by
|
|
/// parse_upto19() directly)
|
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t parse_upto8(const unsigned char* p, unsigned k, const unsigned char* limit) noexcept
|
|
{
|
|
if (NLOHMANN_VIEW_LIKELY(limit - p >= 8))
|
|
{
|
|
// move the k digits to the top and pad the vacated low bytes with '0'
|
|
const unsigned shift = 8 * (8 - k);
|
|
return parse_eight_digits((read_eight_bytes(p) << shift) | (0x3030303030303030u >> (8 * k)));
|
|
}
|
|
std::uint64_t v = 0;
|
|
for (unsigned i = 0; i < k; ++i)
|
|
{
|
|
v = (v * 10) + static_cast<std::uint64_t>(p[i] - '0');
|
|
}
|
|
return v;
|
|
}
|
|
|
|
/// value of k <= 19 digits at p
|
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t parse_upto19(const unsigned char* p, unsigned k, const unsigned char* limit) noexcept
|
|
{
|
|
std::uint64_t w = 0;
|
|
while (k >= 8)
|
|
{
|
|
// (eight digits of the token: they lie below limit)
|
|
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
|
|
p += 8;
|
|
k -= 8;
|
|
}
|
|
if (k != 0)
|
|
{
|
|
w = (w * int_pow10(k)) + parse_upto8(p, k, limit);
|
|
}
|
|
return w;
|
|
}
|
|
|
|
} // namespace view
|
|
} // namespace detail
|
|
NLOHMANN_JSON_NAMESPACE_END
|