Compare commits

..
Author SHA1 Message Date
Niels Lohmann 9666b0e888 Hold the token carry buffer in a std::unique_ptr
clang-tidy rejected the raw new[]/delete[] of token_carry_buffer
(cppcoreguidelines-owning-memory) and the capacity computation
(readability-use-std-min-max). The storage is now owned by a
std::unique_ptr<char[]>, as in json_view's edit storage, and the new
capacity is a std::max.

istream parse instructions (Apple clang, twitter, citm, canada, floats)
are unchanged within noise.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 11:52:05 +02:00
Niels Lohmann 7cb3814308 Read a streambuf without a get area with a shorter path
A streambuf without a get area - std::cin synchronized with stdio, or a
custom unbuffered streambuf - hands out every character from uflow(). #5828
made this path slower than before it (CITM from an unbuffered streambuf:
889M -> 1205M instructions on macOS with JSON_DISABLE_STREAM_BULK_SCAN as
the reference), because every character went through the generic refill code
of get_character().

Once the adapter has seen that there is no get area, it calls uflow()
directly (the get area is known to be exhausted, which is all sbumpc()
checks) and copies the character into the token carried by the adapter. The
carried token is now a small buffer class that appends with a compare and a
store (a std::vector<char>::push_back from this function needed a stack
protector canary and a spill on Linux). A get area that appears later is
picked up again; a new test serves the first characters without one.

Instructions, unbuffered streambuf, relative to the previous commit
(develop; JSON_DISABLE_STREAM_BULK_SCAN) on macOS, 5 parses: CITM 1246M ->
960M (1205M; 889M), twitter 556M -> 451M (535M; 410M), canada 2236M -> 1847M
(2188M; 1824M); std::cin synchronized with stdio, one parse: CITM 784M ->
727M (776M; 713M), twitter 312M -> 292M (309M; 283M), canada 1142M -> 1067M
(1135M; 1062M). Linux Ir (GCC / Clang), one parse: CITM 218.9M / 298.5M ->
185.6M / 219.9M (207.5M / 284.2M; 160.0M / 211.3M), twitter 99.2M / 127.6M
-> 87.0M / 98.6M (95.3M / 122.8M; 78.7M / 91.3M), canada 425.7M / 475.1M ->
380.4M / 372.2M (411.7M / 463.4M; 365.9M / 378.8M). Unbuffered input stays
slower than with JSON_DISABLE_STREAM_BULK_SCAN: macOS +8 % (CITM) / +10 %
(twitter) / +1 % (canada), GCC +16 % / +11 % / +4 %, Clang +4 % / +8 % /
-2 % (canada) - every character is still a call plus the virtual uflow().

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:39 +02:00
Niels Lohmann 16de05b930 Copy small binary reads straight from the streambuf get area
The binary readers read every number and string with get_elements(), which
called the virtual std::streambuf::sgetn() each time. When the requested
bytes are in the get area, input_stream_adapter now copies them (and moves
the get pointer) inline, which is what sgetn() does for them anyway.

Instructions relative to the previous commit, from an ifstream (macOS, 5
parses): CBOR CITM 235M -> 226M, twitter 156M -> 151M, canada 495M -> 463M;
MessagePack CITM 232M -> 222M; UBJSON CITM 258M -> 241M, canada 638M ->
623M. Linux Ir (GCC / Clang): CBOR twitter 34.0M / 41.9M -> 32.2M / 40.0M,
CBOR CITM 51.4M / 58.2M -> 47.7M / 54.6M, MessagePack CITM 50.5M / 57.0M ->
46.8M / 53.4M, UBJSON CITM 56.6M / 64.1M -> 50.7M / 57.9M. Against develop
the three binary readers are 5-12 % cheaper in instructions with this series.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:36 +02:00
Niels Lohmann 419133b2af Step over a character in the get area without leaving get_character()
input_stream_adapter::get_character() was an out-of-line function that
checked for a refill, called sbumpc(), checked for EOF and updated the token
bookkeeping for every character. Characters that come from the get area -
nearly all of them - now take a short inline path (pointer compare, one
store, gbump(1)); the refill, EOF and no-get-area cases moved to an
out-of-line get_character_slow(). Default mode only; with
JSON_PRECISE_STREAM_POSITION the adapter is unchanged.

Instructions, istream/istringstream/custom streambuf, relative to develop
(macOS, 5 parses): CITM from an ifstream 399M -> 380M (-5 %), from an
istringstream 383M -> 366M, twitter 186M -> 179M, canada 979M -> 953M,
accept() twitter 109M -> 102M, accept() CITM 250M -> 231M. Linux Ir (GCC / Clang): CITM 89.0M / 122.1M ->
84.6M / 118.7M, twitter 42.5M / 54.3M -> 40.8M / 52.9M, canada 198.3M /
243.3M -> 192.0M / 237.4M, accept() twitter 25.4M / 36.9M -> 23.7M / 35.5M,
NDJSON 95.5M / 118.7M -> 91.2M / 115.2M. FILE* input is unchanged.

Streambufs without a get area get 3-5 % slower with this commit alone (CITM
1205M -> 1246M on macOS, 207.5M -> 218.9M Ir with GCC); a later commit in
this series makes that path shorter than before.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:33 +02:00
19 changed files with 579 additions and 1344 deletions

No files matched your search

