Ajout du schéma JSON pour lecture de scènes + ajout des composants son avec OpenAL + composants scripts et libraire de script + ajout de librairies pour le son dr_libs et openAL + librairie schéma json validator + correctifs divers d'oubli et autres + entity et components factory fonctionnelles + rework API graphique et systèmes mergés dans Systèmes (audio physique, etc...) + rework sauvegarde des resources de façon unique pour éviter les reload (correctifs associés)

This commit is contained in:
Tom Ray
2026-04-17 21:00:35 +02:00
parent f49b050e85
commit 595f28ecda
167 changed files with 32352 additions and 2229 deletions
@@ -1,9 +1,9 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.11.3
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
@@ -20,6 +20,9 @@
#include <string> // char_traits, string
#include <utility> // make_pair, move
#include <vector> // vector
#ifdef __cpp_lib_byteswap
#include <bit> //byteswap
#endif
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/input/input_adapters.hpp>
@@ -62,7 +65,7 @@ static inline bool little_endianness(int num = 1) noexcept
/*!
@brief deserialization of CBOR, MessagePack, and UBJSON values
*/
template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_parser<BasicJsonType>>
template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_parser<BasicJsonType, InputAdapterType>>
class binary_reader
{
using number_integer_t = typename BasicJsonType::number_integer_t;
@@ -169,7 +172,7 @@ class binary_reader
std::int32_t document_size{};
get_number<std::int32_t, true>(input_format_t::bson, document_size);
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast<std::size_t>(-1))))
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size())))
{
return false;
}
@@ -325,6 +328,12 @@ class binary_reader
return get_number<std::int64_t, true>(input_format_t::bson, value) && sax->number_integer(value);
}
case 0x11: // uint64
{
std::uint64_t value{};
return get_number<std::uint64_t, true>(input_format_t::bson, value) && sax->number_unsigned(value);
}
default: // anything else not supported (yet)
{
std::array<char, 3> cr{{}};
@@ -391,7 +400,7 @@ class binary_reader
std::int32_t document_size{};
get_number<std::int32_t, true>(input_format_t::bson, document_size);
if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast<std::size_t>(-1))))
if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size())))
{
return false;
}
@@ -651,7 +660,7 @@ class binary_reader
}
case 0x9F: // array (indefinite length)
return get_cbor_array(static_cast<std::size_t>(-1), tag_handler);
return get_cbor_array(detail::unknown_size(), tag_handler);
// map (0x00..0x17 pairs of data items follow)
case 0xA0:
@@ -705,7 +714,7 @@ class binary_reader
}
case 0xBF: // map (indefinite length)
return get_cbor_object(static_cast<std::size_t>(-1), tag_handler);
return get_cbor_object(detail::unknown_size(), tag_handler);
case 0xC6: // tagged item
case 0xC7:
@@ -1093,7 +1102,7 @@ class binary_reader
}
/*!
@param[in] len the length of the array or static_cast<std::size_t>(-1) for an
@param[in] len the length of the array or detail::unknown_size() for an
array of indefinite size
@param[in] tag_handler how CBOR tags should be treated
@return whether array creation completed
@@ -1106,7 +1115,7 @@ class binary_reader
return false;
}
if (len != static_cast<std::size_t>(-1))
if (len != detail::unknown_size())
{
for (std::size_t i = 0; i < len; ++i)
{
@@ -1131,7 +1140,7 @@ class binary_reader
}
/*!
@param[in] len the length of the object or static_cast<std::size_t>(-1) for an
@param[in] len the length of the object or detail::unknown_size() for an
object of indefinite size
@param[in] tag_handler how CBOR tags should be treated
@return whether object creation completed
@@ -1147,7 +1156,7 @@ class binary_reader
if (len != 0)
{
string_t key;
if (len != static_cast<std::size_t>(-1))
if (len != detail::unknown_size())
{
for (std::size_t i = 0; i < len; ++i)
{
@@ -2310,6 +2319,16 @@ class binary_reader
case 'Z': // null
return sax->null();
case 'B': // byte
{
if (input_format != input_format_t::bjdata)
{
break;
}
std::uint8_t number{};
return get_number(input_format, number) && sax->number_unsigned(number);
}
case 'U':
{
std::uint8_t number{};
@@ -2510,7 +2529,7 @@ class binary_reader
return false;
}
if (size_and_type.second == 'C')
if (size_and_type.second == 'C' || size_and_type.second == 'B')
{
size_and_type.second = 'U';
}
@@ -2532,6 +2551,13 @@ class binary_reader
return (sax->end_array() && sax->end_object());
}
// If BJData type marker is 'B' decode as binary
if (input_format == input_format_t::bjdata && size_and_type.first != npos && size_and_type.second == 'B')
{
binary_t result;
return get_binary(input_format, size_and_type.first, result) && sax->binary(result);
}
if (size_and_type.first != npos)
{
if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first)))
@@ -2565,7 +2591,7 @@ class binary_reader
}
else
{
if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast<std::size_t>(-1))))
if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size())))
{
return false;
}
@@ -2643,7 +2669,7 @@ class binary_reader
}
else
{
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast<std::size_t>(-1))))
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size())))
{
return false;
}
@@ -2754,6 +2780,29 @@ class binary_reader
return current = ia.get_character();
}
/*!
@brief get_to read into a primitive type
This function provides the interface to the used input adapter. It does
not throw in case the input reached EOF, but returns false instead
@return bool, whether the read was successful
*/
template<class T>
bool get_to(T& dest, const input_format_t format, const char* context)
{
auto new_chars_read = ia.get_elements(&dest);
chars_read += new_chars_read;
if (JSON_HEDLEY_UNLIKELY(new_chars_read < sizeof(T)))
{
// in case of failure, advance position by 1 to report failing location
++chars_read;
sax->parse_error(chars_read, "<end of file>", parse_error::create(110, chars_read, exception_message(format, "unexpected end of input", context), nullptr));
return false;
}
return true;
}
/*!
@return character read from the input after ignoring all 'N' entries
*/
@@ -2768,6 +2817,28 @@ class binary_reader
return current;
}
template<class NumberType>
static void byte_swap(NumberType& number)
{
constexpr std::size_t sz = sizeof(number);
#ifdef __cpp_lib_byteswap
if constexpr (sz == 1)
{
return;
}
if constexpr(std::is_integral_v<NumberType>)
{
number = std::byteswap(number);
return;
}
#endif
auto* ptr = reinterpret_cast<std::uint8_t*>(&number);
for (std::size_t i = 0; i < sz / 2; ++i)
{
std::swap(ptr[i], ptr[sz - i - 1]);
}
}
/*
@brief read a number from the input
@@ -2786,29 +2857,16 @@ class binary_reader
template<typename NumberType, bool InputIsLittleEndian = false>
bool get_number(const input_format_t format, NumberType& result)
{
// step 1: read input into array with system's byte order
std::array<std::uint8_t, sizeof(NumberType)> vec{};
for (std::size_t i = 0; i < sizeof(NumberType); ++i)
// read in the original format
if (JSON_HEDLEY_UNLIKELY(!get_to(result, format, "number")))
{
get();
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "number")))
{
return false;
}
// reverse byte order prior to conversion if necessary
if (is_little_endian != (InputIsLittleEndian || format == input_format_t::bjdata))
{
vec[sizeof(NumberType) - i - 1] = static_cast<std::uint8_t>(current);
}
else
{
vec[i] = static_cast<std::uint8_t>(current); // LCOV_EXCL_LINE
}
return false;
}
if (is_little_endian != (InputIsLittleEndian || format == input_format_t::bjdata))
{
byte_swap(result);
}
// step 2: convert array into number of type T and return
std::memcpy(&result, vec.data(), sizeof(NumberType));
return true;
}
@@ -2947,7 +3005,7 @@ class binary_reader
}
private:
static JSON_INLINE_VARIABLE constexpr std::size_t npos = static_cast<std::size_t>(-1);
static JSON_INLINE_VARIABLE constexpr std::size_t npos = detail::unknown_size();
/// input adapter
InputAdapterType ia;
@@ -2973,6 +3031,7 @@ class binary_reader
#define JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_ \
make_array<bjd_type>( \
bjd_type{'B', "byte"}, \
bjd_type{'C', "char"}, \
bjd_type{'D', "double"}, \
bjd_type{'I', "int16"}, \
@@ -1,9 +1,9 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.11.3
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
@@ -23,6 +23,7 @@
#include <istream> // istream
#endif // JSON_NO_IO
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/iterators/iterator_traits.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/meta/type_traits.hpp>
@@ -67,6 +68,13 @@ class file_input_adapter
return std::fgetc(m_file);
}
// returns the number of characters successfully read
template<class T>
std::size_t get_elements(T* dest, std::size_t count = 1)
{
return fread(dest, 1, sizeof(T) * count, m_file);
}
private:
/// the file pointer to read from
std::FILE* m_file;
@@ -126,6 +134,17 @@ class input_stream_adapter
return res;
}
template<class T>
std::size_t get_elements(T* dest, std::size_t count = 1)
{
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
{
is->clear(is->rdstate() | std::ios::eofbit);
}
return res;
}
private:
/// the associated input stream
std::istream* is = nullptr;
@@ -157,6 +176,26 @@ class iterator_input_adapter
return char_traits<char_type>::eof();
}
// for general iterators, we cannot really do something better than falling back to processing the range one-by-one
template<class T>
std::size_t get_elements(T* dest, std::size_t count = 1)
{
auto* ptr = reinterpret_cast<char*>(dest);
for (std::size_t read_index = 0; read_index < count * sizeof(T); ++read_index)
{
if (JSON_HEDLEY_LIKELY(current != end))
{
ptr[read_index] = static_cast<char>(*current);
std::advance(current, 1);
}
else
{
return read_index;
}
}
return count * sizeof(T);
}
private:
IteratorType current;
IteratorType end;
@@ -320,6 +359,13 @@ class wide_string_input_adapter
return utf8_bytes[utf8_bytes_index++];
}
// parsing binary with wchar doesn't make sense, but since the parsing mode can be runtime, we need something here
template<class T>
std::size_t get_elements(T* /*dest*/, std::size_t /*count*/ = 1)
{
JSON_THROW(parse_error::create(112, 1, "wide string type cannot be interpreted as binary data", nullptr));
}
private:
BaseInputAdapter base_adapter;
@@ -416,10 +462,17 @@ typename container_input_adapter_factory_impl::container_input_adapter_factory<C
return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(container);
}
// specialization for std::string
using string_input_adapter_type = decltype(input_adapter(std::declval<std::string>()));
#ifndef JSON_NO_IO
// Special cases with fast paths
inline file_input_adapter input_adapter(std::FILE* file)
{
if (file == nullptr)
{
JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
}
return file_input_adapter(file);
}
@@ -446,9 +499,13 @@ template < typename CharT,
int >::type = 0 >
contiguous_bytes_input_adapter input_adapter(CharT b)
{
if (b == nullptr)
{
JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
}
auto length = std::strlen(reinterpret_cast<const char*>(b));
const auto* ptr = reinterpret_cast<const char*>(b);
return input_adapter(ptr, ptr + length);
return input_adapter(ptr, ptr + length); // cppcheck-suppress[nullPointerArithmeticRedundantCheck]
}
template<typename T, std::size_t N>
@@ -1,22 +1,23 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.11.3
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
#include <cstddef>
#include <string> // string
#include <type_traits> // enable_if_t
#include <utility> // move
#include <vector> // vector
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/input/lexer.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/string_concat.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
/*!
@@ -144,6 +145,11 @@ struct json_sax
namespace detail
{
constexpr std::size_t unknown_size()
{
return (std::numeric_limits<std::size_t>::max)();
}
/*!
@brief SAX implementation to create a JSON value from SAX events
@@ -157,7 +163,7 @@ constructor contains the parsed value.
@tparam BasicJsonType the JSON type
*/
template<typename BasicJsonType>
template<typename BasicJsonType, typename InputAdapterType>
class json_sax_dom_parser
{
public:
@@ -166,14 +172,15 @@ class json_sax_dom_parser
using number_float_t = typename BasicJsonType::number_float_t;
using string_t = typename BasicJsonType::string_t;
using binary_t = typename BasicJsonType::binary_t;
using lexer_t = lexer<BasicJsonType, InputAdapterType>;
/*!
@param[in,out] r reference to a JSON value that is manipulated while
parsing
@param[in] allow_exceptions_ whether parse errors yield exceptions
*/
explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true)
: root(r), allow_exceptions(allow_exceptions_)
explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true, lexer_t* lexer_ = nullptr)
: root(r), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_)
{}
// make class move-only
@@ -229,7 +236,18 @@ class json_sax_dom_parser
{
ref_stack.push_back(handle_value(BasicJsonType::value_t::object));
if (JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) && len > ref_stack.back()->max_size()))
#if JSON_DIAGNOSTIC_POSITIONS
// Manually set the start position of the object here.
// Ensure this is after the call to handle_value to ensure correct start position.
if (m_lexer_ref)
{
// Lexer has read the first character of the object, so
// subtract 1 from the position to get the correct start position.
ref_stack.back()->start_position = m_lexer_ref->get_position() - 1;
}
#endif
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
{
JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
}
@@ -252,6 +270,14 @@ class json_sax_dom_parser
JSON_ASSERT(!ref_stack.empty());
JSON_ASSERT(ref_stack.back()->is_object());
#if JSON_DIAGNOSTIC_POSITIONS
if (m_lexer_ref)
{
// Lexer's position is past the closing brace, so set that as the end position.
ref_stack.back()->end_position = m_lexer_ref->get_position();
}
#endif
ref_stack.back()->set_parents();
ref_stack.pop_back();
return true;
@@ -261,7 +287,16 @@ class json_sax_dom_parser
{
ref_stack.push_back(handle_value(BasicJsonType::value_t::array));
if (JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) && len > ref_stack.back()->max_size()))
#if JSON_DIAGNOSTIC_POSITIONS
// Manually set the start position of the array here.
// Ensure this is after the call to handle_value to ensure correct start position.
if (m_lexer_ref)
{
ref_stack.back()->start_position = m_lexer_ref->get_position() - 1;
}
#endif
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
{
JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
}
@@ -274,6 +309,14 @@ class json_sax_dom_parser
JSON_ASSERT(!ref_stack.empty());
JSON_ASSERT(ref_stack.back()->is_array());
#if JSON_DIAGNOSTIC_POSITIONS
if (m_lexer_ref)
{
// Lexer's position is past the closing bracket, so set that as the end position.
ref_stack.back()->end_position = m_lexer_ref->get_position();
}
#endif
ref_stack.back()->set_parents();
ref_stack.pop_back();
return true;
@@ -298,6 +341,75 @@ class json_sax_dom_parser
}
private:
#if JSON_DIAGNOSTIC_POSITIONS
void handle_diagnostic_positions_for_json_value(BasicJsonType& v)
{
if (m_lexer_ref)
{
// Lexer has read past the current field value, so set the end position to the current position.
// The start position will be set below based on the length of the string representation
// of the value.
v.end_position = m_lexer_ref->get_position();
switch (v.type())
{
case value_t::boolean:
{
// 4 and 5 are the string length of "true" and "false"
v.start_position = v.end_position - (v.m_data.m_value.boolean ? 4 : 5);
break;
}
case value_t::null:
{
// 4 is the string length of "null"
v.start_position = v.end_position - 4;
break;
}
case value_t::string:
{
// include the length of the quotes, which is 2
v.start_position = v.end_position - v.m_data.m_value.string->size() - 2;
break;
}
// As we handle the start and end positions for values created during parsing,
// we do not expect the following value type to be called. Regardless, set the positions
// in case this is created manually or through a different constructor. Exclude from lcov
// since the exact condition of this switch is esoteric.
// LCOV_EXCL_START
case value_t::discarded:
{
v.end_position = std::string::npos;
v.start_position = v.end_position;
break;
}
// LCOV_EXCL_STOP
case value_t::binary:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
{
v.start_position = v.end_position - m_lexer_ref->get_string().size();
break;
}
case value_t::object:
case value_t::array:
{
// object and array are handled in start_object() and start_array() handlers
// skip setting the values here.
break;
}
default: // LCOV_EXCL_LINE
// Handle all possible types discretely, default handler should never be reached.
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert,-warnings-as-errors) LCOV_EXCL_LINE
}
}
}
#endif
/*!
@invariant If the ref stack is empty, then the passed value will be the new
root.
@@ -311,6 +423,11 @@ class json_sax_dom_parser
if (ref_stack.empty())
{
root = BasicJsonType(std::forward<Value>(v));
#if JSON_DIAGNOSTIC_POSITIONS
handle_diagnostic_positions_for_json_value(root);
#endif
return &root;
}
@@ -319,12 +436,22 @@ class json_sax_dom_parser
if (ref_stack.back()->is_array())
{
ref_stack.back()->m_data.m_value.array->emplace_back(std::forward<Value>(v));
#if JSON_DIAGNOSTIC_POSITIONS
handle_diagnostic_positions_for_json_value(ref_stack.back()->m_data.m_value.array->back());
#endif
return &(ref_stack.back()->m_data.m_value.array->back());
}
JSON_ASSERT(ref_stack.back()->is_object());
JSON_ASSERT(object_element);
*object_element = BasicJsonType(std::forward<Value>(v));
#if JSON_DIAGNOSTIC_POSITIONS
handle_diagnostic_positions_for_json_value(*object_element);
#endif
return object_element;
}
@@ -338,9 +465,11 @@ class json_sax_dom_parser
bool errored = false;
/// whether to throw exceptions in case of errors
const bool allow_exceptions = true;
/// the lexer reference to obtain the current position
lexer_t* m_lexer_ref = nullptr;
};
template<typename BasicJsonType>
template<typename BasicJsonType, typename InputAdapterType>
class json_sax_dom_callback_parser
{
public:
@@ -351,11 +480,13 @@ class json_sax_dom_callback_parser
using binary_t = typename BasicJsonType::binary_t;
using parser_callback_t = typename BasicJsonType::parser_callback_t;
using parse_event_t = typename BasicJsonType::parse_event_t;
using lexer_t = lexer<BasicJsonType, InputAdapterType>;
json_sax_dom_callback_parser(BasicJsonType& r,
const parser_callback_t cb,
const bool allow_exceptions_ = true)
: root(r), callback(cb), allow_exceptions(allow_exceptions_)
parser_callback_t cb,
const bool allow_exceptions_ = true,
lexer_t* lexer_ = nullptr)
: root(r), callback(std::move(cb)), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_)
{
keep_stack.push_back(true);
}
@@ -418,12 +549,26 @@ class json_sax_dom_callback_parser
auto val = handle_value(BasicJsonType::value_t::object, true);
ref_stack.push_back(val.second);
// check object limit
if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) && len > ref_stack.back()->max_size()))
if (ref_stack.back())
{
JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
}
#if JSON_DIAGNOSTIC_POSITIONS
// Manually set the start position of the object here.
// Ensure this is after the call to handle_value to ensure correct start position.
if (m_lexer_ref)
{
// Lexer has read the first character of the object, so
// subtract 1 from the position to get the correct start position.
ref_stack.back()->start_position = m_lexer_ref->get_position() - 1;
}
#endif
// check object limit
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
{
JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
}
}
return true;
}
@@ -452,9 +597,23 @@ class json_sax_dom_callback_parser
{
// discard object
*ref_stack.back() = discarded;
#if JSON_DIAGNOSTIC_POSITIONS
// Set start/end positions for discarded object.
handle_diagnostic_positions_for_json_value(*ref_stack.back());
#endif
}
else
{
#if JSON_DIAGNOSTIC_POSITIONS
if (m_lexer_ref)
{
// Lexer's position is past the closing brace, so set that as the end position.
ref_stack.back()->end_position = m_lexer_ref->get_position();
}
#endif
ref_stack.back()->set_parents();
}
}
@@ -488,10 +647,25 @@ class json_sax_dom_callback_parser
auto val = handle_value(BasicJsonType::value_t::array, true);
ref_stack.push_back(val.second);
// check array limit
if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) && len > ref_stack.back()->max_size()))
if (ref_stack.back())
{
JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
#if JSON_DIAGNOSTIC_POSITIONS
// Manually set the start position of the array here.
// Ensure this is after the call to handle_value to ensure correct start position.
if (m_lexer_ref)
{
// Lexer has read the first character of the array, so
// subtract 1 from the position to get the correct start position.
ref_stack.back()->start_position = m_lexer_ref->get_position() - 1;
}
#endif
// check array limit
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
{
JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
}
}
return true;
@@ -506,12 +680,26 @@ class json_sax_dom_callback_parser
keep = callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back());
if (keep)
{
#if JSON_DIAGNOSTIC_POSITIONS
if (m_lexer_ref)
{
// Lexer's position is past the closing bracket, so set that as the end position.
ref_stack.back()->end_position = m_lexer_ref->get_position();
}
#endif
ref_stack.back()->set_parents();
}
else
{
// discard array
*ref_stack.back() = discarded;
#if JSON_DIAGNOSTIC_POSITIONS
// Set start/end positions for discarded array.
handle_diagnostic_positions_for_json_value(*ref_stack.back());
#endif
}
}
@@ -548,6 +736,71 @@ class json_sax_dom_callback_parser
}
private:
#if JSON_DIAGNOSTIC_POSITIONS
void handle_diagnostic_positions_for_json_value(BasicJsonType& v)
{
if (m_lexer_ref)
{
// Lexer has read past the current field value, so set the end position to the current position.
// The start position will be set below based on the length of the string representation
// of the value.
v.end_position = m_lexer_ref->get_position();
switch (v.type())
{
case value_t::boolean:
{
// 4 and 5 are the string length of "true" and "false"
v.start_position = v.end_position - (v.m_data.m_value.boolean ? 4 : 5);
break;
}
case value_t::null:
{
// 4 is the string length of "null"
v.start_position = v.end_position - 4;
break;
}
case value_t::string:
{
// include the length of the quotes, which is 2
v.start_position = v.end_position - v.m_data.m_value.string->size() - 2;
break;
}
case value_t::discarded:
{
v.end_position = std::string::npos;
v.start_position = v.end_position;
break;
}
case value_t::binary:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
{
v.start_position = v.end_position - m_lexer_ref->get_string().size();
break;
}
case value_t::object:
case value_t::array:
{
// object and array are handled in start_object() and start_array() handlers
// skip setting the values here.
break;
}
default: // LCOV_EXCL_LINE
// Handle all possible types discretely, default handler should never be reached.
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert,-warnings-as-errors) LCOV_EXCL_LINE
}
}
}
#endif
/*!
@param[in] v value to add to the JSON value we build during parsing
@param[in] skip_callback whether we should skip calling the callback
@@ -578,6 +831,10 @@ class json_sax_dom_callback_parser
// create value
auto value = BasicJsonType(std::forward<Value>(v));
#if JSON_DIAGNOSTIC_POSITIONS
handle_diagnostic_positions_for_json_value(value);
#endif
// check callback
const bool keep = skip_callback || callback(static_cast<int>(ref_stack.size()), parse_event_t::value, value);
@@ -632,9 +889,9 @@ class json_sax_dom_callback_parser
/// stack to model hierarchy of values
std::vector<BasicJsonType*> ref_stack {};
/// stack to manage which values to keep
std::vector<bool> keep_stack {};
std::vector<bool> keep_stack {}; // NOLINT(readability-redundant-member-init)
/// stack to manage which object keys to keep
std::vector<bool> key_keep_stack {};
std::vector<bool> key_keep_stack {}; // NOLINT(readability-redundant-member-init)
/// helper to hold the reference for the next object element
BasicJsonType* object_element = nullptr;
/// whether a syntax error occurred
@@ -645,6 +902,8 @@ class json_sax_dom_callback_parser
const bool allow_exceptions = true;
/// a discarded value for the callback
BasicJsonType discarded = BasicJsonType::value_t::discarded;
/// the lexer reference to obtain the current position
lexer_t* m_lexer_ref = nullptr;
};
template<typename BasicJsonType>
@@ -692,7 +951,7 @@ class json_sax_acceptor
return true;
}
bool start_object(std::size_t /*unused*/ = static_cast<std::size_t>(-1))
bool start_object(std::size_t /*unused*/ = detail::unknown_size())
{
return true;
}
@@ -707,7 +966,7 @@ class json_sax_acceptor
return true;
}
bool start_array(std::size_t /*unused*/ = static_cast<std::size_t>(-1))
bool start_array(std::size_t /*unused*/ = detail::unknown_size())
{
return true;
}
@@ -1,9 +1,9 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.11.3
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
@@ -967,7 +967,7 @@ class lexer : public lexer_base<BasicJsonType>
locale's decimal point is used instead of `.` to work with the
locale-dependent converters.
*/
token_type scan_number() // lgtm [cpp/use-of-goto]
token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated.
{
// reset token_buffer to store the number's bytes
reset();
@@ -1049,6 +1049,7 @@ scan_number_zero:
case '.':
{
add(decimal_point_char);
decimal_point_position = token_buffer.size() - 1;
goto scan_number_decimal1;
}
@@ -1085,6 +1086,7 @@ scan_number_any1:
case '.':
{
add(decimal_point_char);
decimal_point_position = token_buffer.size() - 1;
goto scan_number_decimal1;
}
@@ -1245,7 +1247,7 @@ scan_number_done:
// we are done scanning a number)
unget();
char* endptr = nullptr; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
errno = 0;
// try to parse integers first and fall back to floats
@@ -1256,7 +1258,7 @@ scan_number_done:
// we checked the number format before
JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());
if (errno == 0)
if (errno != ERANGE)
{
value_unsigned = static_cast<number_unsigned_t>(x);
if (value_unsigned == x)
@@ -1272,7 +1274,7 @@ scan_number_done:
// we checked the number format before
JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());
if (errno == 0)
if (errno != ERANGE)
{
value_integer = static_cast<number_integer_t>(x);
if (value_integer == x)
@@ -1322,6 +1324,7 @@ scan_number_done:
{
token_buffer.clear();
token_string.clear();
decimal_point_position = std::string::npos;
token_string.push_back(char_traits<char_type>::to_char_type(current));
}
@@ -1430,6 +1433,11 @@ scan_number_done:
/// return current string value (implicitly resets the token; useful only once)
string_t& get_string()
{
// translate decimal points from locale back to '.' (#4084)
if (decimal_point_char != '.' && decimal_point_position != std::string::npos)
{
token_buffer[decimal_point_position] = '.';
}
return token_buffer;
}
@@ -1627,6 +1635,8 @@ scan_number_done:
/// the decimal point
const char_int_type decimal_point_char = '.';
/// the position of the decimal point in the input
std::size_t decimal_point_position = std::string::npos;
};
} // namespace detail
@@ -1,9 +1,9 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.11.3
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
@@ -69,10 +69,10 @@ class parser
public:
/// a parser reading from an input adapter
explicit parser(InputAdapterType&& adapter,
const parser_callback_t<BasicJsonType> cb = nullptr,
parser_callback_t<BasicJsonType> cb = nullptr,
const bool allow_exceptions_ = true,
const bool skip_comments = false)
: callback(cb)
: callback(std::move(cb))
, m_lexer(std::move(adapter), skip_comments)
, allow_exceptions(allow_exceptions_)
{
@@ -94,7 +94,7 @@ class parser
{
if (callback)
{
json_sax_dom_callback_parser<BasicJsonType> sdp(result, callback, allow_exceptions);
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
sax_parse_internal(&sdp);
// in strict mode, input must be completely read
@@ -122,7 +122,7 @@ class parser
}
else
{
json_sax_dom_parser<BasicJsonType> sdp(result, allow_exceptions);
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
sax_parse_internal(&sdp);
// in strict mode, input must be completely read
@@ -194,7 +194,7 @@ class parser
{
case token_type::begin_object:
{
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast<std::size_t>(-1))))
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size())))
{
return false;
}
@@ -239,7 +239,7 @@ class parser
case token_type::begin_array:
{
if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast<std::size_t>(-1))))
if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size())))
{
return false;
}
@@ -1,9 +1,9 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.11.3
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once