From 34f1bff147e884b6223463479a9b52efde5b2002 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 28 Sep 2026 21:32:56 +0200 Subject: [PATCH] Add json_document and json_view: parse, accept, types, materialize The public classes of the zero-copy view (#5295), in the new header : - basic_json_document: parse (borrowing contiguous byte inputs, owning rvalue strings, streams, and other inputs), parse_copy, accept, read, root, is_discarded, source, owns_source, node_count, memory_usage, shrink_to_fit - basic_json_view: type and the is_* queries, size, empty, materialize (the value parse() would produce, built by the same SAX handler), source_offset - the aliases json_document, json_view, ordered_json_document, and ordered_json_view A parse error throws the exception basic_json::parse would throw for the same input: the library parser is run on the failing input, so messages, positions, and exception ids are the same. Inputs of 4 GiB or more are rejected with out_of_range.416. The single header single_include/nlohmann/json_view.hpp keeps including json.hpp; make amalgamate, check-amalgamation, include.zip, and release handle it. Signed-off-by: Niels Lohmann --- BUILD.bazel | 5 + Makefile | 22 +- include/nlohmann/detail/view/errors.hpp | 81 + include/nlohmann/detail/view/input.hpp | 86 + include/nlohmann/detail/view/macro_scope.hpp | 8 + .../nlohmann/detail/view/macro_unscope.hpp | 1 + include/nlohmann/detail/view/materialize.hpp | 130 + include/nlohmann/detail/view/number.hpp | 67 + include/nlohmann/json_view.hpp | 591 ++++ single_include/nlohmann/json_view.hpp | 2638 +++++++++++++++++ 10 files changed, 3623 insertions(+), 6 deletions(-) create mode 100644 include/nlohmann/detail/view/errors.hpp create mode 100644 include/nlohmann/detail/view/input.hpp create mode 100644 include/nlohmann/detail/view/materialize.hpp create mode 100644 include/nlohmann/detail/view/number.hpp create mode 100644 include/nlohmann/json_view.hpp create mode 100644 single_include/nlohmann/json_view.hpp diff --git a/BUILD.bazel b/BUILD.bazel index 51d6525be..6d98715aa 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -68,13 +68,18 @@ cc_library( "include/nlohmann/detail/value_t.hpp", "include/nlohmann/detail/view/builder.hpp", "include/nlohmann/detail/view/document_data.hpp", + "include/nlohmann/detail/view/errors.hpp", + "include/nlohmann/detail/view/input.hpp", "include/nlohmann/detail/view/macro_scope.hpp", "include/nlohmann/detail/view/macro_unscope.hpp", + "include/nlohmann/detail/view/materialize.hpp", "include/nlohmann/detail/view/node.hpp", + "include/nlohmann/detail/view/number.hpp", "include/nlohmann/detail/view/scan.hpp", "include/nlohmann/detail/view/string_ref.hpp", "include/nlohmann/json.hpp", "include/nlohmann/json_fwd.hpp", + "include/nlohmann/json_view.hpp", "include/nlohmann/ordered_map.hpp", "include/nlohmann/thirdparty/hedley/hedley.hpp", "include/nlohmann/thirdparty/hedley/hedley_undef.hpp", diff --git a/Makefile b/Makefile index 196f87243..c37d3ce67 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,7 @@ TESTS_SRCS=$(shell find tests -type f \( -name '*.hpp' -o -name '*.cpp' -o -name # the single headers (amalgamated from the source files) AMALGAMATED_FILE=single_include/nlohmann/json.hpp AMALGAMATED_FWD_FILE=single_include/nlohmann/json_fwd.hpp +AMALGAMATED_VIEW_FILE=single_include/nlohmann/json_view.hpp ########################################################################## @@ -29,7 +30,7 @@ AMALGAMATED_FWD_FILE=single_include/nlohmann/json_fwd.hpp # main target all: - @echo "amalgamate - amalgamate files single_include/nlohmann/json{,_fwd}.hpp from the include/nlohmann sources" + @echo "amalgamate - amalgamate files single_include/nlohmann/json{,_fwd,_view}.hpp from the include/nlohmann sources" @echo "BUILD.bazel - regenerate the Bazel BUILD file from the include/nlohmann sources" @echo "ChangeLog.md - generate ChangeLog file" @echo "check-amalgamation - check whether sources have been amalgamated and BUILD.bazel is up to date" @@ -154,14 +155,14 @@ install_astyle: # call the Artistic Style pretty printer on all source files pretty: install_astyle - $(ASTYLE) --project=tools/astyle/.astylerc $(SRCS) $(TESTS_SRCS) $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) docs/mkdocs/docs/examples/*.cpp + $(ASTYLE) --project=tools/astyle/.astylerc $(SRCS) $(TESTS_SRCS) $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_VIEW_FILE) docs/mkdocs/docs/examples/*.cpp # call the Clang-Format on all source files pretty_format: for FILE in $(SRCS) $(TESTS_SRCS) $(AMALGAMATED_FILE) docs/mkdocs/docs/examples/*.cpp; do echo $$FILE; clang-format -i $$FILE; done # create single header files and pretty print -amalgamate: $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) +amalgamate: $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_VIEW_FILE) $(MAKE) pretty # call the amalgamation tool for json.hpp @@ -172,16 +173,23 @@ $(AMALGAMATED_FILE): $(SRCS) $(AMALGAMATED_FWD_FILE): $(SRCS) tools/amalgamate/amalgamate.py -c tools/amalgamate/config_json_fwd.json -s . --verbose=yes +# call the amalgamation tool for json_view.hpp (keeps including json.hpp) +$(AMALGAMATED_VIEW_FILE): $(SRCS) + tools/amalgamate/amalgamate.py -c tools/amalgamate/config_json_view.json -s . --verbose=yes + # check if file single_include/nlohmann/json.hpp has been amalgamated from the nlohmann sources # Note: this target is called by Travis check-amalgamation: @mv $(AMALGAMATED_FILE) $(AMALGAMATED_FILE)~ @mv $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_FWD_FILE)~ + @mv $(AMALGAMATED_VIEW_FILE) $(AMALGAMATED_VIEW_FILE)~ @$(MAKE) amalgamate @diff $(AMALGAMATED_FILE) $(AMALGAMATED_FILE)~ || (echo "===================================================================\n Amalgamation required! Please read the contribution guidelines\n in file .github/CONTRIBUTING.md.\n===================================================================" ; mv $(AMALGAMATED_FILE)~ $(AMALGAMATED_FILE) ; false) @diff $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_FWD_FILE)~ || (echo "===================================================================\n Amalgamation required! Please read the contribution guidelines\n in file .github/CONTRIBUTING.md.\n===================================================================" ; mv $(AMALGAMATED_FWD_FILE)~ $(AMALGAMATED_FWD_FILE) ; false) + @diff $(AMALGAMATED_VIEW_FILE) $(AMALGAMATED_VIEW_FILE)~ || (echo "===================================================================\n Amalgamation required! Please read the contribution guidelines\n in file .github/CONTRIBUTING.md.\n===================================================================" ; mv $(AMALGAMATED_VIEW_FILE)~ $(AMALGAMATED_VIEW_FILE) ; false) @mv $(AMALGAMATED_FILE)~ $(AMALGAMATED_FILE) @mv $(AMALGAMATED_FWD_FILE)~ $(AMALGAMATED_FWD_FILE) + @mv $(AMALGAMATED_VIEW_FILE)~ $(AMALGAMATED_VIEW_FILE) @mv BUILD.bazel BUILD.bazel~ @$(MAKE) BUILD.bazel @diff BUILD.bazel BUILD.bazel~ || (echo "===================================================================\n BUILD.bazel is out of date! Please run 'make BUILD.bazel'.\n===================================================================" ; mv BUILD.bazel~ BUILD.bazel ; false) @@ -222,7 +230,7 @@ json.tar.xz: # We use `-X` to make the resulting ZIP file reproducible, see # . include.zip: BUILD.bazel - zip -9 --recurse-paths -X include.zip $(SRCS) $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) BUILD.bazel MODULE.bazel meson.build LICENSE.MIT + zip -9 --recurse-paths -X include.zip $(SRCS) $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_VIEW_FILE) BUILD.bazel MODULE.bazel meson.build LICENSE.MIT # Create the files for a release and add signatures and hashes. release: include.zip json.tar.xz @@ -231,11 +239,13 @@ release: include.zip json.tar.xz gpg --armor --detach-sig include.zip gpg --armor --detach-sig $(AMALGAMATED_FILE) gpg --armor --detach-sig $(AMALGAMATED_FWD_FILE) + gpg --armor --detach-sig $(AMALGAMATED_VIEW_FILE) gpg --armor --detach-sig json.tar.xz cp $(AMALGAMATED_FILE) release_files cp $(AMALGAMATED_FWD_FILE) release_files - mv $(AMALGAMATED_FILE).asc $(AMALGAMATED_FWD_FILE).asc json.tar.xz json.tar.xz.asc include.zip include.zip.asc release_files - cd release_files ; shasum -a 256 json.hpp include.zip json.tar.xz > hashes.txt + cp $(AMALGAMATED_VIEW_FILE) release_files + mv $(AMALGAMATED_FILE).asc $(AMALGAMATED_FWD_FILE).asc $(AMALGAMATED_VIEW_FILE).asc json.tar.xz json.tar.xz.asc include.zip include.zip.asc release_files + cd release_files ; shasum -a 256 json.hpp json_view.hpp include.zip json.tar.xz > hashes.txt ########################################################################## diff --git a/include/nlohmann/detail/view/errors.hpp b/include/nlohmann/detail/view/errors.hpp new file mode 100644 index 000000000..2b48d04b1 --- /dev/null +++ b/include/nlohmann/detail/view/errors.hpp @@ -0,0 +1,81 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#pragma once + +#include // min +#include // size_t +#include // string + +#include +#include +#include + +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 +[[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(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 diff --git a/include/nlohmann/detail/view/input.hpp b/include/nlohmann/detail/view/input.hpp new file mode 100644 index 000000000..c43979f1f --- /dev/null +++ b/include/nlohmann/detail/view/input.hpp @@ -0,0 +1,86 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#pragma once + +#include // basic_string, char_traits, string +#include // decay, integral_constant, is_array, is_lvalue_reference, is_pointer, is_same, remove_reference +#include // forward + +#include +#include + +#if NLOHMANN_VIEW_HAS_CPP_17 + #include // 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 +struct classify_input +{ + using R = typename std::remove_reference::type; + using D = typename std::decay::type; + static constexpr bool is_rvalue = !std::is_lvalue_reference::value; + static constexpr bool is_bytes = is_contiguous_byte_container::value; +#if NLOHMANN_VIEW_HAS_CPP_17 + static constexpr bool is_string_view = std::is_same::value; +#else + static constexpr bool is_string_view = false; +#endif + static constexpr input_kind value = + std::is_array::value ? input_kind::char_array + : std::is_pointer::value ? input_kind::c_string + : (is_rvalue && std::is_same::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; +}; + +/// std::basic_string guarantees a NUL at data()[size()] (the parser's sentinel) +template +struct is_std_string : std::false_type {}; + +template +struct is_std_string> : std::true_type {}; + +/// drain a json input adapter (UTF-16/32 inputs arrive as UTF-8) +template +std::string collect_adapter(Adapter&& ia) +{ + std::string buf; + for (;;) + { + const auto ch = ia.get_character(); + if (ch == std::char_traits::eof()) + { + break; + } + buf.push_back(static_cast(ch)); + } + return buf; +} + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/detail/view/macro_scope.hpp b/include/nlohmann/detail/view/macro_scope.hpp index a2d2c52c3..8bbae1970 100644 --- a/include/nlohmann/detail/view/macro_scope.hpp +++ b/include/nlohmann/detail/view/macro_scope.hpp @@ -37,6 +37,14 @@ #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 diff --git a/include/nlohmann/detail/view/macro_unscope.hpp b/include/nlohmann/detail/view/macro_unscope.hpp index c3a6266a8..11f71ad1b 100644 --- a/include/nlohmann/detail/view/macro_unscope.hpp +++ b/include/nlohmann/detail/view/macro_unscope.hpp @@ -15,6 +15,7 @@ #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 diff --git a/include/nlohmann/detail/view/materialize.hpp b/include/nlohmann/detail/view/materialize.hpp new file mode 100644 index 000000000..4d1e7ab93 --- /dev/null +++ b/include/nlohmann/detail/view/materialize.hpp @@ -0,0 +1,130 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#pragma once + +#include // int64_t, uint8_t +#include // vector + +#include +#include +#include +#include +#include + +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 +BasicJsonType materialize(const document_data& d, const node* n) +{ + using string_t = typename BasicJsonType::string_t; + using sax_t = json_sax_dom_parser>; + + 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> open; + for (;;) + { + switch (static_cast(n->kind)) + { + case value_t::object: + case value_t::array: + { + const bool object = n->kind == static_cast(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(static_cast(integer_bits(*n)))); + ++n; + break; + case value_t::number_unsigned: + sax.number_unsigned(static_cast(integer_bits(*n))); + ++n; + break; + case value_t::number_float: + sax.number_float(float_value(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 diff --git a/include/nlohmann/detail/view/number.hpp b/include/nlohmann/detail/view/number.hpp new file mode 100644 index 000000000..c9befdd49 --- /dev/null +++ b/include/nlohmann/detail/view/number.hpp @@ -0,0 +1,67 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#pragma once + +#include // size_t +#include // string + +#include +#include +#include + +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), so that the values are +bit-identical to parse(): float and double are converted without allocation +and independent of the locale. The digit layout recorded while parsing locates +the decimal point and the exponent without scanning the token. +*/ +template +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; + } + } + } + return convert_float(first, last, dot, mantissa_end); +} + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp new file mode 100644 index 000000000..544e54d99 --- /dev/null +++ b/include/nlohmann/json_view.hpp @@ -0,0 +1,591 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// 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 // size_t +#include // memcpy, strlen +#include // distance, input_iterator_tag, iterator_traits +#include // unique_ptr +#include // string +#include // enable_if, integral_constant, is_base_of, is_integral, is_same, remove_cv, remove_extent +#include // forward, move + +#include + +// 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 +#include +#include +#include +#include +#include +#include +#include + +NLOHMANN_JSON_NAMESPACE_BEGIN + +template +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 +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(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(*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(-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(-1); + } + + private: + template 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 +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; + 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 + 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(input), allow_exceptions, ignore_comments, ignore_trailing_commas); + return d; + } + + /// parse [first, last) + template::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 + 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(input)), allow_exceptions, ignore_comments, ignore_trailing_commas); + return d; + } + + /// check whether the input is valid JSON (the result of basic_json::accept) + template + static bool accept(InputType&& input, const bool ignore_comments = false, const bool ignore_trailing_commas = false) + { + basic_json_document d; + d.read(std::forward(input), false, ignore_comments, ignore_trailing_commas); + return !d.is_discarded(); + } + + /// parse into this document, reusing its memory + template + void read(InputType&& input, + const bool allow_exceptions = true, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) + { + read_kind(std::forward(input), allow_exceptions, ignore_comments, ignore_trailing_commas, + std::integral_constant::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 node = 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(::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 < typename BasicJsonType::number_float_t, !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(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 /*unused*/) + { + build_owned(std::move(s), ae, c, tc); + } + + template + void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + { + static_assert(sizeof(CharT) == 1 && std::is_integral::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(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + build(cs, std::strlen(cs), ae, c, tc, false, true); + } + + template + void read_kind(Array& a, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + { + using CharT = typename std::remove_cv::type>::type; + static_assert(sizeof(CharT) == 1 && std::is_integral::value, "json_view parses byte (char-like) input"); + const std::size_t n = std::extent::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::value); + build(reinterpret_cast(&a[0]), terminated ? n - 1 : n, ae, c, tc, false, terminated); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + } + + template + void read_kind(T&& s, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + { + // std::basic_string guarantees data()[size()] == 0: use it as sentinel + build(s.size() == 0 ? "" : reinterpret_cast(s.data()), static_cast(s.size()), ae, c, tc, false, // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + detail::view::is_std_string::type>::value || s.size() == 0); + } + + template + void read_kind(T&& s, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + { + build_owned(std::string(reinterpret_cast(s.data()), static_cast(s.size())), ae, c, tc); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + } + + template + void read_kind(T&& input, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + { + build_owned(detail::view::collect_adapter(detail::input_adapter(std::forward(input))), ae, c, tc); + } + + template + void read_range(IteratorType first, IteratorType last, bool ae, bool c, bool tc) + { + using value_type = typename std::remove_cv::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 + && detail::iterator_input_adapter::supports_bulk_scan > {}); + } + + template + 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::distance(first, last)); + build(size == 0 ? "" : reinterpret_cast(&*first), size, ae, c, tc, false, size == 0); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + } + + template + 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 + static std::string collect(T&& input) + { + return collect_impl(std::forward(input), std::integral_constant::type>::value> {}); + } + + template + static std::string collect_impl(T&& s, std::true_type /*contiguous*/) + { + return std::string(reinterpret_cast(s.data()), static_cast(s.size())); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + } + + template + static std::string collect_impl(T&& s, std::false_type /*other*/) + { + return detail::view::collect_adapter(detail::input_adapter(std::forward(s))); + } + + std::unique_ptr m_data{}; +}; + +/// a parsed JSON text for json +using json_document = basic_json_document; +/// a value of a json_document +using json_view = basic_json_view; +/// a parsed JSON text for ordered_json +using ordered_json_document = basic_json_document; +/// a value of an ordered_json_document +using ordered_json_view = basic_json_view; + +NLOHMANN_JSON_NAMESPACE_END + +#include + +#endif // INCLUDE_NLOHMANN_JSON_VIEW_HPP_ diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp new file mode 100644 index 000000000..4c011d238 --- /dev/null +++ b/single_include/nlohmann/json_view.hpp @@ -0,0 +1,2638 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// 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 // size_t +#include // memcpy, strlen +#include // distance, input_iterator_tag, iterator_traits +#include // unique_ptr +#include // string +#include // enable_if, integral_constant, is_base_of, is_integral, is_same, remove_cv, remove_extent +#include // forward, move + +#include + +// 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 +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-FileCopyrightText: 2020 YaoYuan +// SPDX-License-Identifier: MIT + + + +#include // find, find_if, max +#include // array +#include // size_t, ptrdiff_t +#include // int64_t, uint8_t, uint16_t, uint32_t, uint64_t +#include // memcmp, memcpy +#include // numeric_limits +#include // string +#include // vector + +// #include +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // array +#include // size_t +#include // memcpy +#include // operator new, placement new +#include // string + +// #include +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// 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 + // (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(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 +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // size_t +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // memcpy + +// #include +// #include + + +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(value_t::null) == 0 && static_cast(value_t::object) == 1 + && static_cast(value_t::array) == 2 && static_cast(value_t::string) == 3 + && static_cast(value_t::boolean) == 4 && static_cast(value_t::number_integer) == 5 + && static_cast(value_t::number_unsigned) == 6 && static_cast(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(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(&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(&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(value_t::number_float) ? n.len + : (n.extra & 0xFFu) + (n.kind == static_cast(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 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(static_cast(static_cast(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(::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 + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-FileCopyrightText: 2020 YaoYuan +// SPDX-License-Identifier: MIT + + + +#include // array +#include // size_t +#include // uint8_t, uint16_t, uint64_t +#include // memcpy + +// #include +// #include + + +// 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 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(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(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 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 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(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 + + +// 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 +}; + +/// FloatType: the number_float_t of the document, whose overflow parse() rejects +template +class builder +{ + public: + builder(document_data& d, const char* src, std::size_t size) noexcept + : doc(d) + , b(reinterpret_cast(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 deep{}; + + NLOHMANN_VIEW_NOINLINE bool fail(error_code c, const unsigned char* at) noexcept + { + m_failure.code = c; + m_failure.offset = static_cast(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(at - b) + 1; + const std::uint64_t guess = static_cast(n) * static_cast(e - b + 1) / done; + const std::uint64_t grown = guess + (guess / 4) + 64; // a variable: GCC calls a cast of the sum useless where std::uint64_t is std::size_t + doc.tape_size = n; + doc.reserve((std::max)(static_cast(grown), 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 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(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 != '\\') + { + // (a NUL before the end of the input is a control character, as + // for json::parse, also where a NUL ends the input between values) + 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(p - r)); + } + } + + /// does the float token [s, p) overflow FloatType? (parse() rejects it) + NLOHMANN_VIEW_NOINLINE static bool float_overflows(const unsigned char* s, const unsigned char* p) + { + const auto* const first = reinterpret_cast(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + const auto* const last = reinterpret_cast(p); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + const char* const dot = std::find(first, last, '.'); + const char* const exponent = std::find_if(first, last, [](char c) + { + return c == 'e' || c == 'E'; + }); + const auto v = convert_float(first, last, dot == last ? std::string::npos : static_cast(dot - first), + static_cast(exponent - first)); + return v > (std::numeric_limits::max)() || v < -(std::numeric_limits::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(aw - doc.arena.data()) : 0; + } + + NLOHMANN_VIEW_ALWAYS_INLINE void arena_ensure(std::size_t n) + { + if (NLOHMANN_VIEW_UNLIKELY(static_cast(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(cp); + } + else if (cp < 0x800) + { + *w++ = static_cast(0xC0 | (cp >> 6)); + *w++ = static_cast(0x80 | (cp & 0x3F)); + } + else if (cp < 0x10000) + { + *w++ = static_cast(0xE0 | (cp >> 12)); + *w++ = static_cast(0x80 | ((cp >> 6) & 0x3F)); + *w++ = static_cast(0x80 | (cp & 0x3F)); + } + else + { + *w++ = static_cast(0xF0 | (cp >> 18)); + *w++ = static_cast(0x80 | ((cp >> 12) & 0x3F)); + *w++ = static_cast(0x80 | ((cp >> 6) & 0x3F)); + *w++ = static_cast(0x80 | (cp & 0x3F)); + } + return w; + } + + /// a compile-time option as a runtime condition: testing the template + /// argument directly makes a condition like `TrailingCommas && c == ']'` + /// constant when the option is off, which MSVC reports as C4127 + static NLOHMANN_VIEW_ALWAYS_INLINE bool enabled(bool option) noexcept + { + return option; + } + + /// 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(b), static_cast(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())) { 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())) { 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 (enabled(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 (enabled(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(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(); + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return number(); + 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 (enabled(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(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(k) | (static_cast(flags) << 8) + | (static_cast(extra) << 16) | (static_cast(off) << 32); + std::memcpy(reinterpret_cast(n), &first, 8); + std::memcpy(reinterpret_cast(n) + 8, &second, 8); +#else + n->kind = static_cast(k); + n->flags = flags; + n->extra = extra; + n->off = static_cast(off); + set_integer_bits(*n, second); +#endif + return n; + } + + NLOHMANN_VIEW_ALWAYS_INLINE void open(value_t k) + { + const auto idx = static_cast(emit(k, 0, 0, static_cast(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(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(n) || std::memcmp(p, text, n) != 0)) + { + return fail(error_code::invalid_literal); + } + emit(k, flags, 0, static_cast(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 + 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(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(p - f0); + is_float = true; + } + std::int64_t 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 the largest FloatType (1e308 for double, 1e38 for + // float) need the conversion + if (NLOHMANN_VIEW_UNLIKELY(static_cast(int_digits) + exponent > std::numeric_limits::max_exponent10 - 8 && kind == value_t::number_float)) + { + if (builder::float_overflows(s, p)) + { + p = s; + return fail(error_code::number_overflow); + } + } + const auto layout = static_cast((int_digits < 255 ? int_digits : 255) | ((frac_digits < 255 ? frac_digits : 255) << 8)); + auto second = static_cast(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(int_digits), e) + : (parse_upto19(int_start, 19, e) * 10) + static_cast(int_start[19] - '0'); + second = negative ? 0 - m : m; + } + emit(kind, 0, layout, static_cast(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(s - b), static_cast(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 +inline bool build_with(document_data& d, const char* src, std::size_t size, bool sentinel, parse_failure& failure) +{ + if (sentinel) + { + builder bld(d, src, size); + const bool ok = bld.run(); + failure = bld.failure(); + return ok; + } + builder 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); FloatType: the +/// number_float_t of the document +template +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(d, src, size, sentinel, failure) + : build_with(d, src, size, sentinel, failure); + } + return trailing_commas ? build_with(d, src, size, sentinel, failure) + : build_with(d, src, size, sentinel, failure); +} + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // min +#include // size_t +#include // string + +// #include +// #include + +// #include + + +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 +[[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(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 +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // basic_string, char_traits, string +#include // decay, integral_constant, is_array, is_lvalue_reference, is_pointer, is_same, remove_reference +#include // forward + +// #include +// #include + + +#if NLOHMANN_VIEW_HAS_CPP_17 + #include // 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 +struct classify_input +{ + using R = typename std::remove_reference::type; + using D = typename std::decay::type; + static constexpr bool is_rvalue = !std::is_lvalue_reference::value; + static constexpr bool is_bytes = is_contiguous_byte_container::value; +#if NLOHMANN_VIEW_HAS_CPP_17 + static constexpr bool is_string_view = std::is_same::value; +#else + static constexpr bool is_string_view = false; +#endif + static constexpr input_kind value = + std::is_array::value ? input_kind::char_array + : std::is_pointer::value ? input_kind::c_string + : (is_rvalue && std::is_same::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; +}; + +/// std::basic_string guarantees a NUL at data()[size()] (the parser's sentinel) +template +struct is_std_string : std::false_type {}; + +template +struct is_std_string> : std::true_type {}; + +/// drain a json input adapter (UTF-16/32 inputs arrive as UTF-8) +template +std::string collect_adapter(Adapter&& ia) +{ + std::string buf; + for (;;) + { + const auto ch = ia.get_character(); + if (ch == std::char_traits::eof()) + { + break; + } + buf.push_back(static_cast(ch)); + } + return buf; +} + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // int64_t, uint8_t +#include // vector + +// #include +// #include + +// #include + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // size_t +#include // string + +// #include +// #include + +// #include + + +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), so that the values are +bit-identical to parse(): float and double are converted without allocation +and independent of the locale. The digit layout recorded while parsing locates +the decimal point and the exponent without scanning the token. +*/ +template +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; + } + } + } + return convert_float(first, last, dot, mantissa_end); +} + +} // 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 +BasicJsonType materialize(const document_data& d, const node* n) +{ + using string_t = typename BasicJsonType::string_t; + using sax_t = json_sax_dom_parser>; + + 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> open; + for (;;) + { + switch (static_cast(n->kind)) + { + case value_t::object: + case value_t::array: + { + const bool object = n->kind == static_cast(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(static_cast(integer_bits(*n)))); + ++n; + break; + case value_t::number_unsigned: + sax.number_unsigned(static_cast(integer_bits(*n))); + ++n; + break; + case value_t::number_float: + sax.number_float(float_value(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 + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // min +#include // size_t +#include // memcmp, strlen +#include // basic_string + +// #include +// #include + + +#if NLOHMANN_VIEW_HAS_CPP_17 + #include // string_view +#endif +#ifndef JSON_NO_IO + #include // 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 + string_ref(const std::basic_string& 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 + explicit operator std::basic_string() const + { + return std::basic_string(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(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 +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 +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(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(*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(-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(-1); + } + + private: + template 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 +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; + 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 + 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(input), allow_exceptions, ignore_comments, ignore_trailing_commas); + return d; + } + + /// parse [first, last) + template::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 + 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(input)), allow_exceptions, ignore_comments, ignore_trailing_commas); + return d; + } + + /// check whether the input is valid JSON (the result of basic_json::accept) + template + static bool accept(InputType&& input, const bool ignore_comments = false, const bool ignore_trailing_commas = false) + { + basic_json_document d; + d.read(std::forward(input), false, ignore_comments, ignore_trailing_commas); + return !d.is_discarded(); + } + + /// parse into this document, reusing its memory + template + void read(InputType&& input, + const bool allow_exceptions = true, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) + { + read_kind(std::forward(input), allow_exceptions, ignore_comments, ignore_trailing_commas, + std::integral_constant::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 node = 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(::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 < typename BasicJsonType::number_float_t, !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(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 /*unused*/) + { + build_owned(std::move(s), ae, c, tc); + } + + template + void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + { + static_assert(sizeof(CharT) == 1 && std::is_integral::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(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + build(cs, std::strlen(cs), ae, c, tc, false, true); + } + + template + void read_kind(Array& a, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + { + using CharT = typename std::remove_cv::type>::type; + static_assert(sizeof(CharT) == 1 && std::is_integral::value, "json_view parses byte (char-like) input"); + const std::size_t n = std::extent::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::value); + build(reinterpret_cast(&a[0]), terminated ? n - 1 : n, ae, c, tc, false, terminated); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + } + + template + void read_kind(T&& s, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + { + // std::basic_string guarantees data()[size()] == 0: use it as sentinel + build(s.size() == 0 ? "" : reinterpret_cast(s.data()), static_cast(s.size()), ae, c, tc, false, // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + detail::view::is_std_string::type>::value || s.size() == 0); + } + + template + void read_kind(T&& s, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + { + build_owned(std::string(reinterpret_cast(s.data()), static_cast(s.size())), ae, c, tc); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + } + + template + void read_kind(T&& input, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + { + build_owned(detail::view::collect_adapter(detail::input_adapter(std::forward(input))), ae, c, tc); + } + + template + void read_range(IteratorType first, IteratorType last, bool ae, bool c, bool tc) + { + using value_type = typename std::remove_cv::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 + && detail::iterator_input_adapter::supports_bulk_scan > {}); + } + + template + 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::distance(first, last)); + build(size == 0 ? "" : reinterpret_cast(&*first), size, ae, c, tc, false, size == 0); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + } + + template + 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 + static std::string collect(T&& input) + { + return collect_impl(std::forward(input), std::integral_constant::type>::value> {}); + } + + template + static std::string collect_impl(T&& s, std::true_type /*contiguous*/) + { + return std::string(reinterpret_cast(s.data()), static_cast(s.size())); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + } + + template + static std::string collect_impl(T&& s, std::false_type /*other*/) + { + return detail::view::collect_adapter(detail::input_adapter(std::forward(s))); + } + + std::unique_ptr m_data{}; +}; + +/// a parsed JSON text for json +using json_document = basic_json_document; +/// a value of a json_document +using json_view = basic_json_view; +/// a parsed JSON text for ordered_json +using ordered_json_document = basic_json_document; +/// a value of an ordered_json_document +using ordered_json_view = basic_json_view; + +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// 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_