+1 -2
View File
@@ -80,8 +80,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
constant time on average. constant time on average.
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the 2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
index (unlike `BasicJsonType`'s array, which is random-access). Constant if no element of the array is an array index (unlike `BasicJsonType`'s array, which is random-access).
or object, since the elements are then adjacent in the index.
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at 3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
that level (as 1.) or the index into the array (as 2.). that level (as 1.) or the index into the array (as 2.).
+1 -2
View File
@@ -28,8 +28,7 @@ This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.
Linear in the size of the array or object: unlike [`front()`](front.md), which only ever looks at the first Linear in the size of the array or object: unlike [`front()`](front.md), which only ever looks at the first
element, `back()` has to walk every element to find where they end, since elements are not a fixed size in the element, `back()` has to walk every element to find where they end, since elements are not a fixed size in the
index. Constant for an array none of whose elements is an array or object, since the elements are then adjacent index.
in the index.
## Notes ## Notes
@@ -86,8 +86,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
constant time on average. constant time on average.
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the 2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
index (unlike `BasicJsonType`'s array, which is random-access). Constant if no element of the array is an array index (unlike `BasicJsonType`'s array, which is random-access).
or object, since the elements are then adjacent in the index.
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at 3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
that level (as 1.) or the index into the array (as 2.). that level (as 1.) or the index into the array (as 2.).
+190 -32
View File
@@ -8,17 +8,18 @@
#pragma once #pragma once
#include <algorithm> // count, find, min #include <algorithm> // count, find, max, min
#include <array> // array #include <array> // array
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // uint32_t #include <cstdint> // uint32_t
#include <cstring> // strlen #include <cstring> // strlen
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator #include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator
#include <limits> // numeric_limits #include <limits> // numeric_limits
#include <memory> // unique_ptr
#include <streambuf> // streambuf #include <streambuf> // streambuf
#include <string> // string, char_traits #include <string> // string, char_traits
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer #include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
#include <utility> // pair, declval #include <utility> // declval, move, pair
#include <vector> // vector #include <vector> // vector
#ifndef JSON_NO_IO #ifndef JSON_NO_IO
@@ -72,6 +73,81 @@ enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 };
#ifndef JSON_NO_IO #ifndef JSON_NO_IO
/*! /*!
A growable array of characters for the part of a token that an input adapter
copied out of its window: unlike std::vector<char>::push_back(), appending a
character takes no reference to a local, and is a few instructions when there
is room.
*/
class token_carry_buffer
{
public:
token_carry_buffer() = default;
token_carry_buffer(const token_carry_buffer&) = delete;
token_carry_buffer& operator=(const token_carry_buffer&) = delete;
token_carry_buffer(token_carry_buffer&& rhs) noexcept
: m_data(std::move(rhs.m_data)), m_end(rhs.m_end), m_capacity_end(rhs.m_capacity_end)
{
rhs.m_end = rhs.m_capacity_end = nullptr;
}
token_carry_buffer& operator=(token_carry_buffer&&) = delete;
~token_carry_buffer() = default;
void clear() noexcept
{
m_end = m_data.get();
}
void push_back(char c)
{
if (JSON_HEDLEY_UNLIKELY(m_end == m_capacity_end))
{
grow(1);
}
*m_end++ = c;
}
void append(const char* first, const char* last)
{
const auto n = static_cast<std::size_t>(last - first);
if (n > static_cast<std::size_t>(m_capacity_end - m_end))
{
grow(n);
}
if (n != 0)
{
std::memcpy(m_end, first, n);
}
m_end += n;
}
/// replace the contents of @a out
void copy_to(std::vector<char>& out) const
{
out.assign(m_data.get(), m_end);
}
private:
/// make room for @a extra more characters
JSON_HEDLEY_NEVER_INLINE void grow(std::size_t extra)
{
const auto size = static_cast<std::size_t>(m_end - m_data.get());
const auto capacity = static_cast<std::size_t>(m_capacity_end - m_data.get());
const std::size_t new_capacity = (std::max)(capacity < 32 ? static_cast<std::size_t>(32) : 2 * capacity, size + extra);
std::unique_ptr<char[]> data(new char[new_capacity]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
if (size != 0)
{
std::memcpy(data.get(), m_data.get(), size);
}
m_data = std::move(data);
m_end = m_data.get() + size;
m_capacity_end = m_data.get() + new_capacity;
}
std::unique_ptr<char[]> m_data{}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,readability-redundant-member-init)
char* m_end = nullptr;
char* m_capacity_end = nullptr;
};
/*!
Input adapter for stdio file access. This adapter read only 1 byte and do not use any Input adapter for stdio file access. This adapter read only 1 byte and do not use any
buffer. This adapter is a very low level adapter. buffer. This adapter is a very low level adapter.
*/ */
@@ -209,6 +285,7 @@ class input_stream_adapter
, token_segment(rhs.token_segment) , token_segment(rhs.token_segment)
, current_in_get_area(rhs.current_in_get_area) , current_in_get_area(rhs.current_in_get_area)
, tracking_token(rhs.tracking_token) , tracking_token(rhs.tracking_token)
, no_get_area(rhs.no_get_area)
#endif #endif
{ {
rhs.is = nullptr; rhs.is = nullptr;
@@ -295,37 +372,20 @@ class input_stream_adapter
std::char_traits<char>::int_type get_character() std::char_traits<char>::int_type get_character()
{ {
#if !JSON_DISABLE_STREAM_BULK_SCAN #if !JSON_DISABLE_STREAM_BULK_SCAN
// sbumpc() refills the get area if it is exhausted // the character is in the get area: step over it, as sbumpc() does
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb); const char* const next = streambuf_access::get_ptr(sb);
if (JSON_HEDLEY_UNLIKELY(refill)) if (JSON_HEDLEY_LIKELY(next != streambuf_access::get_end(sb)))
{
save_token_segment();
}
#endif
auto res = sb->sbumpc();
// set eof manually, as we don't use the istream interface.
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
{
#if !JSON_DISABLE_STREAM_BULK_SCAN
current_in_get_area = false;
#endif
is->clear(is->rdstate() | std::ios::eofbit);
}
#if !JSON_DISABLE_STREAM_BULK_SCAN
else if (JSON_HEDLEY_UNLIKELY(refill))
{
// the consumed character is at gptr() - 1, unless the streambuf
// has no get area and returned it from uflow()
const char* const next = streambuf_access::get_ptr(sb);
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
}
else
{ {
current_in_get_area = true; current_in_get_area = true;
streambuf_access::bump(sb, 1);
return std::char_traits<char>::to_int_type(*next);
}
if (no_get_area)
{
return get_character_no_get_area();
} }
#endif #endif
return res; return get_character_slow();
} }
#endif #endif
@@ -334,6 +394,18 @@ class input_stream_adapter
{ {
#if JSON_PRECISE_STREAM_POSITION #if JSON_PRECISE_STREAM_POSITION
commit_lookahead(); commit_lookahead();
#endif
#if !JSON_DISABLE_STREAM_BULK_SCAN
// the elements are in the get area: copy them, as sgetn() does, but
// without the virtual call
const char* const first = streambuf_access::get_ptr(sb);
const auto available = static_cast<std::size_t>(streambuf_access::get_end(sb) - first);
if (JSON_HEDLEY_LIKELY(count * sizeof(T) <= available && count * sizeof(T) <= static_cast<std::size_t>((std::numeric_limits<int>::max)())))
{
std::memcpy(dest, first, count * sizeof(T));
streambuf_access::bump(sb, static_cast<int>(count * sizeof(T)));
return count * sizeof(T);
}
#endif #endif
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T)))); 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))) if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
@@ -421,7 +493,7 @@ class input_stream_adapter
// last one if @a drop_last. // last one if @a drop_last.
void copy_token(std::vector<char>& out, bool drop_last) const void copy_token(std::vector<char>& out, bool drop_last) const
{ {
out = token_carry; token_carry.copy_to(out);
if (token_segment != nullptr) if (token_segment != nullptr)
{ {
out.insert(out.end(), token_segment, current_end()); out.insert(out.end(), token_segment, current_end());
@@ -434,6 +506,47 @@ class input_stream_adapter
#endif #endif
private: private:
#if !JSON_PRECISE_STREAM_POSITION
// the get area is exhausted, or there is none
#if !JSON_DISABLE_STREAM_BULK_SCAN
JSON_HEDLEY_NEVER_INLINE
#endif
std::char_traits<char>::int_type get_character_slow()
{
#if !JSON_DISABLE_STREAM_BULK_SCAN
// sbumpc() refills the get area if it is exhausted
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
if (JSON_HEDLEY_UNLIKELY(refill))
{
save_token_segment();
}
#endif
auto res = sb->sbumpc();
// set eof manually, as we don't use the istream interface.
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
{
#if !JSON_DISABLE_STREAM_BULK_SCAN
current_in_get_area = false;
#endif
is->clear(is->rdstate() | std::ios::eofbit);
}
#if !JSON_DISABLE_STREAM_BULK_SCAN
else if (JSON_HEDLEY_UNLIKELY(refill))
{
// the consumed character is at gptr() - 1, unless the streambuf
// has no get area and returned it from uflow()
const char* const next = streambuf_access::get_ptr(sb);
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
}
else
{
current_in_get_area = true;
}
#endif
return res;
}
#endif
#if !JSON_DISABLE_STREAM_BULK_SCAN #if !JSON_DISABLE_STREAM_BULK_SCAN
// Reads the protected get-area pointers of any std::streambuf. Naming the // Reads the protected get-area pointers of any std::streambuf. Naming the
// members through a derived class makes forming the member pointers // members through a derived class makes forming the member pointers
@@ -457,6 +570,11 @@ class input_stream_adapter
{ {
(s->*(&streambuf_access::gbump))(n); (s->*(&streambuf_access::gbump))(n);
} }
// sbumpc() for an exhausted get area
static std::streambuf::int_type next_character(std::streambuf* s)
{
return (s->*(&streambuf_access::uflow))();
}
}; };
// one past the character last returned by get_character(), if that is in // one past the character last returned by get_character(), if that is in
@@ -477,16 +595,53 @@ class input_stream_adapter
{ {
if (token_segment != nullptr) if (token_segment != nullptr)
{ {
token_carry.insert(token_carry.end(), token_segment, streambuf_access::get_end(sb)); token_carry.append(token_segment, streambuf_access::get_end(sb));
token_segment = nullptr; token_segment = nullptr;
} }
} }
#if !JSON_PRECISE_STREAM_POSITION
// The streambuf has no get area (e.g., std::cin synchronized with stdio):
// every character comes from uflow(), and the token is kept by value.
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_no_get_area()
{
// the get area is exhausted, so this is what sbumpc() does
const auto res = streambuf_access::next_character(sb);
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof() || streambuf_access::get_end(sb) != nullptr))
{
return get_character_no_get_area_other(res);
}
// current_in_get_area is false: only the first character of a get
// area, or the end of the input, is read here
if (tracking_token)
{
token_carry.push_back(std::char_traits<char>::to_char_type(res));
}
return res;
}
// @a res came from uflow() of a streambuf without a get area: it is the
// end of the input, or the streambuf has a get area now
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_no_get_area_other(std::char_traits<char>::int_type res)
{
if (res == std::char_traits<char>::eof())
{
current_in_get_area = false;
is->clear(is->rdstate() | std::ios::eofbit);
return res;
}
// the consumed character is at gptr() - 1
after_refill(res, streambuf_access::get_ptr(sb) != streambuf_access::get_begin(sb));
return res;
}
#endif
// character @a c was just read into a new get area (@a in_get_area) or // character @a c was just read into a new get area (@a in_get_area) or
// returned without one // returned without one
void after_refill(std::char_traits<char>::int_type c, bool in_get_area) void after_refill(std::char_traits<char>::int_type c, bool in_get_area)
{ {
current_in_get_area = in_get_area; current_in_get_area = in_get_area;
no_get_area = !in_get_area;
if (!tracking_token) if (!tracking_token)
{ {
return; return;
@@ -525,13 +680,16 @@ class input_stream_adapter
#endif #endif
#if !JSON_DISABLE_STREAM_BULK_SCAN #if !JSON_DISABLE_STREAM_BULK_SCAN
/// the part of the current token that was in earlier get areas /// the part of the current token that was in earlier get areas
std::vector<char> token_carry{}; // NOLINT(readability-redundant-member-init) token_carry_buffer token_carry{}; // NOLINT(readability-redundant-member-init)
/// start of the current token's part in the get area, or nullptr /// start of the current token's part in the get area, or nullptr
const char* token_segment = nullptr; const char* token_segment = nullptr;
/// whether the character last returned by get_character() is in the get area /// whether the character last returned by get_character() is in the get area
bool current_in_get_area = false; bool current_in_get_area = false;
/// whether a token is tracked at all; the binary readers never start one /// whether a token is tracked at all; the binary readers never start one
bool tracking_token = false; bool tracking_token = false;
/// whether the streambuf had no get area when it was last asked for a
/// character
bool no_get_area = false;
#endif #endif
}; };
#endif // JSON_NO_IO #endif // JSON_NO_IO
+46 -131
View File
@@ -275,41 +275,11 @@ class builder
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 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
} }
}; };
// The write position is kept in locals: the stores of characters into
// the arena may alias the members of the builder, which would then be
// reloaded after every one. It is stored back where the arena grows
// and at the end.
const unsigned char* const end = m_end;
const std::size_t start = arena_used(); const std::size_t start = arena_used();
char* w = m_arena_write; arena_run(s, static_cast<std::size_t>(p - s));
const char* w_end = m_arena_end;
#define NLOHMANN_VIEW_ENSURE(n) \
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(w_end - w) < (n))) \
{ \
m_arena_write = w; \
arena_grow(n); \
w = m_arena_write; \
w_end = m_arena_end; \
}
{
const auto n = static_cast<std::size_t>(p - s);
if (n <= 16 && end - s >= 16)
{
NLOHMANN_VIEW_ENSURE(16)
std::memcpy(w, s, 16);
}
else if (n != 0)
{
// (an empty run leaves an empty arena unallocated, and memcpy()
// must not be passed its null pointer even for zero bytes)
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, s, n);
}
w += n;
}
for (;;) for (;;)
{ {
if (p == end) if (p == m_end)
{ {
return failed(error_code::string_missing_quote, p); return failed(error_code::string_missing_quote, p);
} }
@@ -317,7 +287,6 @@ class builder
if (c == '"') if (c == '"')
{ {
++p; ++p;
m_arena_write = w;
return decoded{p, start, arena_used() - start}; return decoded{p, start, arena_used() - start};
} }
if (c != '\\') if (c != '\\')
@@ -328,7 +297,7 @@ class builder
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p); return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
} }
++p; ++p;
if (p == end) if (p == m_end)
{ {
return failed(error_code::string_missing_quote, p); return failed(error_code::string_missing_quote, p);
} }
@@ -346,7 +315,7 @@ class builder
{ {
return failed(error_code::string_surrogate_low, p); return failed(error_code::string_surrogate_low, p);
} }
if (end - p < 2 || p[0] != '\\' || p[1] != 'u') if (m_end - p < 2 || p[0] != '\\' || p[1] != 'u')
{ {
return failed(error_code::string_surrogate_high, p); return failed(error_code::string_surrogate_high, p);
} }
@@ -362,108 +331,27 @@ class builder
} }
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00); cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
} }
NLOHMANN_VIEW_ENSURE(4) arena_ensure(4);
w = put_utf8(w, cp); m_arena_write = put_utf8(m_arena_write, cp);
} }
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0)) else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
{ {
NLOHMANN_VIEW_ENSURE(1) arena_ensure(1);
*w++ = simple_escape[d]; *m_arena_write++ = simple_escape[d];
} }
else else
{ {
--p; --p;
return failed(error_code::string_escape, p); return failed(error_code::string_escape, p);
} }
#if NLOHMANN_VIEW_NEON if (p != m_end && *p == '\\')
if (p != end && *p == '\\')
{ {
continue; // consecutive escapes ("\u00e4\u00f6"): no run in between continue; // consecutive escapes ("\u00e4\u00f6"): no run in between
} }
// The first 16 bytes of the run are checked one by one, in predicted branches, and copied
// as one block: where the runs between escapes are short, the vector step below would put
// its longer dependency chain (load, compare, mask, count, add) on p for each of them,
// and be slower than these steps (the scanner of the keys works the same way)
{
if (end - p >= 16)
{
const std::uint8_t* const plain = string_plain();
std::size_t steps = 16;
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { steps = (i); goto run_found; }
NLOHMANN_VIEW_REPEAT16(NLOHMANN_VIEW_STEP)
#undef NLOHMANN_VIEW_STEP
run_found:
NLOHMANN_VIEW_ENSURE(16)
std::memcpy(w, p, 16); // (only the first `steps` bytes are kept)
w += steps;
p += steps;
if (steps < 16)
{
if (*p >= 0x80)
{
// non-ASCII: the UTF-8 check, and the rest of the run
const unsigned char* const r = p;
p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, r, n);
w += n;
}
continue;
}
}
}
#else
if (p != end && (*p == '\\' || *p == '"'))
{
continue; // consecutive escapes ("\u00e4\u00f6"), or the end of the string: no run in between
}
#endif
#if NLOHMANN_VIEW_VECTOR
// the plain bytes up to the next quote, backslash, or control
// character are copied as they are scanned, 16 bytes at a time
for (;;)
{
if (NLOHMANN_VIEW_UNLIKELY(end - p < 16))
{
const unsigned char* const r = p;
p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, r, n);
w += n;
break;
}
NLOHMANN_VIEW_ENSURE(16)
const unsigned k = copy_plain_run16(p, w);
w += k;
p += k;
if (k == 16)
{
continue;
}
if (NLOHMANN_VIEW_UNLIKELY(*p >= 0x80))
{
// non-ASCII: the UTF-8 check, and the rest of the run
const unsigned char* const r = p;
p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, r, n);
w += n;
}
break;
}
#else
const unsigned char* const r = p; const unsigned char* const r = p;
p = scan_string_run(p, end); p = scan_string_run(p, m_end);
const auto n = static_cast<std::size_t>(p - r); arena_run(r, static_cast<std::size_t>(p - r));
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, r, n);
w += n;
#endif
} }
#undef NLOHMANN_VIEW_ENSURE
} }
/// does the float token [s, p) overflow FloatType? (parse() rejects it) /// does the float token [s, p) overflow FloatType? (parse() rejects it)
@@ -509,6 +397,14 @@ run_found:
return m_arena_write != nullptr ? static_cast<std::size_t>(m_arena_write - m_doc.arena.data()) : 0; return m_arena_write != nullptr ? static_cast<std::size_t>(m_arena_write - m_doc.arena.data()) : 0;
} }
NLOHMANN_VIEW_ALWAYS_INLINE void arena_ensure(std::size_t n)
{
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(m_arena_end - m_arena_write) < n))
{
arena_grow(n);
}
}
NLOHMANN_VIEW_NOINLINE void arena_grow(std::size_t n) NLOHMANN_VIEW_NOINLINE void arena_grow(std::size_t n)
{ {
const std::size_t used = arena_used(); const std::size_t used = arena_used();
@@ -517,6 +413,25 @@ run_found:
m_arena_end = m_doc.arena.data() + m_doc.arena.size(); m_arena_end = m_doc.arena.data() + m_doc.arena.size();
} }
/// 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)
{
if (n <= 16 && m_end - r >= 16)
{
arena_ensure(16);
std::memcpy(m_arena_write, r, 16);
}
else if (n != 0)
{
// (an empty run leaves an empty arena unallocated, and memcpy()
// must not be passed its null pointer even for zero bytes)
arena_ensure(n);
std::memcpy(m_arena_write, r, n);
}
m_arena_write += n;
}
/// UTF-8 encoding of cp at w (room for 4 bytes); returns the position after /// UTF-8 encoding of cp at w (room for 4 bytes); returns the position after
/// it. (A returned pointer stays in a register; writing through a /// it. (A returned pointer stays in a register; writing through a
/// reference to m_arena_write would store and reload it around every byte.) /// reference to m_arena_write would store and reload it around every byte.)
@@ -1039,11 +954,11 @@ indent_done:
++p; ++p;
} }
const unsigned char* const int_start = p; const unsigned char* const int_start = p;
if (cur() == '0') if (p != e && *p == '0')
{ {
++p; ++p;
} }
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9')) else if (NLOHMANN_VIEW_LIKELY(p != e && *p >= '1' && *p <= '9'))
{ {
p = skip_digits(p + 1, e); p = skip_digits(p + 1, e);
} }
@@ -1054,7 +969,7 @@ indent_done:
const auto int_digits = static_cast<std::size_t>(p - int_start); const auto int_digits = static_cast<std::size_t>(p - int_start);
std::size_t frac_digits = 0; std::size_t frac_digits = 0;
bool is_float = false; bool is_float = false;
if (cur() == '.') if (p != e && *p == '.')
{ {
++p; ++p;
const unsigned char* const f0 = p; const unsigned char* const f0 = p;
@@ -1067,20 +982,20 @@ indent_done:
is_float = true; is_float = true;
} }
std::int64_t exponent = 0; std::int64_t exponent = 0;
if ((cur() | 0x20) == 'e') if (p != e && (*p | 0x20) == 'e')
{ {
++p; ++p;
bool exp_negative = false; bool exp_negative = false;
if (cur() == '+' || cur() == '-') if (p != e && (*p == '+' || *p == '-'))
{ {
exp_negative = *p == '-'; exp_negative = *p == '-';
++p; ++p;
} }
if (NLOHMANN_VIEW_UNLIKELY(!is_digit(cur()))) if (NLOHMANN_VIEW_UNLIKELY(p == e || !is_digit(*p)))
{ {
return fail(error_code::number_after_exponent); return fail(error_code::number_after_exponent);
} }
while (is_digit(cur())) while (p != e && is_digit(*p))
{ {
if (exponent < 100000) if (exponent < 100000)
{ {
@@ -1142,7 +1057,7 @@ indent_done:
++p; // opening quote ++p; // opening quote
const unsigned char* const s = p; const unsigned char* const s = p;
p = scan_string_run<Value>(p, e); p = scan_string_run<Value>(p, e);
if (NLOHMANN_VIEW_LIKELY(cur() == '"')) if (NLOHMANN_VIEW_LIKELY(p != e && *p == '"'))
{ {
push_node(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s)); push_node(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s));
++p; ++p;
-34
View File
@@ -10,9 +10,6 @@
#include <algorithm> // max #include <algorithm> // max
#include <cstddef> // size_t #include <cstddef> // size_t
#ifndef JSON_NO_IO
#include <cstdio> // FILE, fread
#endif
#include <istream> // istream, streambuf #include <istream> // istream, streambuf
#include <string> // basic_string, char_traits, string #include <string> // basic_string, char_traits, string
#include <type_traits> // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_cv, remove_extent, remove_pointer, remove_reference #include <type_traits> // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_cv, remove_extent, remove_pointer, remove_reference
@@ -157,37 +154,6 @@ inline void append_stream(std::string& buf, std::istream& is)
is.clear(is.rdstate() | std::ios::eofbit); is.clear(is.rdstate() | std::ios::eofbit);
} }
/// a pointer to a C stream (never with JSON_NO_IO, which leaves out <cstdio>)
template<typename T>
struct is_c_stream
#ifndef JSON_NO_IO
: std::is_same<T, std::FILE*>
#else
: std::false_type
#endif
{};
#ifndef JSON_NO_IO
/// append the rest of a C stream: in blocks with fread, not character by
/// character (the library's file adapter reads with fgetc);
/// f is not null
inline void append_file(std::string& buf, std::FILE* f)
{
for (;;)
{
const std::size_t used = buf.size();
const std::size_t room = (std::max)(buf.capacity() - used, std::size_t{4096});
buf.resize(used + room);
const std::size_t got = std::fread(&buf[used], 1, room, f);
buf.resize(used + got);
if (got < room)
{
break; // end of file, or an error (as fgetc, which returns EOF for both)
}
}
}
#endif
} // namespace view } // namespace view
} // namespace detail } // namespace detail
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
+8 -10
View File
@@ -147,21 +147,20 @@ class view_item
view_item(const iterator& it, std::size_t index) view_item(const iterator& it, std::size_t index)
: m_it(it) : m_it(it)
, m_position(index) {
{} if (!it.is_object_iterator())
{
m_index = std::to_string(index);
}
}
/// the member key, or the element index for arrays (as text made on the /// the member key, or the element index for arrays
/// first call, so that iterating over the values pays nothing for it)
string_view_t key() const string_view_t key() const
{ {
if (m_it.is_object_iterator()) if (m_it.is_object_iterator())
{ {
return m_it.key(); return m_it.key();
} }
if (m_index.empty())
{
m_index = std::to_string(m_position);
}
return string_view_t(m_index.data(), m_index.size()); return string_view_t(m_index.data(), m_index.size());
} }
@@ -184,8 +183,7 @@ class view_item
private: private:
iterator m_it; iterator m_it;
std::size_t m_position; ///< the index of an array element std::string m_index{}; // NOLINT(readability-redundant-member-init)
mutable std::string m_index{}; ///< the text of m_position, once key() was called // NOLINT(readability-redundant-member-init)
}; };
/// the range returned by basic_json_view::items() /// the range returned by basic_json_view::items()
-8
View File
@@ -166,10 +166,6 @@ const node* element_at(const document_data& d, const node* array, std::size_t id
{ {
return e + idx; // a growable block: one link per element return e + idx; // a growable block: one link per element
} }
if (array->next == array->len + 1u && (!nav::editable() || (array->flags & node_flags::moved) == 0))
{
return e + idx; // no element has a subtree: the elements are the nodes after the array
}
for (std::size_t i = 0; i < idx; ++i) for (std::size_t i = 0; i < idx; ++i)
{ {
e = document_data::after(e); e = document_data::after(e);
@@ -185,10 +181,6 @@ const node* last_child(const document_data& d, const node* container) noexcept
const std::size_t value_offset = container->kind == static_cast<std::uint8_t>(value_t::object) ? 1 : 0; const std::size_t value_offset = container->kind == static_cast<std::uint8_t>(value_t::object) ? 1 : 0;
const node* const end = navigation<Editable>::end(d, container); const node* const end = navigation<Editable>::end(d, container);
const node* last = navigation<Editable>::first(d, container); const node* last = navigation<Editable>::first(d, container);
if (value_offset == 0 && container->next == container->len + 1u && (!navigation<Editable>::editable() || (container->flags & node_flags::moved) == 0))
{
return last + (container->len - 1u); // an array without subtrees: the elements are the nodes after it
}
for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset)) for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset))
{ {
last = c; last = c;
+6 -44
View File
@@ -8,7 +8,7 @@
#pragma once #pragma once
#include <array> // array #include <algorithm> // max
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // int64_t, uint8_t #include <cstdint> // int64_t, uint8_t
#include <string> // string #include <string> // string
@@ -44,9 +44,8 @@ BasicJsonType materialize(const document_data& d, const node* n)
using nav = navigation<Editable>; using nav = navigation<Editable>;
// the open containers: their next element (or key of the next member), // the open containers: their next element (or key of the next member),
// their end, and whether they are objects; for the first 16 levels on the // their end, and whether they are objects; sized once from the
// stack, else sized once from the document's deepest nesting (for a // document's deepest nesting (for a subtree an upper bound)
// subtree an upper bound)
struct frame struct frame
{ {
const node* pos; const node* pos;
@@ -54,43 +53,10 @@ BasicJsonType materialize(const document_data& d, const node* n)
bool object; bool object;
}; };
// a scalar needs neither the handler nor the stack of open containers
switch (static_cast<value_t>(n->kind))
{
case value_t::string:
return BasicJsonType(string_t(d.str(*n), n->len));
case value_t::number_integer:
return BasicJsonType(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(*n))));
case value_t::number_unsigned:
return BasicJsonType(static_cast<typename BasicJsonType::number_unsigned_t>(integer_bits(*n)));
case value_t::number_float:
return BasicJsonType(float_value<typename BasicJsonType::number_float_t>(d, *n));
case value_t::boolean:
return BasicJsonType((n->flags & node_flags::is_true) != 0);
case value_t::null:
return BasicJsonType(nullptr);
case value_t::object:
case value_t::array:
case value_t::binary:
case value_t::discarded:
default:
break;
}
BasicJsonType result; BasicJsonType result;
sax_t sax(result, true); sax_t sax(result, true);
const string_t no_token{}; const string_t no_token{};
// (shallow subtrees on the stack: a call for a small value then allocates only for the value) std::vector<frame> open((std::max)(d.max_depth, std::size_t{1}));
std::array<frame, 16> shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open
std::vector<frame> deep{};
frame* open = shallow.data();
std::size_t capacity = shallow.size();
if (d.max_depth > capacity)
{
deep.resize(d.max_depth);
open = deep.data();
capacity = deep.size();
}
std::size_t depth = 0; std::size_t depth = 0;
for (;;) for (;;)
{ {
@@ -110,13 +76,9 @@ BasicJsonType materialize(const document_data& d, const node* n)
{ {
sax.start_array(n->len); sax.start_array(n->len);
} }
if (NLOHMANN_VIEW_UNLIKELY(depth == capacity)) if (NLOHMANN_VIEW_UNLIKELY(depth == open.size()))
{ {
// (deeper than the recorded nesting, e.g. after edits) open.resize(depth * 2); // (deeper than the recorded nesting, e.g. after edits)
deep.assign(open, open + depth);
deep.resize(depth * 2);
open = deep.data();
capacity = deep.size();
} }
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object}; open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
break; break;
+11 -101
View File
@@ -47,96 +47,27 @@ constexpr std::size_t index_max_displacement = 64;
static_assert(document_data::index_min_members >= 2 * index_max_displacement, static_assert(document_data::index_min_members >= 2 * index_max_displacement,
"a table must be much larger than the probe window of a lookup"); "a table must be much larger than the probe window of a lookup");
/// hash of a key: its bytes, eight at a time (in a fixed byte order), where the /// hash of a key: its bytes, eight at a time, in a fixed byte order
/// last word may overlap the one before (so that no loop over single bytes is
/// needed); a key of fewer than eight bytes is read as two overlapping words of
/// four or two bytes (in the native byte order: the hash is not stored, and the
/// same on both sides of a lookup)
inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept
{ {
std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1); std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1);
const auto* p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) const auto* p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
while (n >= 8)
{
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
h ^= h >> 29u;
p += 8;
n -= 8;
}
std::uint64_t w = 0; std::uint64_t w = 0;
if (n >= 8) for (std::size_t i = 0; i < n; ++i)
{ {
const unsigned char* const last = p + n - 8; w |= static_cast<std::uint64_t>(p[i]) << (8u * i);
while (p < last)
{
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
h ^= h >> 29u;
p += 8;
}
w = read_eight_bytes(last);
} }
else if (n >= 4) h = (h ^ w) * 0x94D049BB133111EBu;
{
std::uint32_t head = 0;
std::uint32_t tail = 0;
std::memcpy(&head, p, 4);
std::memcpy(&tail, p + n - 4, 4);
w = static_cast<std::uint64_t>(head) | (static_cast<std::uint64_t>(tail) << 32u);
}
else if (n >= 2)
{
std::uint16_t head = 0;
std::uint16_t tail = 0;
std::memcpy(&head, p, 2);
std::memcpy(&tail, p + n - 2, 2);
w = static_cast<std::uint64_t>(head) | (static_cast<std::uint64_t>(tail) << 16u);
}
else if (n == 1)
{
w = p[0];
}
// (a product only carries bits upwards, and the slot is taken from the low
// bits: the high bits of the last word are folded down before the multiplication)
h = (h ^ w ^ (w >> 29u)) * 0x94D049BB133111EBu;
return h ^ (h >> 31u); return h ^ (h >> 31u);
} }
/// whether the n <= 16 bytes at a and b are equal, as two overlapping words
/// (the first and the last 8, 4, or 2 bytes) and without a call
NLOHMANN_VIEW_ALWAYS_INLINE bool same_short_bytes(const unsigned char* a, const unsigned char* b, std::size_t n) noexcept
{
if (n >= 8)
{
std::uint64_t x0 = 0;
std::uint64_t x1 = 0;
std::uint64_t y0 = 0;
std::uint64_t y1 = 0;
std::memcpy(&x0, a, 8);
std::memcpy(&x1, a + n - 8, 8);
std::memcpy(&y0, b, 8);
std::memcpy(&y1, b + n - 8, 8);
return x0 == y0 && x1 == y1;
}
if (n >= 4)
{
std::uint32_t x0 = 0;
std::uint32_t x1 = 0;
std::uint32_t y0 = 0;
std::uint32_t y1 = 0;
std::memcpy(&x0, a, 4);
std::memcpy(&x1, a + n - 4, 4);
std::memcpy(&y0, b, 4);
std::memcpy(&y1, b + n - 4, 4);
return x0 == y0 && x1 == y1;
}
if (n >= 2)
{
std::uint16_t x0 = 0;
std::uint16_t x1 = 0;
std::uint16_t y0 = 0;
std::uint16_t y1 = 0;
std::memcpy(&x0, a, 2);
std::memcpy(&x1, a + n - 2, 2);
std::memcpy(&y0, b, 2);
std::memcpy(&y1, b + n - 2, 2);
return x0 == y0 && x1 == y1;
}
return n == 0 || *a == *b;
}
/// build the table of a large object /// build the table of a large object
inline void build_object_index(document_data& d, node* obj) inline void build_object_index(document_data& d, node* obj)
{ {
@@ -201,27 +132,6 @@ inline const node* find_indexed(const document_data& d, const node* obj, const c
const document_data::object_index& ix = d.indexes[obj->extra - 1u]; const document_data::object_index& ix = d.indexes[obj->extra - 1u];
const std::uint32_t* const table = d.index_slots.data() + ix.start; const std::uint32_t* const table = d.index_slots.data() + ix.start;
std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, n)) & ix.mask; std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, n)) & ix.mask;
const auto* const k_bytes = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
// (the loop for longer keys comes first, as the code is laid out; it is not the more common case)
if (NLOHMANN_VIEW_UNLIKELY(n <= 16))
{
// (up to 16 bytes, the bytes are compared as two words, without a call)
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
{
const std::uint32_t s = table[slot];
if (s == 0)
{
return nullptr;
}
const node* const k = obj + s;
if (k->len == n && same_short_bytes(reinterpret_cast<const unsigned char*>(d.str(*k)), k_bytes, n)) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
{
return k;
}
slot = (slot + 1) & ix.mask;
}
return nullptr;
}
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance) for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
{ {
const std::uint32_t s = table[slot]; const std::uint32_t s = table[slot];
-28
View File
@@ -116,34 +116,6 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned
} }
return p; return p;
} }
/*!
@brief copy 16 bytes from p to w, and find the first quote, backslash,
control character, or non-ASCII byte among them
The bytes are copied whether or not they are all plain (the caller has room
for 16 bytes and keeps only the plain ones).
@return the offset of that byte, or 16 if there is none
*/
NLOHMANN_VIEW_ALWAYS_INLINE unsigned copy_plain_run16(const unsigned char* p, char* w) noexcept
{
#if NLOHMANN_VIEW_NEON
const uint8x16_t in = vld1q_u8(p);
vst1q_u8(reinterpret_cast<std::uint8_t*>(w), in); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)));
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits) >> 2u) : 16u;
#else
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(w)), in); // NOLINT(bugprone-casting-through-void)
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)));
const auto bits = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits)) : 16u;
#endif
}
#endif #endif
#if NLOHMANN_VIEW_SSSE3_DISPATCH #if NLOHMANN_VIEW_SSSE3_DISPATCH
+10 -67
View File
@@ -295,7 +295,7 @@ class basic_json_view
} }
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name()); detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
} }
return idx < m_node->len ? element(idx) : basic_json_view(); return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view();
} }
/// any other integer type (int, unsigned, long, std::int64_t, ...; a /// any other integer type (int, unsigned, long, std::int64_t, ...; a
@@ -358,7 +358,7 @@ class basic_json_view
{ {
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range")); detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
} }
return element(idx); return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
} }
/// any other integer type, see operator[]; negative values are out of /// any other integer type, see operator[]; negative values are out of
@@ -392,13 +392,7 @@ class basic_json_view
string_t value(string_view_t key, const char* default_value) const string_t value(string_view_t key, const char* default_value) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_object())) return value(key, string_t(default_value));
{
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
}
// (the default is made only if there is no such member)
const basic_json_view r = lookup(key);
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
} }
/// the value a JSON pointer refers to converted to T, or the default /// the value a JSON pointer refers to converted to T, or the default
@@ -417,12 +411,7 @@ class basic_json_view
string_t value(const json_pointer& ptr, const char* default_value) const string_t value(const json_pointer& ptr, const char* default_value) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_structured())) return value(ptr, string_t(default_value));
{
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
}
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
} }
/// the first element or member value; a primitive value itself. Throws /// the first element or member value; a primitive value itself. Throws
@@ -444,8 +433,7 @@ class basic_json_view
{ {
if (is_structured() && m_node->len != 0) if (is_structured() && m_node->len != 0)
{ {
return unedited() ? basic_json_view(m_doc, detail::view::last_child<false>(*m_doc, m_node) + (is_object() ? 1 : 0)) return basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
: basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
} }
return front(); return front();
} }
@@ -462,8 +450,7 @@ class basic_json_view
{ {
return end(); return end();
} }
const node* const k = unedited() ? detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size()) const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
: detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
return k != nullptr ? iterator(m_doc, k, true) : end(); return k != nullptr ? iterator(m_doc, k, true) : end();
} }
@@ -480,8 +467,7 @@ class basic_json_view
/// whether this is an object with a member with this key /// whether this is an object with a member with this key
bool contains(string_view_t key) const bool contains(string_view_t key) const
{ {
return is_object() && (unedited() ? detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size()) != nullptr return is_object() && detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr;
: detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr);
} }
bool contains(const char* key) const bool contains(const char* key) const
@@ -641,14 +627,7 @@ class basic_json_view
// the compact writer keeps 64 bytes of slack, so that it does not grow // the compact writer keeps 64 bytes of slack, so that it does not grow
// the buffer just before the end // the buffer just before the end
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160; const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160;
if (unedited()) detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
{
detail::view::view_serializer<BasicJsonType, false>(*m_doc, out, estimate, style).dump(m_node);
}
else
{
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
}
return out; return out;
} }
@@ -728,7 +707,7 @@ class basic_json_view
{ {
return BasicJsonType(value_t::discarded); return BasicJsonType(value_t::discarded);
} }
return unedited() ? detail::view::materialize<BasicJsonType, false>(*m_doc, m_node) : detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node); return detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
} }
/// byte offset of this value in the source text (for strings: of the /// byte offset of this value in the source text (for strings: of the
@@ -806,32 +785,10 @@ class basic_json_view
/// (object required) /// (object required)
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
{ {
if (unedited())
{
const node* const k = detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size());
return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view();
}
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()); const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view(); return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view();
} }
/// whether the document has not been edited yet: until the first edit, its index is walked like that of a
/// read-only document (nothing is moved or linked), which is cheaper
NLOHMANN_VIEW_ALWAYS_INLINE bool unedited() const noexcept
{
return !navigation::editable() || m_doc->edits == nullptr;
}
/// the element of an array at an index below its size
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view element(size_type idx) const
{
if (unedited())
{
return basic_json_view(m_doc, detail::view::element_at<false>(*m_doc, m_node, idx));
}
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
}
// --- get() dispatch --- // --- get() dispatch ---
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
@@ -1571,8 +1528,7 @@ class basic_json_document
// NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11 // NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11
append_input_impl(buf, std::forward<T>(input), std::integral_constant < int, detail::is_contiguous_byte_container<D>::value ? 0 append_input_impl(buf, std::forward<T>(input), std::integral_constant < int, detail::is_contiguous_byte_container<D>::value ? 0
: std::is_base_of<std::istream, D>::value ? 1 : std::is_base_of<std::istream, D>::value ? 1
: detail::view::is_adapter_input<D>::value ? 2 : detail::view::is_adapter_input<D>::value ? 2 : 3 > {});
: detail::view::is_c_stream<D>::value ? 4 : 3 > {});
// NOLINTEND(readability-avoid-nested-conditional-operator) // NOLINTEND(readability-avoid-nested-conditional-operator)
} }
@@ -1594,19 +1550,6 @@ class basic_json_document
detail::view::append_adapter(buf, std::move(input.ia)); detail::view::append_adapter(buf, std::move(input.ia));
} }
#ifndef JSON_NO_IO
static void append_input_impl(std::string& buf, std::FILE* file, std::integral_constant<int, 4> /*C stream*/)
{
if (file == nullptr)
{
// (as before: the library's adapter decides what a null stream yields)
detail::view::append_adapter(buf, detail::input_adapter(file));
return;
}
detail::view::append_file(buf, file);
}
#endif
template<typename T> template<typename T>
static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/) static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/)
{ {
+190 -32
View File
@@ -8468,17 +8468,18 @@ NLOHMANN_JSON_NAMESPACE_END
#include <algorithm> // count, find, min #include <algorithm> // count, find, max, min
#include <array> // array #include <array> // array
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // uint32_t #include <cstdint> // uint32_t
#include <cstring> // strlen #include <cstring> // strlen
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator #include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator
#include <limits> // numeric_limits #include <limits> // numeric_limits
#include <memory> // unique_ptr
#include <streambuf> // streambuf #include <streambuf> // streambuf
#include <string> // string, char_traits #include <string> // string, char_traits
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer #include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
#include <utility> // pair, declval #include <utility> // declval, move, pair
#include <vector> // vector #include <vector> // vector
#ifndef JSON_NO_IO #ifndef JSON_NO_IO
@@ -8537,6 +8538,81 @@ enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 };
#ifndef JSON_NO_IO #ifndef JSON_NO_IO
/*! /*!
A growable array of characters for the part of a token that an input adapter
copied out of its window: unlike std::vector<char>::push_back(), appending a
character takes no reference to a local, and is a few instructions when there
is room.
*/
class token_carry_buffer
{
public:
token_carry_buffer() = default;
token_carry_buffer(const token_carry_buffer&) = delete;
token_carry_buffer& operator=(const token_carry_buffer&) = delete;
token_carry_buffer(token_carry_buffer&& rhs) noexcept
: m_data(std::move(rhs.m_data)), m_end(rhs.m_end), m_capacity_end(rhs.m_capacity_end)
{
rhs.m_end = rhs.m_capacity_end = nullptr;
}
token_carry_buffer& operator=(token_carry_buffer&&) = delete;
~token_carry_buffer() = default;
void clear() noexcept
{
m_end = m_data.get();
}
void push_back(char c)
{
if (JSON_HEDLEY_UNLIKELY(m_end == m_capacity_end))
{
grow(1);
}
*m_end++ = c;
}
void append(const char* first, const char* last)
{
const auto n = static_cast<std::size_t>(last - first);
if (n > static_cast<std::size_t>(m_capacity_end - m_end))
{
grow(n);
}
if (n != 0)
{
std::memcpy(m_end, first, n);
}
m_end += n;
}
/// replace the contents of @a out
void copy_to(std::vector<char>& out) const
{
out.assign(m_data.get(), m_end);
}
private:
/// make room for @a extra more characters
JSON_HEDLEY_NEVER_INLINE void grow(std::size_t extra)
{
const auto size = static_cast<std::size_t>(m_end - m_data.get());
const auto capacity = static_cast<std::size_t>(m_capacity_end - m_data.get());
const std::size_t new_capacity = (std::max)(capacity < 32 ? static_cast<std::size_t>(32) : 2 * capacity, size + extra);
std::unique_ptr<char[]> data(new char[new_capacity]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
if (size != 0)
{
std::memcpy(data.get(), m_data.get(), size);
}
m_data = std::move(data);
m_end = m_data.get() + size;
m_capacity_end = m_data.get() + new_capacity;
}
std::unique_ptr<char[]> m_data{}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,readability-redundant-member-init)
char* m_end = nullptr;
char* m_capacity_end = nullptr;
};
/*!
Input adapter for stdio file access. This adapter read only 1 byte and do not use any Input adapter for stdio file access. This adapter read only 1 byte and do not use any
buffer. This adapter is a very low level adapter. buffer. This adapter is a very low level adapter.
*/ */
@@ -8674,6 +8750,7 @@ class input_stream_adapter
, token_segment(rhs.token_segment) , token_segment(rhs.token_segment)
, current_in_get_area(rhs.current_in_get_area) , current_in_get_area(rhs.current_in_get_area)
, tracking_token(rhs.tracking_token) , tracking_token(rhs.tracking_token)
, no_get_area(rhs.no_get_area)
#endif #endif
{ {
rhs.is = nullptr; rhs.is = nullptr;
@@ -8760,37 +8837,20 @@ class input_stream_adapter
std::char_traits<char>::int_type get_character() std::char_traits<char>::int_type get_character()
{ {
#if !JSON_DISABLE_STREAM_BULK_SCAN #if !JSON_DISABLE_STREAM_BULK_SCAN
// sbumpc() refills the get area if it is exhausted // the character is in the get area: step over it, as sbumpc() does
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb); const char* const next = streambuf_access::get_ptr(sb);
if (JSON_HEDLEY_UNLIKELY(refill)) if (JSON_HEDLEY_LIKELY(next != streambuf_access::get_end(sb)))
{
save_token_segment();
}
#endif
auto res = sb->sbumpc();
// set eof manually, as we don't use the istream interface.
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
{
#if !JSON_DISABLE_STREAM_BULK_SCAN
current_in_get_area = false;
#endif
is->clear(is->rdstate() | std::ios::eofbit);
}
#if !JSON_DISABLE_STREAM_BULK_SCAN
else if (JSON_HEDLEY_UNLIKELY(refill))
{
// the consumed character is at gptr() - 1, unless the streambuf
// has no get area and returned it from uflow()
const char* const next = streambuf_access::get_ptr(sb);
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
}
else
{ {
current_in_get_area = true; current_in_get_area = true;
streambuf_access::bump(sb, 1);
return std::char_traits<char>::to_int_type(*next);
}
if (no_get_area)
{
return get_character_no_get_area();
} }
#endif #endif
return res; return get_character_slow();
} }
#endif #endif
@@ -8799,6 +8859,18 @@ class input_stream_adapter
{ {
#if JSON_PRECISE_STREAM_POSITION #if JSON_PRECISE_STREAM_POSITION
commit_lookahead(); commit_lookahead();
#endif
#if !JSON_DISABLE_STREAM_BULK_SCAN
// the elements are in the get area: copy them, as sgetn() does, but
// without the virtual call
const char* const first = streambuf_access::get_ptr(sb);
const auto available = static_cast<std::size_t>(streambuf_access::get_end(sb) - first);
if (JSON_HEDLEY_LIKELY(count * sizeof(T) <= available && count * sizeof(T) <= static_cast<std::size_t>((std::numeric_limits<int>::max)())))
{
std::memcpy(dest, first, count * sizeof(T));
streambuf_access::bump(sb, static_cast<int>(count * sizeof(T)));
return count * sizeof(T);
}
#endif #endif
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T)))); 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))) if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
@@ -8886,7 +8958,7 @@ class input_stream_adapter
// last one if @a drop_last. // last one if @a drop_last.
void copy_token(std::vector<char>& out, bool drop_last) const void copy_token(std::vector<char>& out, bool drop_last) const
{ {
out = token_carry; token_carry.copy_to(out);
if (token_segment != nullptr) if (token_segment != nullptr)
{ {
out.insert(out.end(), token_segment, current_end()); out.insert(out.end(), token_segment, current_end());
@@ -8899,6 +8971,47 @@ class input_stream_adapter
#endif #endif
private: private:
#if !JSON_PRECISE_STREAM_POSITION
// the get area is exhausted, or there is none
#if !JSON_DISABLE_STREAM_BULK_SCAN
JSON_HEDLEY_NEVER_INLINE
#endif
std::char_traits<char>::int_type get_character_slow()
{
#if !JSON_DISABLE_STREAM_BULK_SCAN
// sbumpc() refills the get area if it is exhausted
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
if (JSON_HEDLEY_UNLIKELY(refill))
{
save_token_segment();
}
#endif
auto res = sb->sbumpc();
// set eof manually, as we don't use the istream interface.
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
{
#if !JSON_DISABLE_STREAM_BULK_SCAN
current_in_get_area = false;
#endif
is->clear(is->rdstate() | std::ios::eofbit);
}
#if !JSON_DISABLE_STREAM_BULK_SCAN
else if (JSON_HEDLEY_UNLIKELY(refill))
{
// the consumed character is at gptr() - 1, unless the streambuf
// has no get area and returned it from uflow()
const char* const next = streambuf_access::get_ptr(sb);
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
}
else
{
current_in_get_area = true;
}
#endif
return res;
}
#endif
#if !JSON_DISABLE_STREAM_BULK_SCAN #if !JSON_DISABLE_STREAM_BULK_SCAN
// Reads the protected get-area pointers of any std::streambuf. Naming the // Reads the protected get-area pointers of any std::streambuf. Naming the
// members through a derived class makes forming the member pointers // members through a derived class makes forming the member pointers
@@ -8922,6 +9035,11 @@ class input_stream_adapter
{ {
(s->*(&streambuf_access::gbump))(n); (s->*(&streambuf_access::gbump))(n);
} }
// sbumpc() for an exhausted get area
static std::streambuf::int_type next_character(std::streambuf* s)
{
return (s->*(&streambuf_access::uflow))();
}
}; };
// one past the character last returned by get_character(), if that is in // one past the character last returned by get_character(), if that is in
@@ -8942,16 +9060,53 @@ class input_stream_adapter
{ {
if (token_segment != nullptr) if (token_segment != nullptr)
{ {
token_carry.insert(token_carry.end(), token_segment, streambuf_access::get_end(sb)); token_carry.append(token_segment, streambuf_access::get_end(sb));
token_segment = nullptr; token_segment = nullptr;
} }
} }
#if !JSON_PRECISE_STREAM_POSITION
// The streambuf has no get area (e.g., std::cin synchronized with stdio):
// every character comes from uflow(), and the token is kept by value.
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_no_get_area()
{
// the get area is exhausted, so this is what sbumpc() does
const auto res = streambuf_access::next_character(sb);
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof() || streambuf_access::get_end(sb) != nullptr))
{
return get_character_no_get_area_other(res);
}
// current_in_get_area is false: only the first character of a get
// area, or the end of the input, is read here
if (tracking_token)
{
token_carry.push_back(std::char_traits<char>::to_char_type(res));
}
return res;
}
// @a res came from uflow() of a streambuf without a get area: it is the
// end of the input, or the streambuf has a get area now
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_no_get_area_other(std::char_traits<char>::int_type res)
{
if (res == std::char_traits<char>::eof())
{
current_in_get_area = false;
is->clear(is->rdstate() | std::ios::eofbit);
return res;
}
// the consumed character is at gptr() - 1
after_refill(res, streambuf_access::get_ptr(sb) != streambuf_access::get_begin(sb));
return res;
}
#endif
// character @a c was just read into a new get area (@a in_get_area) or // character @a c was just read into a new get area (@a in_get_area) or
// returned without one // returned without one
void after_refill(std::char_traits<char>::int_type c, bool in_get_area) void after_refill(std::char_traits<char>::int_type c, bool in_get_area)
{ {
current_in_get_area = in_get_area; current_in_get_area = in_get_area;
no_get_area = !in_get_area;
if (!tracking_token) if (!tracking_token)
{ {
return; return;
@@ -8990,13 +9145,16 @@ class input_stream_adapter
#endif #endif
#if !JSON_DISABLE_STREAM_BULK_SCAN #if !JSON_DISABLE_STREAM_BULK_SCAN
/// the part of the current token that was in earlier get areas /// the part of the current token that was in earlier get areas
std::vector<char> token_carry{}; // NOLINT(readability-redundant-member-init) token_carry_buffer token_carry{}; // NOLINT(readability-redundant-member-init)
/// start of the current token's part in the get area, or nullptr /// start of the current token's part in the get area, or nullptr
const char* token_segment = nullptr; const char* token_segment = nullptr;
/// whether the character last returned by get_character() is in the get area /// whether the character last returned by get_character() is in the get area
bool current_in_get_area = false; bool current_in_get_area = false;
/// whether a token is tracked at all; the binary readers never start one /// whether a token is tracked at all; the binary readers never start one
bool tracking_token = false; bool tracking_token = false;
/// whether the streambuf had no get area when it was last asked for a
/// character
bool no_get_area = false;
#endif #endif
}; };
#endif // JSON_NO_IO #endif // JSON_NO_IO
+81 -423
View File
@@ -756,34 +756,6 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned
} }
return p; return p;
} }
/*!
@brief copy 16 bytes from p to w, and find the first quote, backslash,
control character, or non-ASCII byte among them
The bytes are copied whether or not they are all plain (the caller has room
for 16 bytes and keeps only the plain ones).
@return the offset of that byte, or 16 if there is none
*/
NLOHMANN_VIEW_ALWAYS_INLINE unsigned copy_plain_run16(const unsigned char* p, char* w) noexcept
{
#if NLOHMANN_VIEW_NEON
const uint8x16_t in = vld1q_u8(p);
vst1q_u8(reinterpret_cast<std::uint8_t*>(w), in); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)));
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits) >> 2u) : 16u;
#else
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(w)), in); // NOLINT(bugprone-casting-through-void)
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)));
const auto bits = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits)) : 16u;
#endif
}
#endif #endif
#if NLOHMANN_VIEW_SSSE3_DISPATCH #if NLOHMANN_VIEW_SSSE3_DISPATCH
@@ -1465,41 +1437,11 @@ class builder
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 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
} }
}; };
// The write position is kept in locals: the stores of characters into
// the arena may alias the members of the builder, which would then be
// reloaded after every one. It is stored back where the arena grows
// and at the end.
const unsigned char* const end = m_end;
const std::size_t start = arena_used(); const std::size_t start = arena_used();
char* w = m_arena_write; arena_run(s, static_cast<std::size_t>(p - s));
const char* w_end = m_arena_end;
#define NLOHMANN_VIEW_ENSURE(n) \
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(w_end - w) < (n))) \
{ \
m_arena_write = w; \
arena_grow(n); \
w = m_arena_write; \
w_end = m_arena_end; \
}
{
const auto n = static_cast<std::size_t>(p - s);
if (n <= 16 && end - s >= 16)
{
NLOHMANN_VIEW_ENSURE(16)
std::memcpy(w, s, 16);
}
else if (n != 0)
{
// (an empty run leaves an empty arena unallocated, and memcpy()
// must not be passed its null pointer even for zero bytes)
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, s, n);
}
w += n;
}
for (;;) for (;;)
{ {
if (p == end) if (p == m_end)
{ {
return failed(error_code::string_missing_quote, p); return failed(error_code::string_missing_quote, p);
} }
@@ -1507,7 +1449,6 @@ class builder
if (c == '"') if (c == '"')
{ {
++p; ++p;
m_arena_write = w;
return decoded{p, start, arena_used() - start}; return decoded{p, start, arena_used() - start};
} }
if (c != '\\') if (c != '\\')
@@ -1518,7 +1459,7 @@ class builder
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p); return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
} }
++p; ++p;
if (p == end) if (p == m_end)
{ {
return failed(error_code::string_missing_quote, p); return failed(error_code::string_missing_quote, p);
} }
@@ -1536,7 +1477,7 @@ class builder
{ {
return failed(error_code::string_surrogate_low, p); return failed(error_code::string_surrogate_low, p);
} }
if (end - p < 2 || p[0] != '\\' || p[1] != 'u') if (m_end - p < 2 || p[0] != '\\' || p[1] != 'u')
{ {
return failed(error_code::string_surrogate_high, p); return failed(error_code::string_surrogate_high, p);
} }
@@ -1552,108 +1493,27 @@ class builder
} }
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00); cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
} }
NLOHMANN_VIEW_ENSURE(4) arena_ensure(4);
w = put_utf8(w, cp); m_arena_write = put_utf8(m_arena_write, cp);
} }
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0)) else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
{ {
NLOHMANN_VIEW_ENSURE(1) arena_ensure(1);
*w++ = simple_escape[d]; *m_arena_write++ = simple_escape[d];
} }
else else
{ {
--p; --p;
return failed(error_code::string_escape, p); return failed(error_code::string_escape, p);
} }
#if NLOHMANN_VIEW_NEON if (p != m_end && *p == '\\')
if (p != end && *p == '\\')
{ {
continue; // consecutive escapes ("\u00e4\u00f6"): no run in between continue; // consecutive escapes ("\u00e4\u00f6"): no run in between
} }
// The first 16 bytes of the run are checked one by one, in predicted branches, and copied
// as one block: where the runs between escapes are short, the vector step below would put
// its longer dependency chain (load, compare, mask, count, add) on p for each of them,
// and be slower than these steps (the scanner of the keys works the same way)
{
if (end - p >= 16)
{
const std::uint8_t* const plain = string_plain();
std::size_t steps = 16;
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { steps = (i); goto run_found; }
NLOHMANN_VIEW_REPEAT16(NLOHMANN_VIEW_STEP)
#undef NLOHMANN_VIEW_STEP
run_found:
NLOHMANN_VIEW_ENSURE(16)
std::memcpy(w, p, 16); // (only the first `steps` bytes are kept)
w += steps;
p += steps;
if (steps < 16)
{
if (*p >= 0x80)
{
// non-ASCII: the UTF-8 check, and the rest of the run
const unsigned char* const r = p;
p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, r, n);
w += n;
}
continue;
}
}
}
#else
if (p != end && (*p == '\\' || *p == '"'))
{
continue; // consecutive escapes ("\u00e4\u00f6"), or the end of the string: no run in between
}
#endif
#if NLOHMANN_VIEW_VECTOR
// the plain bytes up to the next quote, backslash, or control
// character are copied as they are scanned, 16 bytes at a time
for (;;)
{
if (NLOHMANN_VIEW_UNLIKELY(end - p < 16))
{
const unsigned char* const r = p;
p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, r, n);
w += n;
break;
}
NLOHMANN_VIEW_ENSURE(16)
const unsigned k = copy_plain_run16(p, w);
w += k;
p += k;
if (k == 16)
{
continue;
}
if (NLOHMANN_VIEW_UNLIKELY(*p >= 0x80))
{
// non-ASCII: the UTF-8 check, and the rest of the run
const unsigned char* const r = p;
p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, r, n);
w += n;
}
break;
}
#else
const unsigned char* const r = p; const unsigned char* const r = p;
p = scan_string_run(p, end); p = scan_string_run(p, m_end);
const auto n = static_cast<std::size_t>(p - r); arena_run(r, static_cast<std::size_t>(p - r));
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, r, n);
w += n;
#endif
} }
#undef NLOHMANN_VIEW_ENSURE
} }
/// does the float token [s, p) overflow FloatType? (parse() rejects it) /// does the float token [s, p) overflow FloatType? (parse() rejects it)
@@ -1699,6 +1559,14 @@ run_found:
return m_arena_write != nullptr ? static_cast<std::size_t>(m_arena_write - m_doc.arena.data()) : 0; return m_arena_write != nullptr ? static_cast<std::size_t>(m_arena_write - m_doc.arena.data()) : 0;
} }
NLOHMANN_VIEW_ALWAYS_INLINE void arena_ensure(std::size_t n)
{
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(m_arena_end - m_arena_write) < n))
{
arena_grow(n);
}
}
NLOHMANN_VIEW_NOINLINE void arena_grow(std::size_t n) NLOHMANN_VIEW_NOINLINE void arena_grow(std::size_t n)
{ {
const std::size_t used = arena_used(); const std::size_t used = arena_used();
@@ -1707,6 +1575,25 @@ run_found:
m_arena_end = m_doc.arena.data() + m_doc.arena.size(); m_arena_end = m_doc.arena.data() + m_doc.arena.size();
} }
/// 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)
{
if (n <= 16 && m_end - r >= 16)
{
arena_ensure(16);
std::memcpy(m_arena_write, r, 16);
}
else if (n != 0)
{
// (an empty run leaves an empty arena unallocated, and memcpy()
// must not be passed its null pointer even for zero bytes)
arena_ensure(n);
std::memcpy(m_arena_write, r, n);
}
m_arena_write += n;
}
/// UTF-8 encoding of cp at w (room for 4 bytes); returns the position after /// UTF-8 encoding of cp at w (room for 4 bytes); returns the position after
/// it. (A returned pointer stays in a register; writing through a /// it. (A returned pointer stays in a register; writing through a
/// reference to m_arena_write would store and reload it around every byte.) /// reference to m_arena_write would store and reload it around every byte.)
@@ -2229,11 +2116,11 @@ indent_done:
++p; ++p;
} }
const unsigned char* const int_start = p; const unsigned char* const int_start = p;
if (cur() == '0') if (p != e && *p == '0')
{ {
++p; ++p;
} }
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9')) else if (NLOHMANN_VIEW_LIKELY(p != e && *p >= '1' && *p <= '9'))
{ {
p = skip_digits(p + 1, e); p = skip_digits(p + 1, e);
} }
@@ -2244,7 +2131,7 @@ indent_done:
const auto int_digits = static_cast<std::size_t>(p - int_start); const auto int_digits = static_cast<std::size_t>(p - int_start);
std::size_t frac_digits = 0; std::size_t frac_digits = 0;
bool is_float = false; bool is_float = false;
if (cur() == '.') if (p != e && *p == '.')
{ {
++p; ++p;
const unsigned char* const f0 = p; const unsigned char* const f0 = p;
@@ -2257,20 +2144,20 @@ indent_done:
is_float = true; is_float = true;
} }
std::int64_t exponent = 0; std::int64_t exponent = 0;
if ((cur() | 0x20) == 'e') if (p != e && (*p | 0x20) == 'e')
{ {
++p; ++p;
bool exp_negative = false; bool exp_negative = false;
if (cur() == '+' || cur() == '-') if (p != e && (*p == '+' || *p == '-'))
{ {
exp_negative = *p == '-'; exp_negative = *p == '-';
++p; ++p;
} }
if (NLOHMANN_VIEW_UNLIKELY(!is_digit(cur()))) if (NLOHMANN_VIEW_UNLIKELY(p == e || !is_digit(*p)))
{ {
return fail(error_code::number_after_exponent); return fail(error_code::number_after_exponent);
} }
while (is_digit(cur())) while (p != e && is_digit(*p))
{ {
if (exponent < 100000) if (exponent < 100000)
{ {
@@ -2332,7 +2219,7 @@ indent_done:
++p; // opening quote ++p; // opening quote
const unsigned char* const s = p; const unsigned char* const s = p;
p = scan_string_run<Value>(p, e); p = scan_string_run<Value>(p, e);
if (NLOHMANN_VIEW_LIKELY(cur() == '"')) if (NLOHMANN_VIEW_LIKELY(p != e && *p == '"'))
{ {
push_node(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s)); push_node(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s));
++p; ++p;
@@ -3281,96 +3168,27 @@ constexpr std::size_t index_max_displacement = 64;
static_assert(document_data::index_min_members >= 2 * index_max_displacement, static_assert(document_data::index_min_members >= 2 * index_max_displacement,
"a table must be much larger than the probe window of a lookup"); "a table must be much larger than the probe window of a lookup");
/// hash of a key: its bytes, eight at a time (in a fixed byte order), where the /// hash of a key: its bytes, eight at a time, in a fixed byte order
/// last word may overlap the one before (so that no loop over single bytes is
/// needed); a key of fewer than eight bytes is read as two overlapping words of
/// four or two bytes (in the native byte order: the hash is not stored, and the
/// same on both sides of a lookup)
inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept
{ {
std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1); std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1);
const auto* p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) const auto* p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
while (n >= 8)
{
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
h ^= h >> 29u;
p += 8;
n -= 8;
}
std::uint64_t w = 0; std::uint64_t w = 0;
if (n >= 8) for (std::size_t i = 0; i < n; ++i)
{ {
const unsigned char* const last = p + n - 8; w |= static_cast<std::uint64_t>(p[i]) << (8u * i);
while (p < last)
{
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
h ^= h >> 29u;
p += 8;
}
w = read_eight_bytes(last);
} }
else if (n >= 4) h = (h ^ w) * 0x94D049BB133111EBu;
{
std::uint32_t head = 0;
std::uint32_t tail = 0;
std::memcpy(&head, p, 4);
std::memcpy(&tail, p + n - 4, 4);
w = static_cast<std::uint64_t>(head) | (static_cast<std::uint64_t>(tail) << 32u);
}
else if (n >= 2)
{
std::uint16_t head = 0;
std::uint16_t tail = 0;
std::memcpy(&head, p, 2);
std::memcpy(&tail, p + n - 2, 2);
w = static_cast<std::uint64_t>(head) | (static_cast<std::uint64_t>(tail) << 16u);
}
else if (n == 1)
{
w = p[0];
}
// (a product only carries bits upwards, and the slot is taken from the low
// bits: the high bits of the last word are folded down before the multiplication)
h = (h ^ w ^ (w >> 29u)) * 0x94D049BB133111EBu;
return h ^ (h >> 31u); return h ^ (h >> 31u);
} }
/// whether the n <= 16 bytes at a and b are equal, as two overlapping words
/// (the first and the last 8, 4, or 2 bytes) and without a call
NLOHMANN_VIEW_ALWAYS_INLINE bool same_short_bytes(const unsigned char* a, const unsigned char* b, std::size_t n) noexcept
{
if (n >= 8)
{
std::uint64_t x0 = 0;
std::uint64_t x1 = 0;
std::uint64_t y0 = 0;
std::uint64_t y1 = 0;
std::memcpy(&x0, a, 8);
std::memcpy(&x1, a + n - 8, 8);
std::memcpy(&y0, b, 8);
std::memcpy(&y1, b + n - 8, 8);
return x0 == y0 && x1 == y1;
}
if (n >= 4)
{
std::uint32_t x0 = 0;
std::uint32_t x1 = 0;
std::uint32_t y0 = 0;
std::uint32_t y1 = 0;
std::memcpy(&x0, a, 4);
std::memcpy(&x1, a + n - 4, 4);
std::memcpy(&y0, b, 4);
std::memcpy(&y1, b + n - 4, 4);
return x0 == y0 && x1 == y1;
}
if (n >= 2)
{
std::uint16_t x0 = 0;
std::uint16_t x1 = 0;
std::uint16_t y0 = 0;
std::uint16_t y1 = 0;
std::memcpy(&x0, a, 2);
std::memcpy(&x1, a + n - 2, 2);
std::memcpy(&y0, b, 2);
std::memcpy(&y1, b + n - 2, 2);
return x0 == y0 && x1 == y1;
}
return n == 0 || *a == *b;
}
/// build the table of a large object /// build the table of a large object
inline void build_object_index(document_data& d, node* obj) inline void build_object_index(document_data& d, node* obj)
{ {
@@ -3435,27 +3253,6 @@ inline const node* find_indexed(const document_data& d, const node* obj, const c
const document_data::object_index& ix = d.indexes[obj->extra - 1u]; const document_data::object_index& ix = d.indexes[obj->extra - 1u];
const std::uint32_t* const table = d.index_slots.data() + ix.start; const std::uint32_t* const table = d.index_slots.data() + ix.start;
std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, n)) & ix.mask; std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, n)) & ix.mask;
const auto* const k_bytes = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
// (the loop for longer keys comes first, as the code is laid out; it is not the more common case)
if (NLOHMANN_VIEW_UNLIKELY(n <= 16))
{
// (up to 16 bytes, the bytes are compared as two words, without a call)
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
{
const std::uint32_t s = table[slot];
if (s == 0)
{
return nullptr;
}
const node* const k = obj + s;
if (k->len == n && same_short_bytes(reinterpret_cast<const unsigned char*>(d.str(*k)), k_bytes, n)) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
{
return k;
}
slot = (slot + 1) & ix.mask;
}
return nullptr;
}
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance) for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
{ {
const std::uint32_t s = table[slot]; const std::uint32_t s = table[slot];
@@ -3624,10 +3421,6 @@ const node* element_at(const document_data& d, const node* array, std::size_t id
{ {
return e + idx; // a growable block: one link per element return e + idx; // a growable block: one link per element
} }
if (array->next == array->len + 1u && (!nav::editable() || (array->flags & node_flags::moved) == 0))
{
return e + idx; // no element has a subtree: the elements are the nodes after the array
}
for (std::size_t i = 0; i < idx; ++i) for (std::size_t i = 0; i < idx; ++i)
{ {
e = document_data::after(e); e = document_data::after(e);
@@ -3643,10 +3436,6 @@ const node* last_child(const document_data& d, const node* container) noexcept
const std::size_t value_offset = container->kind == static_cast<std::uint8_t>(value_t::object) ? 1 : 0; const std::size_t value_offset = container->kind == static_cast<std::uint8_t>(value_t::object) ? 1 : 0;
const node* const end = navigation<Editable>::end(d, container); const node* const end = navigation<Editable>::end(d, container);
const node* last = navigation<Editable>::first(d, container); const node* last = navigation<Editable>::first(d, container);
if (value_offset == 0 && container->next == container->len + 1u && (!navigation<Editable>::editable() || (container->flags & node_flags::moved) == 0))
{
return last + (container->len - 1u); // an array without subtrees: the elements are the nodes after it
}
for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset)) for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset))
{ {
last = c; last = c;
@@ -5512,9 +5301,6 @@ NLOHMANN_JSON_NAMESPACE_END
#include <algorithm> // max #include <algorithm> // max
#include <cstddef> // size_t #include <cstddef> // size_t
#ifndef JSON_NO_IO
#include <cstdio> // FILE, fread
#endif
#include <istream> // istream, streambuf #include <istream> // istream, streambuf
#include <string> // basic_string, char_traits, string #include <string> // basic_string, char_traits, string
#include <type_traits> // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_cv, remove_extent, remove_pointer, remove_reference #include <type_traits> // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_cv, remove_extent, remove_pointer, remove_reference
@@ -5660,37 +5446,6 @@ inline void append_stream(std::string& buf, std::istream& is)
is.clear(is.rdstate() | std::ios::eofbit); is.clear(is.rdstate() | std::ios::eofbit);
} }
/// a pointer to a C stream (never with JSON_NO_IO, which leaves out <cstdio>)
template<typename T>
struct is_c_stream
#ifndef JSON_NO_IO
: std::is_same<T, std::FILE*>
#else
: std::false_type
#endif
{};
#ifndef JSON_NO_IO
/// append the rest of a C stream: in blocks with fread, not character by
/// character (the library's file adapter reads with fgetc);
/// f is not null
inline void append_file(std::string& buf, std::FILE* f)
{
for (;;)
{
const std::size_t used = buf.size();
const std::size_t room = (std::max)(buf.capacity() - used, std::size_t{4096});
buf.resize(used + room);
const std::size_t got = std::fread(&buf[used], 1, room, f);
buf.resize(used + got);
if (got < room)
{
break; // end of file, or an error (as fgetc, which returns EOF for both)
}
}
}
#endif
} // namespace view } // namespace view
} // namespace detail } // namespace detail
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
@@ -5849,21 +5604,20 @@ class view_item
view_item(const iterator& it, std::size_t index) view_item(const iterator& it, std::size_t index)
: m_it(it) : m_it(it)
, m_position(index) {
{} if (!it.is_object_iterator())
{
m_index = std::to_string(index);
}
}
/// the member key, or the element index for arrays (as text made on the /// the member key, or the element index for arrays
/// first call, so that iterating over the values pays nothing for it)
string_view_t key() const string_view_t key() const
{ {
if (m_it.is_object_iterator()) if (m_it.is_object_iterator())
{ {
return m_it.key(); return m_it.key();
} }
if (m_index.empty())
{
m_index = std::to_string(m_position);
}
return string_view_t(m_index.data(), m_index.size()); return string_view_t(m_index.data(), m_index.size());
} }
@@ -5886,8 +5640,7 @@ class view_item
private: private:
iterator m_it; iterator m_it;
std::size_t m_position; ///< the index of an array element std::string m_index{}; // NOLINT(readability-redundant-member-init)
mutable std::string m_index{}; ///< the text of m_position, once key() was called // NOLINT(readability-redundant-member-init)
}; };
/// the range returned by basic_json_view::items() /// the range returned by basic_json_view::items()
@@ -5981,7 +5734,7 @@ NLOHMANN_JSON_NAMESPACE_END
#include <array> // array #include <algorithm> // max
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // int64_t, uint8_t #include <cstdint> // int64_t, uint8_t
#include <string> // string #include <string> // string
@@ -6021,9 +5774,8 @@ BasicJsonType materialize(const document_data& d, const node* n)
using nav = navigation<Editable>; using nav = navigation<Editable>;
// the open containers: their next element (or key of the next member), // the open containers: their next element (or key of the next member),
// their end, and whether they are objects; for the first 16 levels on the // their end, and whether they are objects; sized once from the
// stack, else sized once from the document's deepest nesting (for a // document's deepest nesting (for a subtree an upper bound)
// subtree an upper bound)
struct frame struct frame
{ {
const node* pos; const node* pos;
@@ -6031,43 +5783,10 @@ BasicJsonType materialize(const document_data& d, const node* n)
bool object; bool object;
}; };
// a scalar needs neither the handler nor the stack of open containers
switch (static_cast<value_t>(n->kind))
{
case value_t::string:
return BasicJsonType(string_t(d.str(*n), n->len));
case value_t::number_integer:
return BasicJsonType(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(*n))));
case value_t::number_unsigned:
return BasicJsonType(static_cast<typename BasicJsonType::number_unsigned_t>(integer_bits(*n)));
case value_t::number_float:
return BasicJsonType(float_value<typename BasicJsonType::number_float_t>(d, *n));
case value_t::boolean:
return BasicJsonType((n->flags & node_flags::is_true) != 0);
case value_t::null:
return BasicJsonType(nullptr);
case value_t::object:
case value_t::array:
case value_t::binary:
case value_t::discarded:
default:
break;
}
BasicJsonType result; BasicJsonType result;
sax_t sax(result, true); sax_t sax(result, true);
const string_t no_token{}; const string_t no_token{};
// (shallow subtrees on the stack: a call for a small value then allocates only for the value) std::vector<frame> open((std::max)(d.max_depth, std::size_t{1}));
std::array<frame, 16> shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open
std::vector<frame> deep{};
frame* open = shallow.data();
std::size_t capacity = shallow.size();
if (d.max_depth > capacity)
{
deep.resize(d.max_depth);
open = deep.data();
capacity = deep.size();
}
std::size_t depth = 0; std::size_t depth = 0;
for (;;) for (;;)
{ {
@@ -6087,13 +5806,9 @@ BasicJsonType materialize(const document_data& d, const node* n)
{ {
sax.start_array(n->len); sax.start_array(n->len);
} }
if (NLOHMANN_VIEW_UNLIKELY(depth == capacity)) if (NLOHMANN_VIEW_UNLIKELY(depth == open.size()))
{ {
// (deeper than the recorded nesting, e.g. after edits) open.resize(depth * 2); // (deeper than the recorded nesting, e.g. after edits)
deep.assign(open, open + depth);
deep.resize(depth * 2);
open = deep.data();
capacity = deep.size();
} }
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object}; open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
break; break;
@@ -7875,7 +7590,7 @@ class basic_json_view
} }
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name()); detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
} }
return idx < m_node->len ? element(idx) : basic_json_view(); return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view();
} }
/// any other integer type (int, unsigned, long, std::int64_t, ...; a /// any other integer type (int, unsigned, long, std::int64_t, ...; a
@@ -7938,7 +7653,7 @@ class basic_json_view
{ {
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range")); detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
} }
return element(idx); return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
} }
/// any other integer type, see operator[]; negative values are out of /// any other integer type, see operator[]; negative values are out of
@@ -7972,13 +7687,7 @@ class basic_json_view
string_t value(string_view_t key, const char* default_value) const string_t value(string_view_t key, const char* default_value) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_object())) return value(key, string_t(default_value));
{
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
}
// (the default is made only if there is no such member)
const basic_json_view r = lookup(key);
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
} }
/// the value a JSON pointer refers to converted to T, or the default /// the value a JSON pointer refers to converted to T, or the default
@@ -7997,12 +7706,7 @@ class basic_json_view
string_t value(const json_pointer& ptr, const char* default_value) const string_t value(const json_pointer& ptr, const char* default_value) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_structured())) return value(ptr, string_t(default_value));
{
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
}
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
} }
/// the first element or member value; a primitive value itself. Throws /// the first element or member value; a primitive value itself. Throws
@@ -8024,8 +7728,7 @@ class basic_json_view
{ {
if (is_structured() && m_node->len != 0) if (is_structured() && m_node->len != 0)
{ {
return unedited() ? basic_json_view(m_doc, detail::view::last_child<false>(*m_doc, m_node) + (is_object() ? 1 : 0)) return basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
: basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
} }
return front(); return front();
} }
@@ -8042,8 +7745,7 @@ class basic_json_view
{ {
return end(); return end();
} }
const node* const k = unedited() ? detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size()) const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
: detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
return k != nullptr ? iterator(m_doc, k, true) : end(); return k != nullptr ? iterator(m_doc, k, true) : end();
} }
@@ -8060,8 +7762,7 @@ class basic_json_view
/// whether this is an object with a member with this key /// whether this is an object with a member with this key
bool contains(string_view_t key) const bool contains(string_view_t key) const
{ {
return is_object() && (unedited() ? detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size()) != nullptr return is_object() && detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr;
: detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr);
} }
bool contains(const char* key) const bool contains(const char* key) const
@@ -8221,14 +7922,7 @@ class basic_json_view
// the compact writer keeps 64 bytes of slack, so that it does not grow // the compact writer keeps 64 bytes of slack, so that it does not grow
// the buffer just before the end // the buffer just before the end
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160; const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160;
if (unedited()) detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
{
detail::view::view_serializer<BasicJsonType, false>(*m_doc, out, estimate, style).dump(m_node);
}
else
{
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
}
return out; return out;
} }
@@ -8308,7 +8002,7 @@ class basic_json_view
{ {
return BasicJsonType(value_t::discarded); return BasicJsonType(value_t::discarded);
} }
return unedited() ? detail::view::materialize<BasicJsonType, false>(*m_doc, m_node) : detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node); return detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
} }
/// byte offset of this value in the source text (for strings: of the /// byte offset of this value in the source text (for strings: of the
@@ -8386,32 +8080,10 @@ class basic_json_view
/// (object required) /// (object required)
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
{ {
if (unedited())
{
const node* const k = detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size());
return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view();
}
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()); const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view(); return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view();
} }
/// whether the document has not been edited yet: until the first edit, its index is walked like that of a
/// read-only document (nothing is moved or linked), which is cheaper
NLOHMANN_VIEW_ALWAYS_INLINE bool unedited() const noexcept
{
return !navigation::editable() || m_doc->edits == nullptr;
}
/// the element of an array at an index below its size
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view element(size_type idx) const
{
if (unedited())
{
return basic_json_view(m_doc, detail::view::element_at<false>(*m_doc, m_node, idx));
}
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
}
// --- get() dispatch --- // --- get() dispatch ---
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
@@ -9151,8 +8823,7 @@ class basic_json_document
// NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11 // NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11
append_input_impl(buf, std::forward<T>(input), std::integral_constant < int, detail::is_contiguous_byte_container<D>::value ? 0 append_input_impl(buf, std::forward<T>(input), std::integral_constant < int, detail::is_contiguous_byte_container<D>::value ? 0
: std::is_base_of<std::istream, D>::value ? 1 : std::is_base_of<std::istream, D>::value ? 1
: detail::view::is_adapter_input<D>::value ? 2 : detail::view::is_adapter_input<D>::value ? 2 : 3 > {});
: detail::view::is_c_stream<D>::value ? 4 : 3 > {});
// NOLINTEND(readability-avoid-nested-conditional-operator) // NOLINTEND(readability-avoid-nested-conditional-operator)
} }
@@ -9174,19 +8845,6 @@ class basic_json_document
detail::view::append_adapter(buf, std::move(input.ia)); detail::view::append_adapter(buf, std::move(input.ia));
} }
#ifndef JSON_NO_IO
static void append_input_impl(std::string& buf, std::FILE* file, std::integral_constant<int, 4> /*C stream*/)
{
if (file == nullptr)
{
// (as before: the library's adapter decides what a null stream yields)
detail::view::append_adapter(buf, detail::input_adapter(file));
return;
}
detail::view::append_file(buf, file);
}
#endif
template<typename T> template<typename T>
static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/) static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/)
{ {
-123
View File
@@ -185,29 +185,6 @@ TEST_CASE("json_view")
// duplicate keys: the last value, at the position of the first key // duplicate keys: the last value, at the position of the first key
CHECK(materialized<json_document>(R"({"a":1,"b":2,"a":3})") == json::parse(R"({"a":1,"b":2,"a":3})")); CHECK(materialized<json_document>(R"({"a":1,"b":2,"a":3})") == json::parse(R"({"a":1,"b":2,"a":3})"));
CHECK(materialized<ordered_json_document>(R"({"a":1,"b":2,"a":3})").dump() == R"({"a":3,"b":2})"); CHECK(materialized<ordered_json_document>(R"({"a":1,"b":2,"a":3})").dump() == R"({"a":3,"b":2})");
// scalars (made without the handler), and nesting around the depth up to which the stack of open containers is local
for (const char* scalar :
{"null", "true", "false", "0", "-7", "18446744073709551615", "1.5e300", "-0.0", "\"\"", "\"short\"", "\"a string that is longer than fifteen bytes\"", "\"\\u00e4\\n\""
})
{
CAPTURE(scalar)
CHECK(materialized<json_document>(scalar) == json::parse(scalar));
const std::string in_array = std::string("[1,") + scalar + "]";
const json_document d = json_document::parse(in_array);
CHECK(d.root()[1].materialize() == json::parse(scalar));
CHECK(d.root()[1].get<json>() == json::parse(scalar));
}
for (const std::size_t levels :
{
std::size_t{1}, std::size_t{15}, std::size_t{16}, std::size_t{17}, std::size_t{32}, std::size_t{33}
})
{
CAPTURE(levels)
const std::string nested = std::string(levels, '[') + "{\"k\":[1,{\"j\":2}]}" + std::string(levels, ']');
CHECK(materialized<json_document>(nested) == json::parse(nested));
const std::string wide = "{\"a\":" + nested + ",\"b\":" + nested + "}";
CHECK(materialized<json_document>(wide) == json::parse(wide));
}
// very deep nesting (iterative, as parse()) // very deep nesting (iterative, as parse())
const std::string deep = std::string(100000, '[') + std::string(100000, ']'); const std::string deep = std::string(100000, '[') + std::string(100000, ']');
// (one check per step, so that a failure shows which step it is) // (one check per step, so that a failure shows which step it is)
@@ -433,31 +410,6 @@ TEST_CASE("json_view")
std::istringstream rvalue_source("[1]"); std::istringstream rvalue_source("[1]");
CHECK(json_document::parse_copy(std::move(rvalue_source)).owns_source()); CHECK(json_document::parse_copy(std::move(rvalue_source)).owns_source());
// a C stream is read in blocks as well (more than one block, and a
// text whose length is a multiple of the block size)
for (const std::size_t extra :
{
std::size_t{0}, std::size_t{1}
})
{
std::FILE* long_file = std::tmpfile(); // NOLINT(cppcoreguidelines-owning-memory)
if (long_file != nullptr)
{
std::string padded = text;
padded.append((4096 - (padded.size() % 4096)) % 4096 + extra, ' ');
CHECK(std::fwrite(padded.data(), 1, padded.size(), long_file) == padded.size());
CHECK(std::fseek(long_file, 0, SEEK_SET) == 0);
const json_document from_long_file = json_document::parse(long_file);
CHECK(from_long_file.source() == padded);
CHECK(from_long_file.root().materialize() == json::parse(text));
CHECK(std::fclose(long_file) == 0); // NOLINT(cppcoreguidelines-owning-memory)
}
}
{
json_document from_null;
CHECK_THROWS_AS(from_null = json_document::parse(static_cast<std::FILE*>(nullptr)), json::parse_error&);
}
// a stream without a buffer is an empty input // a stream without a buffer is an empty input
std::istream no_buffer(nullptr); std::istream no_buffer(nullptr);
CHECK(json_document::parse(no_buffer, false).is_discarded()); CHECK(json_document::parse(no_buffer, false).is_discarded());
@@ -1054,49 +1006,6 @@ TEST_CASE("json_view element access and iteration")
#endif #endif
} }
SECTION("arrays whose elements have no subtrees: index and back()")
{
// (the elements are adjacent in the index; an array, an object, or an
// empty container anywhere makes the lookup skip element by element)
const std::vector<std::string> texts =
{
"[]", "[1]", "[1,2,3]", "[1,\"two\",3.5,true,null,-7,18446744073709551615]", "[[]]", "[{}]", "[1,[2],3]",
"[1,2,{\"a\":3}]", "[[1,2],3,4]", "[1,2,3,[4,5,6,7,8],9,10,11,12]", "[{\"a\":[1]},2]", "[\"a\\u00e4\",\"b\"]"
};
for (const std::string& text : texts)
{
CAPTURE(text)
const json_document d = json_document::parse(text);
const json_view v = d.root();
const json expected = json::parse(text);
for (std::size_t i = 0; i < expected.size(); ++i)
{
CHECK(v[i].materialize() == expected[i]);
CHECK(v.at(i).materialize() == expected[i]);
}
CHECK(v[expected.size()].is_discarded());
if (!expected.empty())
{
CHECK(v.back().materialize() == expected.back());
}
}
// a long array (the direct lookup is not limited to short ones), also with a container at its end
std::string text = "[0";
for (int i = 1; i < 1000; ++i)
{
text += "," + std::to_string(i);
}
const json_document scalars = json_document::parse(text + "]");
CHECK(scalars.root()[999].get<int>() == 999);
CHECK(scalars.root()[500].get<int>() == 500);
CHECK(scalars.root().at(1).get<int>() == 1);
CHECK(scalars.root().back().get<int>() == 999);
const json_document container_last = json_document::parse(text + ",[1000]]");
CHECK(container_last.root()[999].get<int>() == 999);
CHECK(container_last.root()[1000][0].get<int>() == 1000);
CHECK(container_last.root().back()[0].get<int>() == 1000);
}
SECTION("iterators") SECTION("iterators")
{ {
const json_document d = json_document::parse(R"({"x":[1,{"y":2}],"z":null})"); const json_document d = json_document::parse(R"({"x":[1,{"y":2}],"z":null})");
@@ -1136,28 +1045,6 @@ TEST_CASE("json_view element access and iteration")
CHECK((*first++).key() == "0"); CHECK((*first++).key() == "0");
CHECK((*first).key() == "1"); CHECK((*first).key() == "1");
CHECK(++first == items.end()); CHECK(++first == items.end());
// the keys of array elements are their indexes, whether or not they are asked for
std::string numbers = "[0";
for (int i = 1; i < 1234; ++i)
{
numbers += "," + std::to_string(i);
}
const json_document array = json_document::parse(numbers + "]");
std::size_t position = 0;
for (const auto& item : array.root().items())
{
CHECK(item.value().get<std::size_t>() == position);
if (position % 3 != 0) // (not for every item: values alone do not make the key)
{
CHECK(item.key() == std::to_string(position));
CHECK(item.key().data() == item.key().data()); // (the same text every time)
const json_view::item copy = item;
CHECK(copy.key() == std::to_string(position));
}
++position;
}
CHECK(position == 1234);
#ifdef JSON_HAS_CPP_17 #ifdef JSON_HAS_CPP_17
std::string pairs; std::string pairs;
for (const auto [key, value] : d.root().items()) for (const auto [key, value] : d.root().items())
@@ -1496,17 +1383,7 @@ TEST_CASE("json_view values")
CHECK(exception_of([&] { static_cast<void>(v.value("s", 0)); }) == without_path(exception_of([&] { static_cast<void>(j.value("s", 0)); }))); CHECK(exception_of([&] { static_cast<void>(v.value("s", 0)); }) == without_path(exception_of([&] { static_cast<void>(j.value("s", 0)); })));
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", 0)); }))); CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", 0)); })));
CHECK(exception_of([&] { static_cast<void>(v["n"].value(json::json_pointer("/x"), 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value(json::json_pointer("/x"), 0)); }))); CHECK(exception_of([&] { static_cast<void>(v["n"].value(json::json_pointer("/x"), 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value(json::json_pointer("/x"), 0)); })));
// string defaults: the member is not a string, or the value is neither an object nor an array
CHECK(exception_of([&] { static_cast<void>(v.value("n", "default")); }) == without_path(exception_of([&] { static_cast<void>(j.value("n", "default")); })));
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", "default")); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", "default")); })));
CHECK(exception_of([&] { static_cast<void>(v.value(json::json_pointer("/n"), "default")); }) == without_path(exception_of([&] { static_cast<void>(j.value(json::json_pointer("/n"), "default")); })));
CHECK(exception_of([&] { static_cast<void>(v["s"].value(json::json_pointer("/x"), "default")); }) == without_path(exception_of([&] { static_cast<void>(j["s"].value(json::json_pointer("/x"), "default")); })));
#endif #endif
// (defaults and members longer than the short string optimization)
CHECK(v.value("s", "a default longer than fifteen characters") == "text");
CHECK(v.value("missing", "a default longer than fifteen characters") == "a default longer than fifteen characters");
CHECK(v.value(json::json_pointer("/s"), "a default longer than fifteen characters") == "text");
CHECK(v.value(json::json_pointer("/zz"), "a default longer than fifteen characters") == "a default longer than fifteen characters");
} }
} }
-52
View File
@@ -523,58 +523,6 @@ TEST_CASE("json_view builder: strings across vector blocks")
} }
} }
TEST_CASE("json_view builder: escapes between runs of text")
{
// The text between escapes is copied 16 bytes at a time where there are
// that many bytes left, and with the UTF-8 check where there is
// non-ASCII text. Runs of every length from 0 to 40 between valid and
// invalid escapes, UTF-8 sequences, and control characters, in strings
// that are cut at all places near the end of the text.
const std::array<const char*, 9> valid = {{"\\n", "\\\"", "\\\\", "\\/", "\\t", "\\u00e4", "\\u20ac", "\\ud83d\\ude00", "\\u0000"}};
const std::array<const char*, 9> invalid = {{"\\x", "\\ud800", "\\udc00", "\\u12G4", "\\u12", "\\ud83dx", "\\ud83d\\u0041", "\\", "\x01"}};
const std::array<const char*, 6> utf8 = {{"\xc3\xa4", "\xe2\x82\xac", "\xf0\x9f\x98\x80", "\xc3", "\xff", "\x80"}};
std::mt19937 rng(2026); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible
for (std::size_t run = 0; run <= 40; ++run)
{
for (int i = 0; i < 60; ++i)
{
std::string content = std::string(rng() % 20, 'a');
const auto pieces = 1 + (rng() % 6);
for (unsigned k = 0; k < pieces; ++k)
{
content += valid[rng() % valid.size()];
content += std::string(k == 0 ? run : rng() % 41, static_cast<char>('b' + (rng() % 20)));
switch (rng() % 8)
{
case 0:
content += utf8[rng() % 3];
break;
case 1:
content += utf8[rng() % utf8.size()];
break;
case 2:
content += invalid[rng() % invalid.size()];
break;
default:
break;
}
}
check_string(content);
check_string(content.substr(0, content.size() - (rng() % 4)));
}
}
// the text before the first escape, and runs and escapes at the end of the text
for (std::size_t before = 0; before <= 40; ++before)
{
for (const char* escape : valid)
{
check_string(std::string(before, 'a') + escape);
check_string(std::string(before, 'a') + escape + std::string(before % 19, 'z'));
check_string(std::string(before, 'a') + escape + escape + std::string(before % 19, 'z'));
}
}
}
TEST_CASE("json_view node integer bits") TEST_CASE("json_view node integer bits")
{ {
using nlohmann::detail::view::integer_bits; using nlohmann::detail::view::integer_bits;
-138
View File
@@ -21,7 +21,6 @@ using nlohmann::ordered_json_view;
using json_view_test::generator; using json_view_test::generator;
using json_view_test::has_duplicate_keys; using json_view_test::has_duplicate_keys;
#include <algorithm>
#include <array> #include <array>
#include <cmath> #include <cmath>
#include <cstddef> #include <cstddef>
@@ -30,7 +29,6 @@ using json_view_test::has_duplicate_keys;
#include <iomanip> #include <iomanip>
#include <map> #include <map>
#include <random> #include <random>
#include <set>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -600,142 +598,6 @@ TEST_CASE("json_view large objects")
CHECK(without_collisions.memory_usage() >= with_collisions.memory_usage() + (table_size * sizeof(std::uint32_t))); CHECK(without_collisions.memory_usage() >= with_collisions.memory_usage() + (table_size * sizeof(std::uint32_t)));
} }
SECTION("keys that differ in their last bytes are spread over the table")
{
// The hash must spread the usual families of keys (numbered names, hex
// strings, dates, short names): a key farther than index_max_displacement
// from its home slot would leave the object without a table. The keys
// are placed with linear probing, as the table does.
const auto max_displacement = [](const std::vector<std::string>& keys)
{
std::size_t slots = 16;
while (slots < 2 * keys.size())
{
slots *= 2;
}
std::vector<bool> used(slots, false);
std::size_t farthest = 0;
for (const std::string& key : keys)
{
std::size_t i = static_cast<std::size_t>(nlohmann::detail::view::key_hash(key.data(), key.size())) & (slots - 1);
std::size_t distance = 0;
while (used[i])
{
i = (i + 1) & (slots - 1);
++distance;
}
used[i] = true;
farthest = (std::max)(farthest, distance);
}
return farthest;
};
// the keys of a printf-like pattern with one %d, %0Nd or %0Nx, formatted with a stream
// (a format string that is not a literal is a Flawfinder error)
const auto numbered = [](const char* format, int count, int step)
{
const std::string pattern(format);
const std::size_t percent = pattern.find('%');
std::size_t conversion = percent + 1;
std::size_t width = 0;
while (pattern[conversion] >= '0' && pattern[conversion] <= '9')
{
width = width * 10 + static_cast<std::size_t>(pattern[conversion] - '0');
++conversion;
}
const bool hex = pattern[conversion] == 'x';
std::vector<std::string> keys;
for (int i = 0; i < count; ++i)
{
std::ostringstream key;
key << pattern.substr(0, percent) << std::setfill('0') << std::setw(static_cast<int>(width))
<< (hex ? std::hex : std::dec) << i * step << pattern.substr(conversion + 1);
keys.push_back(key.str());
}
return keys;
};
for (const char* format :
{"key%d", "%d", "item_%04d", "a%05d", "%03dxyz", "id%d", "user_%d", "%d_suffix", "0x%04x", "A%07d", "12345678%d", "1234567%d", "123456789%d",
"configuration_parameter_value_%04d_extra", "2026-01-%02dT00:00:00Z"
})
{
CAPTURE(format)
CHECK(max_displacement(numbered(format, std::string(format).find("%02d") != std::string::npos ? 60 : 1000, 1)) < nlohmann::detail::view::index_max_displacement);
CHECK(max_displacement(numbered(format, 600, 7)) < nlohmann::detail::view::index_max_displacement);
}
std::mt19937_64 rng(7);
for (const std::size_t length :
{
std::size_t{1}, std::size_t{2}, std::size_t{3}, std::size_t{5}, std::size_t{7}, std::size_t{8}, std::size_t{9}, std::size_t{12}, std::size_t{16}, std::size_t{17}, std::size_t{33}
})
{
CAPTURE(length)
std::set<std::string> distinct;
while (distinct.size() < (length == 1 ? 26 : (length == 2 ? 500 : 700))) // (there are 26 and 676 keys of one and two letters)
{
std::string key(length, 'a');
for (char& c : key)
{
c = static_cast<char>('a' + (rng() % 26));
}
distinct.insert(key);
}
CHECK(max_displacement(std::vector<std::string>(distinct.begin(), distinct.end())) < nlohmann::detail::view::index_max_displacement);
}
}
SECTION("keys of every length that differ in one byte")
{
// In a large object, a key is found by comparing its bytes with those of the member the
// table points to: up to 16 bytes as two overlapping words, longer ones with memcmp.
// Keys of every length from 0 to 40, and keys that differ from them in one byte.
std::mt19937_64 rng(2026);
std::vector<std::string> keys;
std::set<std::string> distinct;
for (std::size_t length = 0; length <= 40; ++length)
{
for (int copy = 0; copy < 5; ++copy)
{
std::string key(length, 'a');
for (char& c : key)
{
c = static_cast<char>('a' + (rng() % 26));
}
if (distinct.insert(key).second)
{
keys.push_back(key);
}
}
}
std::string text = "{";
for (std::size_t i = 0; i < keys.size(); ++i)
{
text += (i != 0 ? ",\"" : "\"") + keys[i] + "\":" + std::to_string(i);
}
text += "}";
REQUIRE(keys.size() >= 128); // (a table is built for 128 members)
const json_document d = json_document::parse(text);
const json_view v = d.root();
for (std::size_t i = 0; i < keys.size(); ++i)
{
CAPTURE(keys[i])
CHECK(v[keys[i]].get<std::size_t>() == i);
CHECK(v.at(keys[i]).get<std::size_t>() == i);
CHECK(v.contains(keys[i]));
for (std::size_t at = 0; at < keys[i].size(); ++at)
{
std::string other = keys[i];
other[at] = other[at] == 'z' ? 'y' : 'z';
CAPTURE(other)
CHECK(v.contains(other) == (distinct.count(other) != 0));
}
CHECK(v.contains(keys[i] + "a") == (distinct.count(keys[i] + "a") != 0));
if (!keys[i].empty())
{
CHECK(v.contains(keys[i].substr(1)) == (distinct.count(keys[i].substr(1)) != 0));
}
}
}
SECTION("shrink_to_fit releases the tables' spare capacity") SECTION("shrink_to_fit releases the tables' spare capacity")
{ {
const auto make_text = [](int objects, int members) const auto make_text = [](int objects, int members)
-109
View File
@@ -661,115 +661,6 @@ TEST_CASE("json_view edits: replacing arrays and objects by scalars")
CHECK(d.root().materialize() == expected); CHECK(d.root().materialize() == expected);
} }
TEST_CASE("json_view edits: arrays whose elements have no subtrees")
{
// such an array is indexed directly while it is in its parsed layout; edits
// that replace elements by scalars keep it, others move it
json_editable_document d = json_editable_document::parse("[10,20,30,40]");
json expected = json::array({10, 20, 30, 40});
const auto check = [&]
{
CHECK(d.root().size() == expected.size());
for (std::size_t i = 0; i < expected.size(); ++i)
{
CHECK(d.root()[i].materialize() == expected[i]);
CHECK(d.root().at(i).materialize() == expected[i]);
}
CHECK(d.root()[expected.size()].is_discarded());
CHECK(d.root().back().materialize() == expected.back());
CHECK(d.root().dump() == expected.dump());
};
check();
d.set(d.root()[1], "x");
expected[1] = "x";
check();
d.set(d.root()[3], 2.5);
expected[3] = 2.5;
check();
d.set(d.root()[2], json::array({1, 2}));
expected[2] = json::array({1, 2});
check();
d.push_back(d.root(), 50);
expected.push_back(50);
check();
d.insert(d.root(), 0, true);
expected.insert(expected.begin(), true);
check();
d.erase(d.root(), 1);
expected.erase(expected.begin() + 1);
check();
d.set(d.root()[2], nullptr);
expected[2] = nullptr;
check();
}
TEST_CASE("json_view edits: materializing values nested deeper than the document was parsed")
{
// the stack of open containers of materialize() is sized from the nesting of the parsed text;
// a value added by an edit can be deeper than that (and than the stack that is local to the call)
json_editable_document d = json_editable_document::parse("[1,[2]]");
json expected = json::array({1, json::array({2})});
json nested = 3;
for (int level = 0; level < 40; ++level)
{
nested = json::array({nested, json{{"k", level}}});
}
d.push_back(d.root(), nested);
expected.push_back(nested);
CHECK(d.root().materialize() == expected);
CHECK(d.root()[2].materialize() == nested);
d.set(d.root()[1][0], nested);
expected[1][0] = nested;
CHECK(d.root().materialize() == expected);
CHECK(d.root().get<json>() == expected);
}
TEST_CASE("json_view edits: an editable document before and after its first edit")
{
// until the first edit, an editable document is read like a read-only one; views made before the edit
// keep working after it
const std::string text = R"({"a":[1,2,{"x":"y"},4],"b":{"c":true,"d":null},"e":"text","f":[]})";
const json_document read_only = json_document::parse(text);
json_editable_document d = json_editable_document::parse(text);
const auto root = d.root();
const auto same_as_read_only = [&]
{
CHECK(root.dump() == read_only.root().dump());
CHECK(root.dump(2) == read_only.root().dump(2));
CHECK(root.materialize() == read_only.root().materialize());
CHECK(root["a"][2]["x"].get_string() == "y");
CHECK(root["a"].at(3).get<int>() == 4);
CHECK(root["a"].back().get<int>() == 4);
CHECK(root.contains("b"));
CHECK(!root.contains("zz"));
CHECK(root.find("e")->get_string() == "text");
CHECK(root.find("zz") == root.end());
CHECK(root["b"]["c"].get<bool>());
CHECK(root.value("e", "none") == "text");
std::size_t count = 0;
for (const auto m : root["a"])
{
CHECK(m.materialize() == read_only.root()["a"][count].materialize());
++count;
}
CHECK(count == 4);
};
same_as_read_only();
// an edit that changes a scalar in place, then one that moves an array
d.set(root["e"], "other");
CHECK(root["e"].get_string() == "other");
CHECK(root["a"][2]["x"].get_string() == "y");
CHECK(root["a"].back().get<int>() == 4);
d.push_back(root["f"], 7);
CHECK(root["f"][0].get<int>() == 7);
CHECK(root["f"].back().get<int>() == 7);
CHECK(root.dump() == R"({"a":[1,2,{"x":"y"},4],"b":{"c":true,"d":null},"e":"other","f":[7]})");
d.set(root["a"], 2, json::array({1, 2}));
CHECK(root["a"][2][1].get<int>() == 2);
CHECK(root["a"].dump() == "[1,2,[1,2],4]");
CHECK(root.materialize() == json::parse(R"({"a":[1,2,[1,2],4],"b":{"c":true,"d":null},"e":"other","f":[7]})"));
}
namespace namespace
{ {
// reads of "a" in an object that repeats it: the first member wins, however the // reads of "a" in an object that repeats it: the first member wins, however the
+34 -6
View File
@@ -48,14 +48,16 @@ namespace
class chunked_streambuf : public std::streambuf class chunked_streambuf : public std::streambuf
{ {
public: public:
chunked_streambuf(std::string s, std::size_t chunk_size) // The first @a unbuffered characters are served without a get area, the
: m_data(std::move(s)), m_chunk_size(chunk_size) // rest in get areas of @a chunk_size.
chunked_streambuf(std::string s, std::size_t chunk_size, std::size_t unbuffered = 0)
: m_data(std::move(s)), m_chunk_size(chunk_size), m_unbuffered(unbuffered)
{} {}
protected: protected:
int_type underflow() override int_type underflow() override
{ {
if (m_chunk_size == 0) if (m_chunk_size == 0 || m_pos < m_unbuffered)
{ {
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos]) : traits_type::eof(); return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos]) : traits_type::eof();
} }
@@ -77,7 +79,7 @@ class chunked_streambuf : public std::streambuf
int_type uflow() override int_type uflow() override
{ {
if (m_chunk_size == 0) if (m_chunk_size == 0 || m_pos < m_unbuffered)
{ {
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos++]) : traits_type::eof(); return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos++]) : traits_type::eof();
} }
@@ -88,6 +90,7 @@ class chunked_streambuf : public std::streambuf
std::string m_data; std::string m_data;
std::size_t m_pos = 0; std::size_t m_pos = 0;
std::size_t m_chunk_size; std::size_t m_chunk_size;
std::size_t m_unbuffered;
}; };
// how a test reads from the stream // how a test reads from the stream
@@ -101,9 +104,9 @@ enum class read_mode
// Read @a input from a chunked_streambuf and describe everything observable: // Read @a input from a chunked_streambuf and describe everything observable:
// the values or the exception, the stream state after each value, and the // the values or the exception, the stream state after each value, and the
// characters left in the stream. // characters left in the stream.
std::string observe(const std::string& input, std::size_t chunk_size, read_mode mode) std::string observe(const std::string& input, std::size_t chunk_size, read_mode mode, std::size_t unbuffered = 0)
{ {
chunked_streambuf buf(input, chunk_size); chunked_streambuf buf(input, chunk_size, unbuffered);
std::istream is(&buf); std::istream is(&buf);
std::string result; std::string result;
for (int i = 0; i < 4; ++i) for (int i = 0; i < 4; ++i)
@@ -212,6 +215,31 @@ TEST_CASE("parsing from a std::istream scans the get area in bulk")
} }
} }
SECTION("results do not depend on a get area that appears late")
{
// the first characters come without a get area, then the streambuf
// starts to use one: the token that is read at that point is complete
for (const auto& input : corpus())
{
const std::string expected = observe(input, 0, read_mode::extract);
for (const std::size_t unbuffered :
{
1u, 2u, 3u, 5u, 8u, 13u, 21u
})
{
for (const std::size_t chunk_size :
{
1u, 2u, 3u, 7u, 64u
})
{
CAPTURE(input)
CAPTURE(unbuffered)
CAPTURE(chunk_size)
CHECK(observe(input, chunk_size, read_mode::extract, unbuffered) == expected);
}
}
}
}
#endif // !defined(JSON_NOEXCEPTION) #endif // !defined(JSON_NOEXCEPTION)
SECTION("values are the same as when parsing from a string") SECTION("values are the same as when parsing from a string")