mirror of
https://github.com/nlohmann/json.git
synced 2026-09-30 14:05:18 +00:00
- the input dispatch takes byte ranges by const reference and reads the size once (which also settles a finding of the static analyzer); input adapters are taken by value - the classification of inputs keeps its nested conditional operators, a constant expression of C++11 (NOLINT) - the test's C arrays, fixed seed, and escaped literals are marked, as in the other tests Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2639 lines
90 KiB
C++
2639 lines
90 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
/****************************************************************************\
|
|
* Zero-copy, read-only view of a parsed JSON text. *
|
|
* *
|
|
* json_document::parse() builds a flat index of the values of a JSON text *
|
|
* (16 bytes per value) instead of a tree of basic_json values. Strings and *
|
|
* numbers stay in the source text; only strings with escapes are decoded, *
|
|
* into one buffer. json_view is a handle to one value of the document, with *
|
|
* the read-only part of the basic_json interface; materialize() turns a *
|
|
* subtree into the basic_json value that parse() would produce. *
|
|
* *
|
|
* The source text must outlive a document that borrows it (lvalue byte *
|
|
* containers, C strings); rvalue strings, streams, and other inputs are *
|
|
* owned by the document. *
|
|
\****************************************************************************/
|
|
|
|
#ifndef INCLUDE_NLOHMANN_JSON_VIEW_HPP_
|
|
#define INCLUDE_NLOHMANN_JSON_VIEW_HPP_
|
|
|
|
#include <cstddef> // size_t
|
|
#include <cstring> // memcpy, strlen
|
|
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
|
#include <memory> // unique_ptr
|
|
#include <string> // string
|
|
#include <type_traits> // enable_if, integral_constant, is_base_of, is_integral, is_same, remove_cv, remove_extent
|
|
#include <utility> // forward, move
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
// the view builds on internals of the library: both must be the same version
|
|
#if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0
|
|
#error "json_view.hpp requires json.hpp of the same version (3.12.0)"
|
|
#endif
|
|
|
|
// #include <nlohmann/detail/view/builder.hpp>
|
|
// __ _____ _____ _____
|
|
// __| | __| | | | 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
|
|
|
|
|
|
|
|
#include <algorithm> // max
|
|
#include <array> // array
|
|
#include <cstddef> // size_t, ptrdiff_t
|
|
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
|
|
#include <cstring> // memcmp, memcpy
|
|
#include <limits> // numeric_limits
|
|
#include <string> // string
|
|
#include <vector> // vector
|
|
|
|
// #include <nlohmann/json.hpp>
|
|
// #include <nlohmann/detail/view/document_data.hpp>
|
|
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
#include <array> // array
|
|
#include <cstddef> // size_t
|
|
#include <cstring> // memcpy
|
|
#include <new> // operator new, placement new
|
|
#include <string> // string
|
|
|
|
// #include <nlohmann/json.hpp>
|
|
// #include <nlohmann/detail/view/macro_scope.hpp>
|
|
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
// Macros of json_view.hpp and its detail headers. json.hpp undefines its own
|
|
// macros at its end (macro_unscope.hpp), so the view defines the few it needs
|
|
// under its own prefix; json_view.hpp undefines them all at its end
|
|
// (detail/view/macro_unscope.hpp). Configuration that json.hpp undefines is
|
|
// read from detail::abi_config instead.
|
|
|
|
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
|
|
#define NLOHMANN_VIEW_HAS_CPP_17 1
|
|
#else
|
|
#define NLOHMANN_VIEW_HAS_CPP_17 0
|
|
#endif
|
|
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
#define NLOHMANN_VIEW_LIKELY(x) __builtin_expect(!!(x), 1)
|
|
#define NLOHMANN_VIEW_UNLIKELY(x) __builtin_expect(!!(x), 0)
|
|
#define NLOHMANN_VIEW_ALWAYS_INLINE inline __attribute__((always_inline))
|
|
#define NLOHMANN_VIEW_NOINLINE __attribute__((noinline))
|
|
#elif defined(_MSC_VER)
|
|
#define NLOHMANN_VIEW_LIKELY(x) (x)
|
|
#define NLOHMANN_VIEW_UNLIKELY(x) (x)
|
|
#define NLOHMANN_VIEW_ALWAYS_INLINE __forceinline
|
|
#define NLOHMANN_VIEW_NOINLINE __declspec(noinline)
|
|
#else
|
|
#define NLOHMANN_VIEW_LIKELY(x) (x)
|
|
#define NLOHMANN_VIEW_UNLIKELY(x) (x)
|
|
#define NLOHMANN_VIEW_ALWAYS_INLINE inline
|
|
#define NLOHMANN_VIEW_NOINLINE
|
|
#endif
|
|
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
#define NLOHMANN_VIEW_NODISCARD __attribute__((warn_unused_result))
|
|
#elif defined(_MSC_VER)
|
|
#define NLOHMANN_VIEW_NODISCARD _Check_return_
|
|
#else
|
|
#define NLOHMANN_VIEW_NODISCARD
|
|
#endif
|
|
|
|
// exceptions as in json.hpp (JSON_NOEXCEPTION, JSON_THROW_USER)
|
|
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
|
#define NLOHMANN_VIEW_THROW(exception) throw exception
|
|
#else
|
|
#include <cstdlib>
|
|
// (the exception is built first, so that the arguments of the throwing
|
|
// helpers count as used; the program ends anyway)
|
|
#define NLOHMANN_VIEW_THROW(exception) (static_cast<void>(exception), std::abort())
|
|
#endif
|
|
#if defined(JSON_THROW_USER)
|
|
#undef NLOHMANN_VIEW_THROW
|
|
#define NLOHMANN_VIEW_THROW JSON_THROW_USER
|
|
#endif
|
|
|
|
// the parser stores a node's first word at once where the layout of `node` is
|
|
// known to be little-endian (MSVC targets are); elsewhere field by field
|
|
#if (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || defined(_MSC_VER)
|
|
#define NLOHMANN_VIEW_LITTLE_ENDIAN 1
|
|
#else
|
|
#define NLOHMANN_VIEW_LITTLE_ENDIAN 0
|
|
#endif
|
|
|
|
/// sixteen checks at fixed offsets 0..15
|
|
#define NLOHMANN_VIEW_REPEAT16(X) X(0) X(1) X(2) X(3) X(4) X(5) X(6) X(7) X(8) X(9) X(10) X(11) X(12) X(13) X(14) X(15)
|
|
|
|
// #include <nlohmann/detail/view/node.hpp>
|
|
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
#include <cstddef> // size_t
|
|
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
|
|
#include <cstring> // memcpy
|
|
|
|
// #include <nlohmann/json.hpp>
|
|
// #include <nlohmann/detail/view/macro_scope.hpp>
|
|
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
namespace detail
|
|
{
|
|
namespace view
|
|
{
|
|
|
|
// the node kinds are value_t values; the tests of is_container() and of the
|
|
// number kinds depend on this numbering
|
|
static_assert(static_cast<std::uint8_t>(value_t::null) == 0 && static_cast<std::uint8_t>(value_t::object) == 1
|
|
&& static_cast<std::uint8_t>(value_t::array) == 2 && static_cast<std::uint8_t>(value_t::string) == 3
|
|
&& static_cast<std::uint8_t>(value_t::boolean) == 4 && static_cast<std::uint8_t>(value_t::number_integer) == 5
|
|
&& static_cast<std::uint8_t>(value_t::number_unsigned) == 6 && static_cast<std::uint8_t>(value_t::number_float) == 7,
|
|
"the node format depends on the numbering of value_t");
|
|
|
|
/// node flags
|
|
struct node_flags
|
|
{
|
|
static constexpr std::uint8_t escaped = 1; ///< string payload lives in the decode arena, not the source
|
|
static constexpr std::uint8_t storage = 3; ///< mask: where a string or number token lives (index into document_data::base)
|
|
static constexpr std::uint8_t is_true = 4; ///< boolean value
|
|
};
|
|
|
|
/// One entry of the flat index, in document order. An object's members are
|
|
/// stored as key node followed by the value's subtree. Integers keep their
|
|
/// converted 64-bit value in the len/next bytes (the node after a scalar is
|
|
/// always the next one, and the token length follows from `extra`).
|
|
struct node
|
|
{
|
|
std::uint8_t kind; ///< value_t
|
|
std::uint8_t flags; ///< node_flags
|
|
std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; otherwise 0
|
|
std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if node_flags::escaped
|
|
std::uint32_t len; ///< string: decoded bytes; float: token bytes; array/object: element count
|
|
std::uint32_t next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence)
|
|
};
|
|
static_assert(sizeof(node) == 16, "node must stay 16 bytes");
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE bool is_container(const node& n) noexcept
|
|
{
|
|
return static_cast<unsigned>(n.kind) - 1u <= 1u;
|
|
}
|
|
|
|
/// the converted value of an integer node (stored in len/next)
|
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t integer_bits(const node& n) noexcept
|
|
{
|
|
std::uint64_t v = 0;
|
|
std::memcpy(&v, reinterpret_cast<const unsigned char*>(&n) + 8, 8); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
return v;
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE void set_integer_bits(node& n, std::uint64_t v) noexcept
|
|
{
|
|
std::memcpy(reinterpret_cast<unsigned char*>(&n) + 8, &v, 8); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
}
|
|
|
|
/// token length of a number node
|
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t number_length(const node& n) noexcept
|
|
{
|
|
return n.kind == static_cast<std::uint8_t>(value_t::number_float) ? n.len
|
|
: (n.extra & 0xFFu) + (n.kind == static_cast<std::uint8_t>(value_t::number_integer) ? 1u : 0u);
|
|
}
|
|
|
|
/// estimated number of nodes for an input of `size` bytes (one node per ~12
|
|
/// bytes covers typical documents without regrowth)
|
|
inline std::size_t estimate_nodes(std::size_t size) noexcept
|
|
{
|
|
return (size / 12) + 16;
|
|
}
|
|
|
|
/// estimated number of nodes for the input [src, src + size): pretty-printed
|
|
/// input (whitespace after the first byte) needs about a node per 12 bytes,
|
|
/// minified input up to one per 4 (yyjson tells the two apart the same way)
|
|
inline std::size_t estimate_nodes(const char* src, std::size_t size) noexcept
|
|
{
|
|
return size >= 2 && (src[1] == ' ' || src[1] == '\n' || src[1] == '\r' || src[1] == '\t') ? estimate_nodes(size) : (size / 4) + 16;
|
|
}
|
|
|
|
} // namespace view
|
|
} // namespace detail
|
|
NLOHMANN_JSON_NAMESPACE_END
|
|
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
namespace detail
|
|
{
|
|
namespace view
|
|
{
|
|
|
|
/// storage of a parsed document; heap-allocated (header and an initial node
|
|
/// array in one block) so that views survive moves of the owning document
|
|
struct document_data
|
|
{
|
|
const char* src = nullptr;
|
|
std::size_t size = 0;
|
|
node* tape = nullptr;
|
|
std::size_t tape_size = 0;
|
|
std::size_t tape_cap = 0;
|
|
node* inline_tape = nullptr; ///< node array allocated together with this header
|
|
std::size_t inline_cap = 0;
|
|
std::string arena{}; ///< decoded strings that contained escapes // NOLINT(readability-redundant-member-init)
|
|
std::string owned{}; ///< owned copy of the input, if any // NOLINT(readability-redundant-member-init)
|
|
std::array<const char*, 4> base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena (indexed by flags & node_flags::storage)
|
|
bool discarded = true;
|
|
|
|
/// one allocation for the header and room for `nodes` nodes; large
|
|
/// documents get a separate node array instead (so it can be trimmed)
|
|
static document_data* create(std::size_t nodes)
|
|
{
|
|
nodes = nodes <= 256 ? nodes : 0;
|
|
void* mem = ::operator new (sizeof(document_data) + (nodes * sizeof(node)));
|
|
auto* d = new (mem) document_data(); // NOLINT(cppcoreguidelines-owning-memory): owned by the returned pointer, freed by deleter
|
|
// (aligned: sizeof is a multiple of the alignment; through void*, as GCC's -Wcast-align wants)
|
|
d->inline_tape = static_cast<node*>(static_cast<void*>(static_cast<char*>(mem) + sizeof(document_data))); // NOLINT(bugprone-casting-through-void)
|
|
d->inline_cap = nodes;
|
|
d->tape = d->inline_tape;
|
|
d->tape_cap = nodes;
|
|
return d;
|
|
}
|
|
|
|
struct deleter
|
|
{
|
|
void operator()(document_data* d) const noexcept
|
|
{
|
|
d->~document_data();
|
|
::operator delete (d);
|
|
}
|
|
};
|
|
|
|
document_data() noexcept = default;
|
|
document_data(const document_data&) = delete;
|
|
document_data(document_data&&) = delete;
|
|
document_data& operator=(const document_data&) = delete;
|
|
document_data& operator=(document_data&&) = delete;
|
|
~document_data()
|
|
{
|
|
release();
|
|
}
|
|
|
|
void release() noexcept
|
|
{
|
|
if (tape != inline_tape)
|
|
{
|
|
::operator delete (tape);
|
|
}
|
|
tape = inline_tape;
|
|
tape_cap = inline_cap;
|
|
}
|
|
|
|
/// make room for n nodes; keeps the first tape_size nodes
|
|
void reserve(std::size_t n)
|
|
{
|
|
if (n <= tape_cap)
|
|
{
|
|
return;
|
|
}
|
|
node* fresh = static_cast<node*>(::operator new (n * sizeof(node)));
|
|
if (tape_size != 0)
|
|
{
|
|
std::memcpy(fresh, tape, tape_size * sizeof(node));
|
|
}
|
|
release();
|
|
tape = fresh;
|
|
tape_cap = n;
|
|
}
|
|
|
|
const char* str(const node& n) const noexcept
|
|
{
|
|
return base[n.flags & node_flags::storage] + n.off;
|
|
}
|
|
|
|
/// the node after n's subtree (containers span `next` nodes, scalars one)
|
|
static NLOHMANN_VIEW_ALWAYS_INLINE const node* after(const node* n) noexcept
|
|
{
|
|
return n + (is_container(*n) ? n->next : 1u);
|
|
}
|
|
|
|
/// first element (array) or first key (object) of a container
|
|
static NLOHMANN_VIEW_ALWAYS_INLINE const node* first_child(const node* n) noexcept
|
|
{
|
|
return n + 1;
|
|
}
|
|
|
|
/// end of the elements of a container
|
|
static NLOHMANN_VIEW_ALWAYS_INLINE const node* child_end(const node* n) noexcept
|
|
{
|
|
return n + n->next;
|
|
}
|
|
};
|
|
|
|
} // namespace view
|
|
} // namespace detail
|
|
NLOHMANN_JSON_NAMESPACE_END
|
|
|
|
// #include <nlohmann/detail/view/macro_scope.hpp>
|
|
|
|
// #include <nlohmann/detail/view/node.hpp>
|
|
|
|
// #include <nlohmann/detail/view/scan.hpp>
|
|
// __ _____ _____ _____
|
|
// __| | __| | | | 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
|
|
|
|
|
|
|
|
#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>
|
|
|
|
|
|
// 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
|
|
/// 16 bytes are checked one by one, so that the position advances by
|
|
/// constants in predicted branches (most strings are short); longer runs
|
|
/// continue eight bytes at a time.
|
|
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 (e - p >= 16)
|
|
{
|
|
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { p += (i); goto stop; }
|
|
NLOHMANN_VIEW_REPEAT16(NLOHMANN_VIEW_STEP)
|
|
#undef NLOHMANN_VIEW_STEP
|
|
p += 16;
|
|
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;
|
|
}
|
|
continue;
|
|
}
|
|
while (p != e && plain[*p] != 0)
|
|
{
|
|
++p;
|
|
}
|
|
if (p == e)
|
|
{
|
|
return p;
|
|
}
|
|
stop:
|
|
if (*p < 0x80)
|
|
{
|
|
return p; // quote, backslash, or control character
|
|
}
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
/// 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;
|
|
}
|
|
|
|
/// powers of ten up to 10^19 as integers
|
|
inline std::uint64_t int_pow10(unsigned k) noexcept
|
|
{
|
|
static const std::array<std::uint64_t, 20> table =
|
|
{
|
|
{
|
|
1u, 10u, 100u, 1000u, 10000u, 100000u, 1000000u, 10000000u, 100000000u, 1000000000u,
|
|
10000000000u, 100000000000u, 1000000000000u, 10000000000000u, 100000000000000u, 1000000000000000u,
|
|
10000000000000000u, 100000000000000000u, 1000000000000000000u, 10000000000000000000u
|
|
}
|
|
};
|
|
return table[k];
|
|
}
|
|
|
|
/// value of k <= 8 digits at p in one step if [p, p + 8) lies below limit,
|
|
/// else one digit at a time
|
|
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))
|
|
{
|
|
if (k == 8)
|
|
{
|
|
return parse_eight_digits(read_eight_bytes(p));
|
|
}
|
|
// 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)
|
|
{
|
|
w = (w * 100000000u) + parse_upto8(p, 8, limit);
|
|
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
|
|
|
|
|
|
// The view's parser: one pass over the input that emits the node index (see
|
|
// node.hpp). The table-driven decoding of \u escapes and the fast paths for
|
|
// ": " and indentation follow yyjson (https://github.com/ibireme/yyjson, MIT
|
|
// license).
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
namespace detail
|
|
{
|
|
namespace view
|
|
{
|
|
|
|
enum class error_code : std::uint8_t
|
|
{
|
|
none,
|
|
empty_input,
|
|
unexpected_value,
|
|
invalid_literal,
|
|
expected_key,
|
|
expected_colon,
|
|
expected_array_end,
|
|
expected_object_end,
|
|
trailing_characters,
|
|
number_after_minus,
|
|
number_after_dot,
|
|
number_after_exponent,
|
|
number_overflow,
|
|
string_missing_quote,
|
|
string_control_character,
|
|
string_utf8,
|
|
string_escape,
|
|
string_unicode_hex,
|
|
string_surrogate_high,
|
|
string_surrogate_low,
|
|
comment_start,
|
|
comment_unterminated,
|
|
input_too_large,
|
|
};
|
|
|
|
struct parse_failure
|
|
{
|
|
error_code code = error_code::none;
|
|
std::size_t offset = 0; ///< byte offset of the offending character
|
|
};
|
|
|
|
template<bool Comments, bool TrailingCommas, bool NulIsEnd, bool Sentinel>
|
|
class builder
|
|
{
|
|
public:
|
|
builder(document_data& d, const char* src, std::size_t size) noexcept
|
|
: doc(d)
|
|
, b(reinterpret_cast<const unsigned char*>(src))
|
|
, e(b + size)
|
|
{}
|
|
|
|
/// returns false and fills `failure` on error
|
|
bool run()
|
|
{
|
|
cursor c(*this);
|
|
return c.run();
|
|
}
|
|
|
|
builder(const builder&) = delete;
|
|
builder& operator=(const builder&) = delete;
|
|
builder(builder&&) = delete;
|
|
builder& operator=(builder&&) = delete;
|
|
~builder() = default;
|
|
|
|
/// where and why the parse failed (after run() returned false)
|
|
const parse_failure& failure() const noexcept
|
|
{
|
|
return m_failure;
|
|
}
|
|
|
|
private:
|
|
struct frame
|
|
{
|
|
std::uint32_t idx;
|
|
std::uint32_t count;
|
|
bool is_object;
|
|
};
|
|
|
|
document_data& doc;
|
|
const unsigned char* const b;
|
|
const unsigned char* const e;
|
|
parse_failure m_failure{};
|
|
|
|
// the open array/object is in the cursor; enclosing ones on a stack that is
|
|
// inline for the first 64 levels
|
|
frame shallow[64]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): not initialized on purpose; filled as containers open
|
|
std::vector<frame> deep{};
|
|
|
|
NLOHMANN_VIEW_NOINLINE bool fail(error_code c, const unsigned char* at) noexcept
|
|
{
|
|
m_failure.code = c;
|
|
m_failure.offset = static_cast<std::size_t>(at - b);
|
|
doc.tape_size = 0;
|
|
return false;
|
|
}
|
|
|
|
/// a failure (recorded by fail) as a comment() result
|
|
const unsigned char* fail_at(error_code c, const unsigned char* at) noexcept
|
|
{
|
|
fail(c, at);
|
|
return nullptr;
|
|
}
|
|
|
|
/// a decoded string: p after its closing quote (nullptr: an error), and
|
|
/// its bytes in the arena
|
|
struct decoded
|
|
{
|
|
const unsigned char* p;
|
|
std::size_t start;
|
|
std::size_t len;
|
|
};
|
|
|
|
decoded failed(error_code c, const unsigned char* at) noexcept
|
|
{
|
|
fail(c, at);
|
|
return decoded{nullptr, 0, 0};
|
|
}
|
|
|
|
/// the comment at p (*p == '/'): the position after it, or nullptr on error
|
|
NLOHMANN_VIEW_NOINLINE const unsigned char* comment(const unsigned char* p)
|
|
{
|
|
if (e - p < 2)
|
|
{
|
|
++p;
|
|
return fail_at(error_code::comment_start, p);
|
|
}
|
|
if (p[1] == '/')
|
|
{
|
|
p += 2;
|
|
while (p != e && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0))
|
|
{
|
|
++p;
|
|
}
|
|
if (NulIsEnd && p != e && *p == 0)
|
|
{
|
|
++p; // as in parse(), a null byte ends the comment like a line break
|
|
}
|
|
return p;
|
|
}
|
|
if (p[1] == '*')
|
|
{
|
|
p += 2;
|
|
for (;;)
|
|
{
|
|
if (p == e || (NulIsEnd && *p == 0))
|
|
{
|
|
return fail_at(error_code::comment_unterminated, p);
|
|
}
|
|
if (*p == '*' && p + 1 != e && p[1] == '/')
|
|
{
|
|
p += 2;
|
|
return p;
|
|
}
|
|
++p;
|
|
}
|
|
}
|
|
++p;
|
|
return fail_at(error_code::comment_start, p);
|
|
}
|
|
|
|
/// the index is full (n nodes, parsed up to at): extrapolate the node
|
|
/// count from the nodes per input byte so far (with headroom, and at least
|
|
/// 1.5 times as many), so that dense inputs regrow once instead of
|
|
/// doubling repeatedly; returns the new node array
|
|
NLOHMANN_VIEW_NOINLINE node* grow(std::size_t n, const unsigned char* at)
|
|
{
|
|
const std::uint64_t done = static_cast<std::uint64_t>(at - b) + 1;
|
|
const std::uint64_t guess = static_cast<std::uint64_t>(n) * static_cast<std::uint64_t>(e - b + 1) / done;
|
|
doc.tape_size = n;
|
|
doc.reserve((std::max)(static_cast<std::size_t>(guess + (guess / 4) + 64), n + (n / 2) + 64));
|
|
return doc.tape;
|
|
}
|
|
|
|
/// four hex digits at p as a code unit (p moves past them), or -1 (p at
|
|
/// the first bad digit); one table lookup per digit and a single check,
|
|
/// the four hex digits of a unicode escape (the library's table, after
|
|
/// yyjson's read_hex_u16), or -1
|
|
NLOHMANN_VIEW_ALWAYS_INLINE int hex4(const unsigned char*& p) noexcept
|
|
{
|
|
if (NLOHMANN_VIEW_LIKELY(e - p >= 4))
|
|
{
|
|
const int cp = hex_codepoint(p);
|
|
if (NLOHMANN_VIEW_LIKELY(cp >= 0))
|
|
{
|
|
p += 4;
|
|
return cp;
|
|
}
|
|
}
|
|
p = hex4_error(p);
|
|
return -1;
|
|
}
|
|
|
|
/// hex4() failed: the first bad digit (none: p)
|
|
NLOHMANN_VIEW_NOINLINE const unsigned char* hex4_error(const unsigned char* p) noexcept
|
|
{
|
|
if (e - p >= 4)
|
|
{
|
|
while (is_hex(*p))
|
|
{
|
|
++p;
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/// escapes present (or an error) in the string at s, scanned up to p:
|
|
/// decode into the arena
|
|
NLOHMANN_VIEW_NOINLINE decoded slow_string(const unsigned char* s, const unsigned char* p)
|
|
{
|
|
// single-character escapes; 0: invalid (and 'u', handled separately)
|
|
static const std::array<char, 128> simple_escape =
|
|
{
|
|
{
|
|
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,
|
|
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, 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, '\b', 0, 0, 0, '\f', 0, 0, 0, 0, 0, 0, 0, '\n', 0, 0, 0, '\r', 0, '\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
}
|
|
};
|
|
const std::size_t start = arena_used();
|
|
arena_run(s, static_cast<std::size_t>(p - s));
|
|
for (;;)
|
|
{
|
|
if (p == e)
|
|
{
|
|
return failed(error_code::string_missing_quote, p);
|
|
}
|
|
const unsigned char c = *p;
|
|
if (c == '"')
|
|
{
|
|
++p;
|
|
return decoded{p, start, arena_used() - start};
|
|
}
|
|
if (c != '\\')
|
|
{
|
|
if (NulIsEnd && c == 0)
|
|
{
|
|
return failed(error_code::string_missing_quote, p);
|
|
}
|
|
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
|
|
}
|
|
++p;
|
|
if (p == e)
|
|
{
|
|
return failed(error_code::string_missing_quote, p);
|
|
}
|
|
const unsigned char d = *p++;
|
|
arena_ensure(4);
|
|
if (d == 'u')
|
|
{
|
|
int cp = hex4(p);
|
|
if (NLOHMANN_VIEW_UNLIKELY(cp < 0))
|
|
{
|
|
return failed(error_code::string_unicode_hex, p);
|
|
}
|
|
if (NLOHMANN_VIEW_UNLIKELY((cp & 0xF800) == 0xD800)) // a surrogate
|
|
{
|
|
if (cp >= 0xDC00)
|
|
{
|
|
return failed(error_code::string_surrogate_low, p);
|
|
}
|
|
if (e - p < 2 || p[0] != '\\' || p[1] != 'u')
|
|
{
|
|
return failed(error_code::string_surrogate_high, p);
|
|
}
|
|
p += 2;
|
|
const int lo = hex4(p);
|
|
if (lo < 0)
|
|
{
|
|
return failed(error_code::string_unicode_hex, p);
|
|
}
|
|
if (lo < 0xDC00 || lo > 0xDFFF)
|
|
{
|
|
return failed(error_code::string_surrogate_high, p);
|
|
}
|
|
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
|
}
|
|
aw = put_utf8(aw, cp);
|
|
}
|
|
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
|
|
{
|
|
*aw++ = simple_escape[d];
|
|
}
|
|
else
|
|
{
|
|
--p;
|
|
return failed(error_code::string_escape, p);
|
|
}
|
|
if (p != e && *p == '\\')
|
|
{
|
|
continue; // consecutive escapes ("\u00e4\u00f6"): no run in between
|
|
}
|
|
const unsigned char* const r = p;
|
|
p = scan_string_run(p, e);
|
|
arena_run(r, static_cast<std::size_t>(p - r));
|
|
}
|
|
}
|
|
|
|
/// does the float token [s, p) overflow a double? (parse() rejects it)
|
|
NLOHMANN_VIEW_NOINLINE static bool float_overflows(const unsigned char* s, const unsigned char* p)
|
|
{
|
|
std::string token(reinterpret_cast<const char*>(s), static_cast<std::size_t>(p - s)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
const std::size_t dot = token.find('.');
|
|
const std::size_t exponent = token.find_first_of("eE");
|
|
double v = 0;
|
|
if (!convert_float_fast(token.data(), token.data() + token.size(), dot, exponent == std::string::npos ? token.size() : exponent, v))
|
|
{
|
|
convert_float_locale_aware(token, dot, v);
|
|
}
|
|
return v > (std::numeric_limits<double>::max)() || v < -(std::numeric_limits<double>::max)();
|
|
}
|
|
|
|
/// does the magnitude digits [d, d + n) exceed the given limit (same length)?
|
|
static bool digits_exceed(const unsigned char* d, const char* limit, std::size_t n) noexcept
|
|
{
|
|
return std::memcmp(d, limit, n) > 0;
|
|
}
|
|
|
|
static bool is_hex(unsigned char c) noexcept
|
|
{
|
|
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
|
|
}
|
|
|
|
// decode arena: the std::string in the document, written through a raw
|
|
// pointer (resized ahead in large steps; trimmed when parsing succeeds)
|
|
char* aw = nullptr;
|
|
char* aend = nullptr;
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE std::size_t arena_used() const noexcept
|
|
{
|
|
return aw != nullptr ? static_cast<std::size_t>(aw - doc.arena.data()) : 0;
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE void arena_ensure(std::size_t n)
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(aend - aw) < n))
|
|
{
|
|
arena_grow(n);
|
|
}
|
|
}
|
|
|
|
NLOHMANN_VIEW_NOINLINE void arena_grow(std::size_t n)
|
|
{
|
|
const std::size_t used = arena_used();
|
|
doc.arena.resize((std::max)(doc.arena.size() * 2, used + n + 256));
|
|
aw = &doc.arena[0] + used; // NOLINT(readability-container-data-pointer): data() is const before C++17
|
|
aend = &doc.arena[0] + doc.arena.size(); // NOLINT(readability-container-data-pointer)
|
|
}
|
|
|
|
/// append the run [r, r + n) to the arena; short runs as one fixed-size
|
|
/// 16-byte move when both sides have the room (no library call)
|
|
NLOHMANN_VIEW_ALWAYS_INLINE void arena_run(const unsigned char* r, std::size_t n)
|
|
{
|
|
arena_ensure(n + 16);
|
|
if (n <= 16 && e - r >= 16)
|
|
{
|
|
std::memcpy(aw, r, 16);
|
|
}
|
|
else
|
|
{
|
|
std::memcpy(aw, r, n);
|
|
}
|
|
aw += n;
|
|
}
|
|
|
|
/// UTF-8 encoding of cp at w (room for 4 bytes)
|
|
static char* put_utf8(char* w, int cp) noexcept
|
|
{
|
|
if (cp < 0x80)
|
|
{
|
|
*w++ = static_cast<char>(cp);
|
|
}
|
|
else if (cp < 0x800)
|
|
{
|
|
*w++ = static_cast<char>(0xC0 | (cp >> 6));
|
|
*w++ = static_cast<char>(0x80 | (cp & 0x3F));
|
|
}
|
|
else if (cp < 0x10000)
|
|
{
|
|
*w++ = static_cast<char>(0xE0 | (cp >> 12));
|
|
*w++ = static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
|
|
*w++ = static_cast<char>(0x80 | (cp & 0x3F));
|
|
}
|
|
else
|
|
{
|
|
*w++ = static_cast<char>(0xF0 | (cp >> 18));
|
|
*w++ = static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
|
|
*w++ = static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
|
|
*w++ = static_cast<char>(0x80 | (cp & 0x3F));
|
|
}
|
|
return w;
|
|
}
|
|
|
|
/// The parse state and the parser proper. The cursor is a local object of
|
|
/// run() whose address never escapes (everything it calls out of line is a
|
|
/// member of the builder and gets the positions it needs), so that the
|
|
/// compiler keeps the state in registers instead of reloading it from
|
|
/// memory after every node store and call.
|
|
struct cursor
|
|
{
|
|
explicit cursor(builder& owner) noexcept
|
|
: cold(owner)
|
|
, b(owner.b)
|
|
, p(owner.b)
|
|
, e(owner.e)
|
|
{}
|
|
|
|
builder& cold; ///< out-of-line helpers and state that needs no registers
|
|
const unsigned char* const b;
|
|
const unsigned char* p;
|
|
const unsigned char* const e;
|
|
node* base = nullptr;
|
|
node* out = nullptr;
|
|
node* cap = nullptr;
|
|
|
|
// the open array/object
|
|
std::uint32_t cur_idx = 0;
|
|
std::uint32_t cur_count = 0;
|
|
bool cur_is_object = false;
|
|
std::size_t depth = 0;
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE bool run()
|
|
{
|
|
cold.doc.reserve(estimate_nodes(reinterpret_cast<const char*>(b), static_cast<std::size_t>(e - b)));
|
|
base = cold.doc.tape;
|
|
out = base;
|
|
cap = base + cold.doc.tape_cap;
|
|
|
|
if (e - p >= 3 && p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBF)
|
|
{
|
|
p += 3; // byte order mark
|
|
}
|
|
if (!ws())
|
|
{
|
|
return false;
|
|
}
|
|
if (p == e || (NulIsEnd && *p == 0))
|
|
{
|
|
return fail(error_code::empty_input);
|
|
}
|
|
|
|
// root value
|
|
switch (cur())
|
|
{
|
|
case '{':
|
|
open(value_t::object);
|
|
++p;
|
|
goto obj_first;
|
|
case '[':
|
|
open(value_t::array);
|
|
++p;
|
|
goto arr_first;
|
|
default:
|
|
if (!scalar())
|
|
{
|
|
return false;
|
|
}
|
|
goto root_done;
|
|
}
|
|
|
|
// value dispatch, expanded once for array elements and once for member
|
|
// values: each jump has its own history (arrays tend to hold one kind of
|
|
// value), and the continuation needs no branch on the container kind
|
|
#define NLOHMANN_VIEW_VALUE(NEXT) \
|
|
switch (cur()) \
|
|
{ \
|
|
case '"': \
|
|
if (NLOHMANN_VIEW_UNLIKELY(!string())) { return false; } \
|
|
goto NEXT; \
|
|
case '{': \
|
|
open(value_t::object); \
|
|
++p; \
|
|
goto obj_first; \
|
|
case '[': \
|
|
open(value_t::array); \
|
|
++p; \
|
|
goto arr_first; \
|
|
case '-': \
|
|
if (NLOHMANN_VIEW_UNLIKELY(!number<true>())) { return false; } \
|
|
goto NEXT; \
|
|
case '0': case '1': case '2': case '3': \
|
|
case '4': case '5': case '6': case '7': case '8': case '9': \
|
|
if (NLOHMANN_VIEW_UNLIKELY(!number<false>())) { return false; } \
|
|
goto NEXT; \
|
|
case 't': \
|
|
if (NLOHMANN_VIEW_UNLIKELY(!literal("true", 4, value_t::boolean, node_flags::is_true))) { return false; } \
|
|
goto NEXT; \
|
|
case 'f': \
|
|
if (NLOHMANN_VIEW_UNLIKELY(!literal_false())) { return false; } \
|
|
goto NEXT; \
|
|
case 'n': \
|
|
if (NLOHMANN_VIEW_UNLIKELY(!literal("null", 4, value_t::null, 0))) { return false; } \
|
|
goto NEXT; \
|
|
default: \
|
|
return fail(error_code::unexpected_value); \
|
|
}
|
|
|
|
arr_first:
|
|
if (!ws())
|
|
{
|
|
return false;
|
|
}
|
|
if (cur() == ']')
|
|
{
|
|
++p;
|
|
goto close_container;
|
|
}
|
|
value:
|
|
NLOHMANN_VIEW_VALUE(arr_next)
|
|
arr_next:
|
|
++cur_count;
|
|
if (!ws())
|
|
{
|
|
return false;
|
|
}
|
|
if (NLOHMANN_VIEW_LIKELY(cur() == ','))
|
|
{
|
|
++p;
|
|
if (!ws())
|
|
{
|
|
return false;
|
|
}
|
|
if (TrailingCommas && cur() == ']')
|
|
{
|
|
++p;
|
|
goto close_container;
|
|
}
|
|
goto value;
|
|
}
|
|
if (cur() == ']')
|
|
{
|
|
++p;
|
|
goto close_container;
|
|
}
|
|
return fail(error_code::expected_array_end);
|
|
|
|
obj_first:
|
|
if (!ws())
|
|
{
|
|
return false;
|
|
}
|
|
if (cur() == '}')
|
|
{
|
|
++p;
|
|
goto close_container;
|
|
}
|
|
obj_key:
|
|
if (NLOHMANN_VIEW_UNLIKELY(cur() != '"'))
|
|
{
|
|
return fail(error_code::expected_key);
|
|
}
|
|
if (NLOHMANN_VIEW_UNLIKELY(!string()))
|
|
{
|
|
return false;
|
|
}
|
|
if (NLOHMANN_VIEW_LIKELY(cur() == ':' && (Sentinel || e - p >= 2) && p[1] == ' '))
|
|
{
|
|
p += 2; // ": " (pretty-printed input; a fast path of yyjson)
|
|
}
|
|
else
|
|
{
|
|
if (!ws())
|
|
{
|
|
return false;
|
|
}
|
|
if (NLOHMANN_VIEW_UNLIKELY(cur() != ':'))
|
|
{
|
|
return fail(error_code::expected_colon);
|
|
}
|
|
++p;
|
|
}
|
|
if (!ws())
|
|
{
|
|
return false;
|
|
}
|
|
NLOHMANN_VIEW_VALUE(obj_next)
|
|
obj_next:
|
|
++cur_count;
|
|
if (!ws())
|
|
{
|
|
return false;
|
|
}
|
|
if (NLOHMANN_VIEW_LIKELY(cur() == ','))
|
|
{
|
|
++p;
|
|
if (!ws())
|
|
{
|
|
return false;
|
|
}
|
|
if (TrailingCommas && cur() == '}')
|
|
{
|
|
++p;
|
|
goto close_container;
|
|
}
|
|
goto obj_key;
|
|
}
|
|
if (cur() == '}')
|
|
{
|
|
++p;
|
|
goto close_container;
|
|
}
|
|
return fail(error_code::expected_object_end);
|
|
|
|
#undef NLOHMANN_VIEW_VALUE
|
|
|
|
close_container:
|
|
close();
|
|
if (NLOHMANN_VIEW_UNLIKELY(depth == 0))
|
|
{
|
|
goto root_done;
|
|
}
|
|
if (cur_is_object)
|
|
{
|
|
goto obj_next;
|
|
}
|
|
goto arr_next;
|
|
|
|
root_done:
|
|
if (!ws())
|
|
{
|
|
return false;
|
|
}
|
|
if (p != e && !(NulIsEnd && *p == 0))
|
|
{
|
|
return fail(error_code::trailing_characters);
|
|
}
|
|
cold.doc.tape_size = static_cast<std::size_t>(out - base);
|
|
cold.doc.arena.resize(cold.arena_used());
|
|
return true;
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE bool fail(error_code c) noexcept
|
|
{
|
|
return cold.fail(c, p);
|
|
}
|
|
|
|
/// the current byte, or 0 at the end. With a NUL-terminated input
|
|
/// (Sentinel) the terminator is read instead of checking the bounds; a 0
|
|
/// never matches a JSON token, so the error paths tell the end apart.
|
|
NLOHMANN_VIEW_ALWAYS_INLINE unsigned char cur() const noexcept
|
|
{
|
|
if (Sentinel)
|
|
{
|
|
return *p;
|
|
}
|
|
return p != e ? *p : 0;
|
|
}
|
|
|
|
/// root scalar
|
|
NLOHMANN_VIEW_ALWAYS_INLINE bool scalar()
|
|
{
|
|
switch (cur())
|
|
{
|
|
case '"':
|
|
return string();
|
|
case 't':
|
|
return literal("true", 4, value_t::boolean, node_flags::is_true);
|
|
case 'f':
|
|
return literal_false();
|
|
case 'n':
|
|
return literal("null", 4, value_t::null, 0);
|
|
case '-':
|
|
return number<true>();
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
return number<false>();
|
|
default:
|
|
return fail(error_code::unexpected_value);
|
|
}
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE bool literal_false()
|
|
{
|
|
return literal("false", 5, value_t::boolean, 0);
|
|
}
|
|
|
|
/// skip whitespace (and comments); false on a malformed comment
|
|
NLOHMANN_VIEW_ALWAYS_INLINE bool ws()
|
|
{
|
|
const unsigned char c = cur();
|
|
if (NLOHMANN_VIEW_LIKELY(c > ' ' && (!Comments || c != '/')))
|
|
{
|
|
return true; // no whitespace: the common case in minified input
|
|
}
|
|
return ws_slow();
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE bool ws_slow()
|
|
{
|
|
for (;;)
|
|
{
|
|
if (cur() == ' ' && (Sentinel || e - p >= 2) && p[1] > ' ' && (!Comments || p[1] != '/'))
|
|
{
|
|
++p; // single space, e.g. after ':' or ','
|
|
return true;
|
|
}
|
|
if (cur() == '\n' || cur() == '\r')
|
|
{
|
|
// (a branch, not an add of the comparison: p must not
|
|
// wait for the byte after the line break)
|
|
if (NLOHMANN_VIEW_UNLIKELY(cur() == '\r') && (Sentinel || e - p >= 2) && p[1] == '\n')
|
|
{
|
|
p += 2;
|
|
}
|
|
else
|
|
{
|
|
++p;
|
|
}
|
|
// indentation: two spaces per step, fixed offsets (after yyjson)
|
|
while (e - p >= 32)
|
|
{
|
|
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(load16(p + (std::ptrdiff_t{2} * (i))) == 0x2020)) {} else { p += std::ptrdiff_t{2} * (i); goto indent_done; }
|
|
NLOHMANN_VIEW_REPEAT16(NLOHMANN_VIEW_STEP)
|
|
#undef NLOHMANN_VIEW_STEP
|
|
p += 32;
|
|
}
|
|
indent_done:
|
|
;
|
|
}
|
|
for (unsigned char c = cur(); c == ' ' || c == '\n' || c == '\r' || c == '\t'; c = cur())
|
|
{
|
|
++p;
|
|
}
|
|
if (Comments && cur() == '/')
|
|
{
|
|
const unsigned char* const q = cold.comment(p);
|
|
if (q == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
p = q;
|
|
continue;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// append a node: (kind, flags, extra, off) and the second word (len, or
|
|
/// an integer's value); two stores on little-endian targets
|
|
NLOHMANN_VIEW_ALWAYS_INLINE node* emit(value_t k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second)
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(out == cap))
|
|
{
|
|
const auto n = static_cast<std::size_t>(out - base);
|
|
base = cold.grow(n, p);
|
|
out = base + n;
|
|
cap = base + cold.doc.tape_cap;
|
|
}
|
|
node* n = out++;
|
|
#if NLOHMANN_VIEW_LITTLE_ENDIAN
|
|
const std::uint64_t first = static_cast<std::uint64_t>(k) | (static_cast<std::uint64_t>(flags) << 8)
|
|
| (static_cast<std::uint64_t>(extra) << 16) | (static_cast<std::uint64_t>(off) << 32);
|
|
std::memcpy(reinterpret_cast<unsigned char*>(n), &first, 8);
|
|
std::memcpy(reinterpret_cast<unsigned char*>(n) + 8, &second, 8);
|
|
#else
|
|
n->kind = static_cast<std::uint8_t>(k);
|
|
n->flags = flags;
|
|
n->extra = extra;
|
|
n->off = static_cast<std::uint32_t>(off);
|
|
set_integer_bits(*n, second);
|
|
#endif
|
|
return n;
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE void open(value_t k)
|
|
{
|
|
const auto idx = static_cast<std::uint32_t>(emit(k, 0, 0, static_cast<std::size_t>(p - b), 0) - base);
|
|
if (depth != 0)
|
|
{
|
|
const frame f = {cur_idx, cur_count, cur_is_object};
|
|
if (NLOHMANN_VIEW_LIKELY(depth <= 64))
|
|
{
|
|
cold.shallow[depth - 1] = f;
|
|
}
|
|
else
|
|
{
|
|
cold.deep.push_back(f);
|
|
}
|
|
}
|
|
++depth;
|
|
cur_idx = idx;
|
|
cur_count = 0;
|
|
cur_is_object = k == value_t::object;
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE void close()
|
|
{
|
|
node& n = base[cur_idx];
|
|
n.len = cur_count;
|
|
n.next = static_cast<std::uint32_t>(out - base) - cur_idx;
|
|
if (--depth != 0)
|
|
{
|
|
frame f{};
|
|
if (NLOHMANN_VIEW_LIKELY(depth <= 64))
|
|
{
|
|
f = cold.shallow[depth - 1];
|
|
}
|
|
else
|
|
{
|
|
f = cold.deep.back();
|
|
cold.deep.pop_back();
|
|
}
|
|
cur_idx = f.idx;
|
|
cur_count = f.count;
|
|
cur_is_object = f.is_object;
|
|
}
|
|
}
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE bool literal(const char* text, std::size_t n, value_t k, std::uint8_t flags)
|
|
{
|
|
if (NLOHMANN_VIEW_UNLIKELY(e - p < static_cast<std::ptrdiff_t>(n) || std::memcmp(p, text, n) != 0))
|
|
{
|
|
return fail(error_code::invalid_literal);
|
|
}
|
|
emit(k, flags, 0, static_cast<std::size_t>(p - b), n);
|
|
p += n;
|
|
return true;
|
|
}
|
|
|
|
/// a number at p; the sign is known from the dispatch (so that p does
|
|
/// not have to wait for the first byte)
|
|
template<bool negative>
|
|
NLOHMANN_VIEW_ALWAYS_INLINE bool number()
|
|
{
|
|
const unsigned char* const s = p;
|
|
if (negative)
|
|
{
|
|
++p;
|
|
}
|
|
const unsigned char* const int_start = p;
|
|
if (p != e && *p == '0')
|
|
{
|
|
++p;
|
|
}
|
|
else if (NLOHMANN_VIEW_LIKELY(p != e && *p >= '1' && *p <= '9'))
|
|
{
|
|
p = skip_digits(p + 1, e);
|
|
}
|
|
else
|
|
{
|
|
return fail(error_code::number_after_minus);
|
|
}
|
|
const auto int_digits = static_cast<std::size_t>(p - int_start);
|
|
std::size_t frac_digits = 0;
|
|
bool is_float = false;
|
|
if (p != e && *p == '.')
|
|
{
|
|
++p;
|
|
const unsigned char* const f0 = p;
|
|
p = skip_digits(p, e);
|
|
if (NLOHMANN_VIEW_UNLIKELY(p == f0))
|
|
{
|
|
return fail(error_code::number_after_dot);
|
|
}
|
|
frac_digits = static_cast<std::size_t>(p - f0);
|
|
is_float = true;
|
|
}
|
|
long exponent = 0;
|
|
if (p != e && (*p | 0x20) == 'e')
|
|
{
|
|
++p;
|
|
bool exp_negative = false;
|
|
if (p != e && (*p == '+' || *p == '-'))
|
|
{
|
|
exp_negative = *p == '-';
|
|
++p;
|
|
}
|
|
if (NLOHMANN_VIEW_UNLIKELY(p == e || !is_digit(*p)))
|
|
{
|
|
return fail(error_code::number_after_exponent);
|
|
}
|
|
while (p != e && is_digit(*p))
|
|
{
|
|
if (exponent < 100000)
|
|
{
|
|
exponent = (exponent * 10) + (*p - '0');
|
|
}
|
|
++p;
|
|
}
|
|
if (exp_negative)
|
|
{
|
|
exponent = -exponent;
|
|
}
|
|
is_float = true;
|
|
}
|
|
|
|
value_t kind = value_t::number_float;
|
|
if (!is_float)
|
|
{
|
|
kind = negative ? value_t::number_integer : value_t::number_unsigned;
|
|
}
|
|
if (!is_float)
|
|
{
|
|
// integers that do not fit become floats, as in parse()
|
|
if (NLOHMANN_VIEW_UNLIKELY(int_digits >= 19))
|
|
{
|
|
if (negative)
|
|
{
|
|
if (int_digits > 19 || (int_digits == 19 && digits_exceed(int_start, "9223372036854775808", 19)))
|
|
{
|
|
kind = value_t::number_float;
|
|
}
|
|
}
|
|
else if (int_digits > 20 || (int_digits == 20 && digits_exceed(int_start, "18446744073709551615", 20)))
|
|
{
|
|
kind = value_t::number_float;
|
|
}
|
|
}
|
|
}
|
|
// parse() rejects floats that overflow; only numbers whose magnitude
|
|
// could reach 1e308 need the conversion
|
|
if (NLOHMANN_VIEW_UNLIKELY(static_cast<long>(int_digits) + exponent > 300 && kind == value_t::number_float))
|
|
{
|
|
if (builder::float_overflows(s, p))
|
|
{
|
|
p = s;
|
|
return fail(error_code::number_overflow);
|
|
}
|
|
}
|
|
const auto layout = static_cast<std::uint16_t>((int_digits < 255 ? int_digits : 255) | ((frac_digits < 255 ? frac_digits : 255) << 8));
|
|
auto second = static_cast<std::uint64_t>(p - s);
|
|
if (kind != value_t::number_float)
|
|
{
|
|
// integers are converted now, while their digits are in cache
|
|
const std::uint64_t m = int_digits <= 19 ? parse_upto19(int_start, static_cast<unsigned>(int_digits), e)
|
|
: (parse_upto19(int_start, 19, e) * 10) + static_cast<std::uint64_t>(int_start[19] - '0');
|
|
second = negative ? 0 - m : m;
|
|
}
|
|
emit(kind, 0, layout, static_cast<std::size_t>(s - b), second);
|
|
return true;
|
|
}
|
|
|
|
/// a string (value or key) at p
|
|
NLOHMANN_VIEW_ALWAYS_INLINE bool string()
|
|
{
|
|
++p; // opening quote
|
|
const unsigned char* const s = p;
|
|
p = scan_string_run(p, e);
|
|
if (NLOHMANN_VIEW_LIKELY(p != e && *p == '"'))
|
|
{
|
|
emit(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s));
|
|
++p;
|
|
return true;
|
|
}
|
|
const decoded r = cold.slow_string(s, p);
|
|
if (r.p == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
p = r.p;
|
|
emit(value_t::string, node_flags::escaped, 0, r.start, r.len);
|
|
return true;
|
|
}
|
|
};
|
|
};
|
|
|
|
/// run the builder with compile-time options
|
|
template<bool NulIsEnd, bool Comments, bool TrailingCommas>
|
|
inline bool build_with(document_data& d, const char* src, std::size_t size, bool sentinel, parse_failure& failure)
|
|
{
|
|
if (sentinel)
|
|
{
|
|
builder<Comments, TrailingCommas, NulIsEnd, true> bld(d, src, size);
|
|
const bool ok = bld.run();
|
|
failure = bld.failure();
|
|
return ok;
|
|
}
|
|
builder<Comments, TrailingCommas, NulIsEnd, false> bld(d, src, size);
|
|
const bool ok = bld.run();
|
|
failure = bld.failure();
|
|
return ok;
|
|
}
|
|
|
|
/// sentinel: src[size] is readable and 0 (e.g. std::string)
|
|
template<bool NulIsEnd>
|
|
inline bool build(document_data& d, const char* src, std::size_t size, bool comments, bool trailing_commas, bool sentinel, parse_failure& failure)
|
|
{
|
|
if (comments)
|
|
{
|
|
return trailing_commas ? build_with<NulIsEnd, true, true>(d, src, size, sentinel, failure)
|
|
: build_with<NulIsEnd, true, false>(d, src, size, sentinel, failure);
|
|
}
|
|
return trailing_commas ? build_with<NulIsEnd, false, true>(d, src, size, sentinel, failure)
|
|
: build_with<NulIsEnd, false, false>(d, src, size, sentinel, failure);
|
|
}
|
|
|
|
} // namespace view
|
|
} // namespace detail
|
|
NLOHMANN_JSON_NAMESPACE_END
|
|
|
|
// #include <nlohmann/detail/view/document_data.hpp>
|
|
|
|
// #include <nlohmann/detail/view/errors.hpp>
|
|
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
#include <algorithm> // min
|
|
#include <cstddef> // size_t
|
|
#include <string> // string
|
|
|
|
// #include <nlohmann/json.hpp>
|
|
// #include <nlohmann/detail/view/builder.hpp>
|
|
|
|
// #include <nlohmann/detail/view/macro_scope.hpp>
|
|
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
namespace detail
|
|
{
|
|
namespace view
|
|
{
|
|
|
|
// Exceptions are thrown out of line, so that the accessors that may throw stay
|
|
// small enough to be inlined.
|
|
|
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const char* prefix, const char* type)
|
|
{
|
|
NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr));
|
|
}
|
|
|
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(int id, const std::string& msg)
|
|
{
|
|
NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr));
|
|
}
|
|
|
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(int id, const char* msg)
|
|
{
|
|
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
|
|
}
|
|
|
|
/*!
|
|
@brief throw the exception BasicJsonType::parse would throw for this input
|
|
|
|
The view accepts exactly the inputs parse() accepts, so on a failure the
|
|
library parser is run on the same bytes: it throws the exception parse() would
|
|
throw, with the same message, position, and "last read" token. The error path
|
|
is cold, so this costs nothing on valid input. Should parse() accept the input
|
|
nevertheless (a bug), the view's own failure is reported.
|
|
*/
|
|
template<typename BasicJsonType>
|
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_parse_failure(const parse_failure& f, const char* src, std::size_t size,
|
|
bool ignore_comments, bool ignore_trailing_commas)
|
|
{
|
|
if (f.code == error_code::input_too_large)
|
|
{
|
|
NLOHMANN_VIEW_THROW(out_of_range::create(416, "input of 4 GiB or more is not supported by json_document", nullptr));
|
|
}
|
|
const BasicJsonType accepted = BasicJsonType::parse(src, src + size, nullptr, true, ignore_comments, ignore_trailing_commas);
|
|
static_cast<void>(accepted);
|
|
|
|
position_t pos;
|
|
const std::size_t off = (std::min)(f.offset, size);
|
|
pos.chars_read_total = off + 1;
|
|
std::size_t line_start = 0;
|
|
for (std::size_t i = 0; i < off; ++i)
|
|
{
|
|
if (src[i] == '\n')
|
|
{
|
|
++pos.lines_read;
|
|
line_start = i + 1;
|
|
}
|
|
}
|
|
pos.chars_read_current_line = off + 1 - line_start;
|
|
NLOHMANN_VIEW_THROW(parse_error::create(101, pos, "syntax error while parsing value", nullptr));
|
|
}
|
|
|
|
} // namespace view
|
|
} // namespace detail
|
|
NLOHMANN_JSON_NAMESPACE_END
|
|
|
|
// #include <nlohmann/detail/view/input.hpp>
|
|
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
#include <string> // basic_string, char_traits, string
|
|
#include <type_traits> // decay, integral_constant, is_array, is_lvalue_reference, is_pointer, is_same, remove_reference
|
|
#include <utility> // forward
|
|
|
|
// #include <nlohmann/json.hpp>
|
|
// #include <nlohmann/detail/view/macro_scope.hpp>
|
|
|
|
|
|
#if NLOHMANN_VIEW_HAS_CPP_17
|
|
#include <string_view> // string_view
|
|
#endif
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
namespace detail
|
|
{
|
|
namespace view
|
|
{
|
|
|
|
/// how a document takes its input
|
|
enum class input_kind
|
|
{
|
|
move_string, ///< rvalue std::string: owned without a copy
|
|
c_string, ///< const char* (NUL-terminated): borrowed
|
|
char_array, ///< char array (e.g. a string literal): borrowed
|
|
borrow_range, ///< lvalue contiguous byte container, or std::string_view: borrowed
|
|
copy_range, ///< rvalue contiguous byte container: copied
|
|
adapter, ///< anything else parse() accepts (streams, wide strings, ...): read into a buffer
|
|
};
|
|
|
|
template<typename InputType>
|
|
struct classify_input
|
|
{
|
|
using R = typename std::remove_reference<InputType>::type;
|
|
using D = typename std::decay<InputType>::type;
|
|
static constexpr bool is_rvalue = !std::is_lvalue_reference<InputType>::value;
|
|
static constexpr bool is_bytes = is_contiguous_byte_container<D>::value;
|
|
#if NLOHMANN_VIEW_HAS_CPP_17
|
|
static constexpr bool is_string_view = std::is_same<D, std::string_view>::value;
|
|
#else
|
|
static constexpr bool is_string_view = false;
|
|
#endif
|
|
// NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11
|
|
static constexpr input_kind value =
|
|
std::is_array<R>::value ? input_kind::char_array
|
|
: std::is_pointer<D>::value ? input_kind::c_string
|
|
: (is_rvalue && std::is_same<D, std::string>::value) ? input_kind::move_string
|
|
: (is_bytes && (!is_rvalue || is_string_view)) ? input_kind::borrow_range
|
|
: is_bytes ? input_kind::copy_range
|
|
: input_kind::adapter;
|
|
// NOLINTEND(readability-avoid-nested-conditional-operator)
|
|
};
|
|
|
|
/// std::basic_string guarantees a NUL at data()[size()] (the parser's sentinel)
|
|
template<typename T>
|
|
struct is_std_string : std::false_type {};
|
|
|
|
template<typename Traits, typename Alloc>
|
|
struct is_std_string<std::basic_string<char, Traits, Alloc>> : std::true_type {};
|
|
|
|
/// drain a json input adapter (UTF-16/32 inputs arrive as UTF-8)
|
|
template<typename Adapter>
|
|
std::string collect_adapter(Adapter ia)
|
|
{
|
|
std::string buf;
|
|
for (;;)
|
|
{
|
|
const auto ch = ia.get_character();
|
|
if (ch == std::char_traits<char>::eof())
|
|
{
|
|
break;
|
|
}
|
|
buf.push_back(static_cast<char>(ch));
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
} // namespace view
|
|
} // namespace detail
|
|
NLOHMANN_JSON_NAMESPACE_END
|
|
|
|
// #include <nlohmann/detail/view/macro_scope.hpp>
|
|
|
|
// #include <nlohmann/detail/view/materialize.hpp>
|
|
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
#include <cstdint> // int64_t, uint8_t
|
|
#include <vector> // vector
|
|
|
|
// #include <nlohmann/json.hpp>
|
|
// #include <nlohmann/detail/view/document_data.hpp>
|
|
|
|
// #include <nlohmann/detail/view/macro_scope.hpp>
|
|
|
|
// #include <nlohmann/detail/view/node.hpp>
|
|
|
|
// #include <nlohmann/detail/view/number.hpp>
|
|
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
#include <cstddef> // size_t
|
|
#include <string> // string
|
|
|
|
// #include <nlohmann/json.hpp>
|
|
// #include <nlohmann/detail/view/macro_scope.hpp>
|
|
|
|
// #include <nlohmann/detail/view/node.hpp>
|
|
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
namespace detail
|
|
{
|
|
namespace view
|
|
{
|
|
|
|
/*!
|
|
@brief the value of the float token of a node, as parse() converts it
|
|
|
|
Uses the lexer's conversion (detail::convert_float_fast, then the locale-aware
|
|
strtod fallback), so that the values are bit-identical to parse(). The digit
|
|
layout recorded while parsing locates the decimal point and the exponent
|
|
without scanning the token.
|
|
*/
|
|
template<typename FloatType>
|
|
NLOHMANN_VIEW_NOINLINE FloatType float_value(const char* first, const node& n)
|
|
{
|
|
const char* const last = first + n.len;
|
|
const std::size_t neg = first[0] == '-' ? 1 : 0;
|
|
const std::size_t int_digits = n.extra & 0xFFu;
|
|
const std::size_t frac_digits = n.extra >> 8u;
|
|
std::size_t dot = std::string::npos;
|
|
std::size_t mantissa_end = n.len;
|
|
if (int_digits != 255 && frac_digits != 255)
|
|
{
|
|
dot = frac_digits != 0 ? neg + int_digits : std::string::npos;
|
|
mantissa_end = neg + int_digits + (frac_digits != 0 ? 1 + frac_digits : 0);
|
|
}
|
|
else
|
|
{
|
|
// more digits than the layout records: locate them
|
|
for (std::size_t i = 0; i < n.len; ++i)
|
|
{
|
|
if (first[i] == '.')
|
|
{
|
|
dot = i;
|
|
}
|
|
else if (first[i] == 'e' || first[i] == 'E')
|
|
{
|
|
mantissa_end = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
FloatType v{};
|
|
if (!convert_float_fast(first, last, dot, mantissa_end, v))
|
|
{
|
|
std::string token(first, last);
|
|
convert_float_locale_aware(token, dot, v);
|
|
}
|
|
return v;
|
|
}
|
|
|
|
} // namespace view
|
|
} // namespace detail
|
|
NLOHMANN_JSON_NAMESPACE_END
|
|
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
namespace detail
|
|
{
|
|
namespace view
|
|
{
|
|
|
|
/*!
|
|
@brief the basic_json value of the subtree at n
|
|
|
|
The subtree is replayed into the SAX handler that parse() uses to build its
|
|
values, so the result is the value parse() would produce: duplicate keys keep
|
|
the last value, and with JSON_DIAGNOSTICS the parent pointers are set. It is
|
|
iterative, so the nesting depth is limited by memory only, as for parse().
|
|
Without a lexer the handler records no source positions
|
|
(JSON_DIAGNOSTIC_POSITIONS).
|
|
*/
|
|
template<typename BasicJsonType>
|
|
BasicJsonType materialize(const document_data& d, const node* n)
|
|
{
|
|
using string_t = typename BasicJsonType::string_t;
|
|
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>>;
|
|
|
|
BasicJsonType result;
|
|
sax_t sax(result, true);
|
|
const string_t no_token{};
|
|
// the ends of the open containers, and whether they are objects
|
|
std::vector<std::pair<const node*, bool>> open;
|
|
for (;;)
|
|
{
|
|
switch (static_cast<value_t>(n->kind))
|
|
{
|
|
case value_t::object:
|
|
case value_t::array:
|
|
{
|
|
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
|
if (object)
|
|
{
|
|
sax.start_object(n->len);
|
|
}
|
|
else
|
|
{
|
|
sax.start_array(n->len);
|
|
}
|
|
open.emplace_back(document_data::child_end(n), object);
|
|
n = document_data::first_child(n);
|
|
break;
|
|
}
|
|
case value_t::string:
|
|
{
|
|
string_t s(d.str(*n), n->len);
|
|
sax.string(s);
|
|
++n;
|
|
break;
|
|
}
|
|
case value_t::number_integer:
|
|
sax.number_integer(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(*n))));
|
|
++n;
|
|
break;
|
|
case value_t::number_unsigned:
|
|
sax.number_unsigned(static_cast<typename BasicJsonType::number_unsigned_t>(integer_bits(*n)));
|
|
++n;
|
|
break;
|
|
case value_t::number_float:
|
|
sax.number_float(float_value<typename BasicJsonType::number_float_t>(d.str(*n), *n), no_token);
|
|
++n;
|
|
break;
|
|
case value_t::boolean:
|
|
sax.boolean((n->flags & node_flags::is_true) != 0);
|
|
++n;
|
|
break;
|
|
case value_t::null:
|
|
case value_t::binary:
|
|
case value_t::discarded:
|
|
default:
|
|
sax.null();
|
|
++n;
|
|
break;
|
|
}
|
|
for (;;)
|
|
{
|
|
if (open.empty())
|
|
{
|
|
return result;
|
|
}
|
|
if (n != open.back().first)
|
|
{
|
|
break;
|
|
}
|
|
if (open.back().second)
|
|
{
|
|
sax.end_object();
|
|
}
|
|
else
|
|
{
|
|
sax.end_array();
|
|
}
|
|
open.pop_back();
|
|
}
|
|
if (open.back().second)
|
|
{
|
|
// the key of the next member
|
|
string_t key(d.str(*n), n->len);
|
|
sax.key(key);
|
|
++n;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace view
|
|
} // namespace detail
|
|
NLOHMANN_JSON_NAMESPACE_END
|
|
|
|
// #include <nlohmann/detail/view/node.hpp>
|
|
|
|
// #include <nlohmann/detail/view/string_ref.hpp>
|
|
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
#include <algorithm> // min
|
|
#include <cstddef> // size_t
|
|
#include <cstring> // memcmp, strlen
|
|
#include <string> // basic_string
|
|
|
|
// #include <nlohmann/json.hpp>
|
|
// #include <nlohmann/detail/view/macro_scope.hpp>
|
|
|
|
|
|
#if NLOHMANN_VIEW_HAS_CPP_17
|
|
#include <string_view> // string_view
|
|
#endif
|
|
#ifndef JSON_NO_IO
|
|
#include <ostream> // ostream
|
|
#endif
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
namespace detail
|
|
{
|
|
namespace view
|
|
{
|
|
|
|
#if NLOHMANN_VIEW_HAS_CPP_17
|
|
using string_ref = std::string_view;
|
|
#else
|
|
/// minimal C++11 stand-in for std::string_view
|
|
class string_ref
|
|
{
|
|
public:
|
|
using size_type = std::size_t;
|
|
using const_iterator = const char*;
|
|
|
|
string_ref() noexcept = default;
|
|
string_ref(const char* s) : m_data(s), m_size(std::strlen(s)) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
|
string_ref(const char* s, std::size_t n) noexcept : m_data(s), m_size(n) {}
|
|
template<typename Traits, typename Alloc>
|
|
string_ref(const std::basic_string<char, Traits, Alloc>& s) noexcept : m_data(s.data()), m_size(s.size()) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
|
|
|
const char* data() const noexcept
|
|
{
|
|
return m_data;
|
|
}
|
|
std::size_t size() const noexcept
|
|
{
|
|
return m_size;
|
|
}
|
|
std::size_t length() const noexcept
|
|
{
|
|
return m_size;
|
|
}
|
|
bool empty() const noexcept
|
|
{
|
|
return m_size == 0;
|
|
}
|
|
const char* begin() const noexcept
|
|
{
|
|
return m_data;
|
|
}
|
|
const char* end() const noexcept
|
|
{
|
|
return m_data + m_size;
|
|
}
|
|
char operator[](std::size_t i) const noexcept
|
|
{
|
|
return m_data[i];
|
|
}
|
|
|
|
template<typename Traits, typename Alloc>
|
|
explicit operator std::basic_string<char, Traits, Alloc>() const
|
|
{
|
|
return std::basic_string<char, Traits, Alloc>(m_data, m_size);
|
|
}
|
|
|
|
friend bool operator==(string_ref a, string_ref b) noexcept
|
|
{
|
|
return a.m_size == b.m_size && (a.m_size == 0 || std::memcmp(a.m_data, b.m_data, a.m_size) == 0);
|
|
}
|
|
friend bool operator!=(string_ref a, string_ref b) noexcept
|
|
{
|
|
return !(a == b);
|
|
}
|
|
friend bool operator<(string_ref a, string_ref b) noexcept
|
|
{
|
|
const int c = std::memcmp(a.m_data, b.m_data, (std::min)(a.m_size, b.m_size));
|
|
return c != 0 ? c < 0 : a.m_size < b.m_size;
|
|
}
|
|
#ifndef JSON_NO_IO
|
|
friend std::ostream& operator<<(std::ostream& o, string_ref s)
|
|
{
|
|
return o.write(s.m_data, static_cast<std::streamsize>(s.m_size));
|
|
}
|
|
#endif
|
|
|
|
private:
|
|
const char* m_data = "";
|
|
std::size_t m_size = 0;
|
|
};
|
|
#endif
|
|
|
|
} // namespace view
|
|
} // namespace detail
|
|
NLOHMANN_JSON_NAMESPACE_END
|
|
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
|
|
template<typename BasicJsonType>
|
|
class basic_json_document;
|
|
|
|
/*!
|
|
@brief read-only handle to one value of a basic_json_document
|
|
|
|
Trivially copyable (two pointers). Valid as long as the document is alive and
|
|
has not been re-parsed, and as long as a borrowed source text is alive.
|
|
*/
|
|
template<typename BasicJsonType>
|
|
class basic_json_view
|
|
{
|
|
using node = detail::view::node;
|
|
using document_data = detail::view::document_data;
|
|
|
|
public:
|
|
using value_t = detail::value_t;
|
|
using string_t = typename BasicJsonType::string_t;
|
|
using number_integer_t = typename BasicJsonType::number_integer_t;
|
|
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
|
using number_float_t = typename BasicJsonType::number_float_t;
|
|
using json_pointer = typename BasicJsonType::json_pointer;
|
|
using size_type = std::size_t;
|
|
/// std::string_view from C++17 on
|
|
using string_view_t = detail::view::string_ref;
|
|
|
|
/// an invalid view (type() == value_t::discarded)
|
|
basic_json_view() noexcept = default;
|
|
|
|
//////////
|
|
// type //
|
|
//////////
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE value_t type() const noexcept
|
|
{
|
|
return m_node != nullptr ? static_cast<value_t>(m_node->kind) : value_t::discarded;
|
|
}
|
|
|
|
bool is_null() const noexcept
|
|
{
|
|
return type() == value_t::null;
|
|
}
|
|
|
|
bool is_boolean() const noexcept
|
|
{
|
|
return type() == value_t::boolean;
|
|
}
|
|
|
|
bool is_number() const noexcept
|
|
{
|
|
return is_number_integer() || is_number_float();
|
|
}
|
|
|
|
bool is_number_integer() const noexcept
|
|
{
|
|
return type() == value_t::number_integer || type() == value_t::number_unsigned;
|
|
}
|
|
|
|
bool is_number_unsigned() const noexcept
|
|
{
|
|
return type() == value_t::number_unsigned;
|
|
}
|
|
|
|
bool is_number_float() const noexcept
|
|
{
|
|
return type() == value_t::number_float;
|
|
}
|
|
|
|
bool is_string() const noexcept
|
|
{
|
|
return type() == value_t::string;
|
|
}
|
|
|
|
bool is_array() const noexcept
|
|
{
|
|
return type() == value_t::array;
|
|
}
|
|
|
|
bool is_object() const noexcept
|
|
{
|
|
return type() == value_t::object;
|
|
}
|
|
|
|
/// always false: JSON text has no binary values
|
|
bool is_binary() const noexcept
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool is_primitive() const noexcept
|
|
{
|
|
return is_null() || is_string() || is_boolean() || is_number();
|
|
}
|
|
|
|
bool is_structured() const noexcept
|
|
{
|
|
return is_array() || is_object();
|
|
}
|
|
|
|
/// the root of a failed parse with allow_exceptions == false, or a
|
|
/// default-constructed view
|
|
bool is_discarded() const noexcept
|
|
{
|
|
return type() == value_t::discarded;
|
|
}
|
|
|
|
/// false for discarded views
|
|
explicit operator bool() const noexcept
|
|
{
|
|
return m_node != nullptr;
|
|
}
|
|
|
|
//////////////
|
|
// capacity //
|
|
//////////////
|
|
|
|
/// the number of elements (arrays, objects), 0 for null and discarded,
|
|
/// 1 otherwise, as basic_json::size()
|
|
size_type size() const noexcept
|
|
{
|
|
switch (type())
|
|
{
|
|
case value_t::null:
|
|
case value_t::discarded:
|
|
return 0;
|
|
case value_t::array:
|
|
case value_t::object:
|
|
return m_node->len;
|
|
case value_t::string:
|
|
case value_t::boolean:
|
|
case value_t::number_integer:
|
|
case value_t::number_unsigned:
|
|
case value_t::number_float:
|
|
case value_t::binary:
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/// as basic_json::empty()
|
|
bool empty() const noexcept
|
|
{
|
|
switch (type())
|
|
{
|
|
case value_t::null:
|
|
case value_t::discarded:
|
|
return true;
|
|
case value_t::array:
|
|
case value_t::object:
|
|
return m_node->len == 0;
|
|
case value_t::string:
|
|
case value_t::boolean:
|
|
case value_t::number_integer:
|
|
case value_t::number_unsigned:
|
|
case value_t::number_float:
|
|
case value_t::binary:
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/////////////////
|
|
// materialize //
|
|
/////////////////
|
|
|
|
/// the basic_json value of this subtree, as parse() would produce it
|
|
/// (a discarded value for a discarded view)
|
|
BasicJsonType materialize() const
|
|
{
|
|
if (m_node == nullptr)
|
|
{
|
|
return BasicJsonType(value_t::discarded);
|
|
}
|
|
return detail::view::materialize<BasicJsonType>(*m_doc, m_node);
|
|
}
|
|
|
|
/// byte offset of this value in the source text (for strings: of the
|
|
/// first byte after the opening quote); static_cast<std::size_t>(-1) for
|
|
/// a discarded view and for strings with escapes, which are decoded
|
|
std::size_t source_offset() const noexcept
|
|
{
|
|
return m_node != nullptr && (m_node->flags & detail::view::node_flags::storage) == 0
|
|
? m_node->off : static_cast<std::size_t>(-1);
|
|
}
|
|
|
|
private:
|
|
template<typename> friend class basic_json_document;
|
|
|
|
basic_json_view(const document_data* d, const node* n) noexcept
|
|
: m_doc(d), m_node(n)
|
|
{}
|
|
|
|
const document_data* m_doc = nullptr;
|
|
const node* m_node = nullptr;
|
|
};
|
|
|
|
/*!
|
|
@brief a parsed JSON text: owns the node index (and, optionally, the text)
|
|
|
|
Borrowed parses keep a pointer to the caller's text, which must outlive the
|
|
document. Owned parses (parse_copy, rvalue std::string, streams, and inputs
|
|
that are not contiguous byte ranges) keep their own copy.
|
|
*/
|
|
template<typename BasicJsonType>
|
|
class basic_json_document
|
|
{
|
|
using document_data = detail::view::document_data;
|
|
|
|
static_assert(sizeof(typename BasicJsonType::number_integer_t) == 8 && sizeof(typename BasicJsonType::number_unsigned_t) == 8,
|
|
"json_view supports 64-bit integer types only");
|
|
|
|
public:
|
|
using view_type = basic_json_view<BasicJsonType>;
|
|
using value_t = detail::value_t;
|
|
|
|
/// an empty (discarded) document
|
|
basic_json_document() = default;
|
|
basic_json_document(basic_json_document&&) noexcept = default;
|
|
basic_json_document& operator=(basic_json_document&&) noexcept = default;
|
|
basic_json_document(const basic_json_document&) = delete;
|
|
basic_json_document& operator=(const basic_json_document&) = delete;
|
|
~basic_json_document() = default;
|
|
|
|
/////////////
|
|
// parsing //
|
|
/////////////
|
|
|
|
/// parse a JSON text; contiguous byte inputs are borrowed, everything else
|
|
/// (and rvalue std::string) is owned
|
|
template<typename InputType>
|
|
NLOHMANN_VIEW_NODISCARD
|
|
static basic_json_document parse(InputType&& input,
|
|
const bool allow_exceptions = true,
|
|
const bool ignore_comments = false,
|
|
const bool ignore_trailing_commas = false)
|
|
{
|
|
basic_json_document d;
|
|
d.read(std::forward<InputType>(input), allow_exceptions, ignore_comments, ignore_trailing_commas);
|
|
return d;
|
|
}
|
|
|
|
/// parse [first, last)
|
|
template<typename IteratorType, typename std::enable_if<
|
|
std::is_base_of<std::input_iterator_tag, typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0>
|
|
NLOHMANN_VIEW_NODISCARD
|
|
static basic_json_document parse(IteratorType first, IteratorType last,
|
|
const bool allow_exceptions = true,
|
|
const bool ignore_comments = false,
|
|
const bool ignore_trailing_commas = false)
|
|
{
|
|
basic_json_document d;
|
|
d.read_range(first, last, allow_exceptions, ignore_comments, ignore_trailing_commas);
|
|
return d;
|
|
}
|
|
|
|
/// parse a copy of the input; the document does not depend on it afterwards
|
|
template<typename InputType>
|
|
NLOHMANN_VIEW_NODISCARD
|
|
static basic_json_document parse_copy(InputType&& input,
|
|
const bool allow_exceptions = true,
|
|
const bool ignore_comments = false,
|
|
const bool ignore_trailing_commas = false)
|
|
{
|
|
basic_json_document d;
|
|
d.build_owned(collect(std::forward<InputType>(input)), allow_exceptions, ignore_comments, ignore_trailing_commas);
|
|
return d;
|
|
}
|
|
|
|
/// check whether the input is valid JSON (the result of basic_json::accept)
|
|
template<typename InputType>
|
|
static bool accept(InputType&& input, const bool ignore_comments = false, const bool ignore_trailing_commas = false)
|
|
{
|
|
basic_json_document d;
|
|
d.read(std::forward<InputType>(input), false, ignore_comments, ignore_trailing_commas);
|
|
return !d.is_discarded();
|
|
}
|
|
|
|
/// parse into this document, reusing its memory
|
|
template<typename InputType>
|
|
void read(InputType&& input,
|
|
const bool allow_exceptions = true,
|
|
const bool ignore_comments = false,
|
|
const bool ignore_trailing_commas = false)
|
|
{
|
|
read_kind(std::forward<InputType>(input), allow_exceptions, ignore_comments, ignore_trailing_commas,
|
|
std::integral_constant<detail::view::input_kind, detail::view::classify_input<InputType>::value> {});
|
|
}
|
|
|
|
////////////
|
|
// access //
|
|
////////////
|
|
|
|
/// the root value (discarded if parsing failed without exceptions)
|
|
view_type root() const noexcept
|
|
{
|
|
if (!m_data || m_data->discarded)
|
|
{
|
|
return view_type();
|
|
}
|
|
return view_type(m_data.get(), m_data->tape);
|
|
}
|
|
|
|
bool is_discarded() const noexcept
|
|
{
|
|
return !m_data || m_data->discarded;
|
|
}
|
|
|
|
/// the parsed text
|
|
typename view_type::string_view_t source() const noexcept
|
|
{
|
|
return m_data ? typename view_type::string_view_t(m_data->src, m_data->size) : typename view_type::string_view_t();
|
|
}
|
|
|
|
/// whether the document holds its own copy of the text
|
|
bool owns_source() const noexcept
|
|
{
|
|
return m_data && !m_data->owned.empty() && m_data->src == m_data->owned.data();
|
|
}
|
|
|
|
/// number of index nodes (values plus object keys)
|
|
std::size_t node_count() const noexcept
|
|
{
|
|
return m_data ? m_data->tape_size : 0;
|
|
}
|
|
|
|
/// bytes held by the document (index, decoded strings, owned text)
|
|
std::size_t memory_usage() const noexcept
|
|
{
|
|
if (!m_data)
|
|
{
|
|
return 0;
|
|
}
|
|
return sizeof(document_data) + (m_data->inline_cap * sizeof(detail::view::node))
|
|
+ (m_data->tape != m_data->inline_tape ? m_data->tape_cap * sizeof(detail::view::node) : 0)
|
|
+ m_data->arena.capacity() + m_data->owned.capacity();
|
|
}
|
|
|
|
/// release unused capacity of the index and the decoded strings; like
|
|
/// std::vector::shrink_to_fit, this invalidates the views of the document
|
|
/// (take new ones from root())
|
|
void shrink_to_fit()
|
|
{
|
|
if (!m_data)
|
|
{
|
|
return;
|
|
}
|
|
using detail::view::node;
|
|
document_data& d = *m_data;
|
|
|
|
// allocate everything first, so that an exception leaves the document
|
|
// unchanged
|
|
const bool shrink_arena = d.arena.capacity() > d.arena.size();
|
|
std::string arena(shrink_arena ? d.arena : std::string());
|
|
const bool shrink_tape = d.tape != d.inline_tape && d.tape_size != d.tape_cap;
|
|
const bool into_header = d.tape_size <= d.inline_cap;
|
|
node* fresh = (shrink_tape && !into_header) ? static_cast<node*>(::operator new (d.tape_size * sizeof(node))) : d.inline_tape;
|
|
|
|
if (shrink_tape)
|
|
{
|
|
std::memcpy(fresh, d.tape, d.tape_size * sizeof(node));
|
|
::operator delete (d.tape);
|
|
d.tape = fresh;
|
|
d.tape_cap = into_header ? d.inline_cap : d.tape_size;
|
|
}
|
|
if (shrink_arena)
|
|
{
|
|
d.arena.swap(arena);
|
|
d.base[1] = d.arena.data();
|
|
}
|
|
}
|
|
|
|
private:
|
|
using input_kind = detail::view::input_kind;
|
|
|
|
/// create the storage (sized for the input) on first use
|
|
void ensure_data(const char* src, std::size_t size)
|
|
{
|
|
if (!m_data)
|
|
{
|
|
m_data.reset(document_data::create(detail::view::estimate_nodes(src, size)));
|
|
}
|
|
}
|
|
|
|
/// parse a buffer the document takes ownership of
|
|
void build_owned(std::string&& buf, bool allow_exceptions, bool comments, bool trailing_commas)
|
|
{
|
|
ensure_data(buf.data(), buf.size());
|
|
m_data->owned = std::move(buf);
|
|
build(m_data->owned.data(), m_data->owned.size(), allow_exceptions, comments, trailing_commas, true, true);
|
|
}
|
|
|
|
/// sentinel: src[size] is readable and 0 (std::string, C strings)
|
|
void build(const char* src, std::size_t size, bool allow_exceptions, bool comments, bool trailing_commas, bool owned, bool sentinel)
|
|
{
|
|
ensure_data(src, size);
|
|
document_data& d = *m_data;
|
|
if (!owned)
|
|
{
|
|
d.owned.clear();
|
|
}
|
|
d.src = src;
|
|
d.size = size;
|
|
d.tape_size = 0;
|
|
d.arena.clear();
|
|
d.discarded = true;
|
|
detail::view::parse_failure failure;
|
|
bool ok = false;
|
|
if (NLOHMANN_VIEW_UNLIKELY(size >= 0xFFFFFFF0u))
|
|
{
|
|
failure.code = detail::view::error_code::input_too_large;
|
|
}
|
|
else
|
|
{
|
|
ok = detail::view::build < !detail::abi_config::strict_nul_handling > (d, src, size, comments, trailing_commas, sentinel, failure);
|
|
}
|
|
if (NLOHMANN_VIEW_LIKELY(ok))
|
|
{
|
|
d.base[0] = d.src;
|
|
d.base[1] = d.arena.data();
|
|
d.discarded = false;
|
|
return;
|
|
}
|
|
if (allow_exceptions)
|
|
{
|
|
detail::view::throw_parse_failure<BasicJsonType>(failure, src, size, comments, trailing_commas);
|
|
}
|
|
}
|
|
|
|
// --- input dispatch (see detail::view::input_kind) ---
|
|
|
|
void read_kind(std::string&& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::move_string> /*unused*/)
|
|
{
|
|
build_owned(std::move(s), ae, c, tc);
|
|
}
|
|
|
|
template<typename CharT>
|
|
void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::c_string> /*unused*/)
|
|
{
|
|
static_assert(sizeof(CharT) == 1 && std::is_integral<typename std::remove_cv<CharT>::type>::value, "json_view parses byte (char-like) input");
|
|
if (s == nullptr)
|
|
{
|
|
build("", 0, ae, c, tc, false, true);
|
|
return;
|
|
}
|
|
const char* cs = reinterpret_cast<const char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
build(cs, std::strlen(cs), ae, c, tc, false, true);
|
|
}
|
|
|
|
template<typename Array>
|
|
void read_kind(Array& a, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::char_array> /*unused*/)
|
|
{
|
|
using CharT = typename std::remove_cv<typename std::remove_extent<Array>::type>::type;
|
|
static_assert(sizeof(CharT) == 1 && std::is_integral<CharT>::value, "json_view parses byte (char-like) input");
|
|
const std::size_t n = std::extent<Array>::value;
|
|
// a trailing NUL (string literals) is not part of the text, as for
|
|
// parse(), and serves as sentinel
|
|
const bool terminated = n > 0 && a[n - 1] == 0 && (!detail::abi_config::strict_nul_handling || std::is_same<CharT, char>::value);
|
|
build(reinterpret_cast<const char*>(&a[0]), terminated ? n - 1 : n, ae, c, tc, false, terminated); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
}
|
|
|
|
template<typename T>
|
|
void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::borrow_range> /*unused*/)
|
|
{
|
|
// std::basic_string guarantees data()[size()] == 0: use it as sentinel
|
|
const auto size = static_cast<std::size_t>(s.size());
|
|
build(size == 0 ? "" : reinterpret_cast<const char*>(s.data()), size, ae, c, tc, false, // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
detail::view::is_std_string<T>::value || size == 0);
|
|
}
|
|
|
|
template<typename T>
|
|
void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::copy_range> /*unused*/)
|
|
{
|
|
build_owned(std::string(reinterpret_cast<const char*>(s.data()), static_cast<std::size_t>(s.size())), ae, c, tc); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
}
|
|
|
|
template<typename T>
|
|
void read_kind(T&& input, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::adapter> /*unused*/)
|
|
{
|
|
build_owned(detail::view::collect_adapter(detail::input_adapter(std::forward<T>(input))), ae, c, tc);
|
|
}
|
|
|
|
template<typename IteratorType>
|
|
void read_range(IteratorType first, IteratorType last, bool ae, bool c, bool tc)
|
|
{
|
|
using value_type = typename std::remove_cv<typename std::iterator_traits<IteratorType>::value_type>::type;
|
|
// borrow the range where the library's input adapter scans it as one
|
|
// block: pointers and, in C++20, contiguous iterators (std::vector,
|
|
// std::string, ...) over single bytes
|
|
read_range_impl(first, last, ae, c, tc, std::integral_constant < bool, std::is_integral<value_type>::value
|
|
&& detail::iterator_input_adapter<IteratorType>::supports_bulk_scan > {});
|
|
}
|
|
|
|
template<typename IteratorType>
|
|
void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::true_type /*contiguous bytes*/)
|
|
{
|
|
const auto size = static_cast<std::size_t>(std::distance(first, last));
|
|
build(size == 0 ? "" : reinterpret_cast<const char*>(&*first), size, ae, c, tc, false, size == 0); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
}
|
|
|
|
template<typename IteratorType>
|
|
void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::false_type /*other*/)
|
|
{
|
|
build_owned(detail::view::collect_adapter(detail::input_adapter(first, last)), ae, c, tc);
|
|
}
|
|
|
|
template<typename T>
|
|
static std::string collect(T&& input)
|
|
{
|
|
return collect_impl(std::forward<T>(input), std::integral_constant<bool, detail::is_contiguous_byte_container<typename std::decay<T>::type>::value> {});
|
|
}
|
|
|
|
template<typename T>
|
|
static std::string collect_impl(const T& s, std::true_type /*contiguous*/)
|
|
{
|
|
return {reinterpret_cast<const char*>(s.data()), static_cast<std::size_t>(s.size())}; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
}
|
|
|
|
template<typename T>
|
|
static std::string collect_impl(T&& s, std::false_type /*other*/)
|
|
{
|
|
return detail::view::collect_adapter(detail::input_adapter(std::forward<T>(s)));
|
|
}
|
|
|
|
std::unique_ptr<document_data, document_data::deleter> m_data{}; // NOLINT(readability-redundant-member-init)
|
|
};
|
|
|
|
/// a parsed JSON text for json
|
|
using json_document = basic_json_document<json>;
|
|
/// a value of a json_document
|
|
using json_view = basic_json_view<json>;
|
|
/// a parsed JSON text for ordered_json
|
|
using ordered_json_document = basic_json_document<ordered_json>;
|
|
/// a value of an ordered_json_document
|
|
using ordered_json_view = basic_json_view<ordered_json>;
|
|
|
|
NLOHMANN_JSON_NAMESPACE_END
|
|
|
|
// #include <nlohmann/detail/view/macro_unscope.hpp>
|
|
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
// undefine the macros of detail/view/macro_scope.hpp (at the end of json_view.hpp)
|
|
|
|
#undef NLOHMANN_VIEW_HAS_CPP_17
|
|
#undef NLOHMANN_VIEW_LIKELY
|
|
#undef NLOHMANN_VIEW_UNLIKELY
|
|
#undef NLOHMANN_VIEW_ALWAYS_INLINE
|
|
#undef NLOHMANN_VIEW_NOINLINE
|
|
#undef NLOHMANN_VIEW_NODISCARD
|
|
#undef NLOHMANN_VIEW_THROW
|
|
#undef NLOHMANN_VIEW_LITTLE_ENDIAN
|
|
#undef NLOHMANN_VIEW_REPEAT16
|
|
|
|
|
|
#endif // INCLUDE_NLOHMANN_JSON_VIEW_HPP_
|