Compare commits

..
Author SHA1 Message Date
Niels Lohmann f2f64eb67c Bump lukka/get-cmake from 4.4.5 to 4.5.0
get-cmake 4.4.5 does not know CMake 4.5.0, so the ci_module_cpp20 jobs
that request '^4.5.0' failed in the setup step. Bump every pin so all
jobs use the same action version.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-13 14:32:21 +01:00
Niels Lohmann 3004829134 Use CMake 4.5's stable import std support in the module test
CMake 4.5 makes `import std` available to every target that scans for
C++ modules and drops the experimental CMAKE_EXPERIMENTAL_CXX_IMPORT_STD
gate and the CXX_MODULE_STD property. The module test no longer needs the
version-specific opt-in token, so the ci_module_cpp20 jobs no longer have
to pin CMake 4.3.4 and now request CMake ^4.5.0.

The modules, CMake and README documentation now list the CMake, generator
and toolchain requirements for building the module and for `import std;`.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-13 09:36:55 +01:00
13 changed files with 140 additions and 600 deletions

No files matched your search

+21 -23
View File
@@ -25,7 +25,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Get latest CMake and ninja - name: Get latest CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5 uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Run CMake - name: Run CMake
run: cmake -S . -B build -DJSON_CI=On run: cmake -S . -B build -DJSON_CI=On
- name: Build - name: Build
@@ -38,7 +38,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Get latest CMake and ninja - name: Get latest CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5 uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Check that Meson and pkg-config offer the CMake options - name: Check that Meson and pkg-config offer the CMake options
run: make check_build_options run: make check_build_options
- name: Install Meson - name: Install Meson
@@ -92,7 +92,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Get latest CMake and ninja - name: Get latest CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5 uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Run CMake - name: Run CMake
run: cmake -S . -B build -DJSON_CI=On run: cmake -S . -B build -DJSON_CI=On
- name: Build - name: Build
@@ -123,7 +123,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Get latest CMake and ninja - name: Get latest CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5 uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Run CMake - name: Run CMake
run: cmake -S . -B build -DJSON_CI=On run: cmake -S . -B build -DJSON_CI=On
- name: Build - name: Build
@@ -142,7 +142,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Get latest CMake and ninja - name: Get latest CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5 uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Run CMake - name: Run CMake
run: cmake -S . -B build -DJSON_CI=On run: cmake -S . -B build -DJSON_CI=On
- name: Build - name: Build
@@ -168,7 +168,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Get latest CMake and ninja - name: Get latest CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5 uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Run CMake - name: Run CMake
run: cmake -S . -B build -DJSON_CI=On run: cmake -S . -B build -DJSON_CI=On
- name: Build - name: Build
@@ -244,7 +244,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Get latest CMake and ninja - name: Get latest CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5 uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Run CMake - name: Run CMake
run: CXX=g++-${{ matrix.compiler }} cmake -S . -B build -DJSON_CI=On run: CXX=g++-${{ matrix.compiler }} cmake -S . -B build -DJSON_CI=On
- name: Build - name: Build
@@ -262,7 +262,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Get latest CMake and ninja - name: Get latest CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5 uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Run CMake - name: Run CMake
run: cmake -S . -B build -DJSON_CI=On run: cmake -S . -B build -DJSON_CI=On
- name: Build - name: Build
@@ -279,7 +279,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Get latest CMake and ninja - name: Get latest CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5 uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Disable filesystem support (std::filesystem is unusable) for clang 7 / 8 / 9 / 10 - name: Disable filesystem support (std::filesystem is unusable) for clang 7 / 8 / 9 / 10
run: echo "JSON_FORCED_GLOBAL_COMPILE_OPTIONS=-DJSON_HAS_FILESYSTEM=0;-DJSON_HAS_EXPERIMENTAL_FILESYSTEM=0" >> "$GITHUB_ENV" run: echo "JSON_FORCED_GLOBAL_COMPILE_OPTIONS=-DJSON_HAS_FILESYSTEM=0;-DJSON_HAS_EXPERIMENTAL_FILESYSTEM=0" >> "$GITHUB_ENV"
if: ${{ matrix.compiler == '7' || matrix.compiler == '8' || matrix.compiler == '9' || matrix.compiler == '10' }} if: ${{ matrix.compiler == '7' || matrix.compiler == '8' || matrix.compiler == '9' || matrix.compiler == '10' }}
@@ -299,7 +299,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Get latest CMake and ninja - name: Get latest CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5 uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Run CMake - name: Run CMake
run: cmake -S . -B build -DJSON_CI=On run: cmake -S . -B build -DJSON_CI=On
- name: Build - name: Build
@@ -319,7 +319,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Get latest CMake and ninja - name: Get latest CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5 uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Run CMake - name: Run CMake
run: cmake -S . -B build -DJSON_CI=On run: cmake -S . -B build -DJSON_CI=On
- name: Build with libc++ - name: Build with libc++
@@ -346,7 +346,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Get latest CMake and ninja - name: Get latest CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5 uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Run CMake - name: Run CMake
run: cmake -S . -B build -DJSON_CI=On run: cmake -S . -B build -DJSON_CI=On
- name: Build - name: Build
@@ -362,13 +362,11 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
persist-credentials: false persist-credentials: false
# The module test uses `import std;`, which needs CMake's experimental # The module test uses `import std;`, which needs CMake 4.5 or newer.
# import-std support. Its opt-in token is CMake-version-specific, so pin - name: Get CMake 4.5+ and ninja
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt. uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Get pinned CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
with: with:
cmakeVersion: 4.3.4 cmakeVersion: '^4.5.0'
# Clang: the std library module is provided by libc++ (the image's libstdc++ # Clang: the std library module is provided by libc++ (the image's libstdc++
# ships none), and the image's libc++ module manifest has a broken relative # ships none), and the image's libc++ module manifest has a broken relative
# path — repoint it at the real module sources. # path — repoint it at the real module sources.
@@ -392,7 +390,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Get latest CMake and ninja - name: Get latest CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5 uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Run CMake - name: Run CMake
run: cmake -S . -B build -DJSON_CI=On run: cmake -S . -B build -DJSON_CI=On
- name: Build - name: Build
@@ -409,7 +407,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Get latest CMake and ninja - name: Get latest CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5 uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Run CMake - name: Run CMake
run: cmake -S . -B build -DJSON_CI=On run: cmake -S . -B build -DJSON_CI=On
- name: Build - name: Build
@@ -423,7 +421,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Get latest CMake and ninja - name: Get latest CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5 uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Run CMake - name: Run CMake
run: cmake -S . -B build -DJSON_CI=On run: cmake -S . -B build -DJSON_CI=On
- name: Build - name: Build
@@ -443,7 +441,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Get latest CMake and ninja - name: Get latest CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5 uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Run CMake - name: Run CMake
run: cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -GNinja run: cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -GNinja
- name: Build - name: Build
@@ -484,7 +482,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Get latest CMake and ninja - name: Get latest CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5 uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Check that the job runs natively on arm64 - name: Check that the job runs natively on arm64
run: uname -m && test "$(uname -m)" = aarch64 run: uname -m && test "$(uname -m)" = aarch64
- name: Run CMake - name: Run CMake
+5 -7
View File
@@ -90,7 +90,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Get latest CMake and ninja - name: Get latest CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5 uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Set extra CXX_FLAGS for latest std_version - name: Set extra CXX_FLAGS for latest std_version
# /wd5285 silences C5285 emitted by the bundled third-party doctest.h, which # /wd5285 silences C5285 emitted by the bundled third-party doctest.h, which
# specializes std::tuple (newly diagnosed by the VS2026 v145 toolset) # specializes std::tuple (newly diagnosed by the VS2026 v145 toolset)
@@ -212,13 +212,11 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
persist-credentials: false persist-credentials: false
# The module test uses `import std;`, which needs CMake's experimental # The module test uses `import std;`, which needs CMake 4.5 or newer.
# import-std support. Its opt-in token is CMake-version-specific, so pin - name: Get CMake 4.5+ and ninja
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt. uses: lukka/get-cmake@16e8153a8b4918849d92aad131befe67a3705daa # v4.5.0
- name: Get pinned CMake and ninja
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
with: with:
cmakeVersion: 4.3.4 cmakeVersion: '^4.5.0'
# CMAKE_CXX_FLAGS replaces CMake's MSVC defaults (which include /EHsc), and # CMAKE_CXX_FLAGS replaces CMake's MSVC defaults (which include /EHsc), and
# ci_module_cpp20 forwards it to the module build, so list /EHsc explicitly # ci_module_cpp20 forwards it to the module build, so list /EHsc explicitly
- name: Run CMake (Debug) - name: Run CMake (Debug)
+2 -1
View File
@@ -165,7 +165,8 @@ std::ifstream f("example.json");
json data = json::parse(f); json data = json::parse(f);
``` ```
If using modules (enabled with `NLOHMANN_JSON_BUILD_MODULES`), this example becomes: If using modules (enabled with `NLOHMANN_JSON_BUILD_MODULES`; `import std;` needs CMake 4.5 or newer, see the
[modules documentation](https://json.nlohmann.me/features/modules/)), this example becomes:
```cpp ```cpp
import std; import std;
import nlohmann.json; import nlohmann.json;
+14 -2
View File
@@ -9,7 +9,19 @@ Importing the module instead of including the header reduces the compile time of
## Requirements ## Requirements
The `nlohmann.json` module requires that the build system is configured to build and resolve modules when imported. Obviously, as modules were introduced in C++20, this feature can only be used in C++20 and subsequent versions. The `nlohmann.json` module requires that the build system is configured to build and resolve modules when imported. Obviously, as modules were introduced in C++20, this feature can only be used in C++20 and subsequent versions.
To enable building the `nlohmann.json` module (which is not done by default), the macro `NLOHMANN_JSON_BUILD_MODULES` must be passed to the build system. To enable building the `nlohmann.json` module (which is not done by default), set the CMake option
[`NLOHMANN_JSON_BUILD_MODULES`](../integration/cmake.md#nlohmann_json_build_modules) and link the
`nlohmann_json_modules` target.
With CMake, building the module needs:
- CMake 3.28 or newer, and
- a generator that scans for modules: Ninja (1.11 or newer), Ninja Multi-Config, or Visual Studio 2022 or newer.
The example below also uses `import std;`. CMake supports it without experimental opt-in since version 4.5, and then
only with the Ninja generators and these toolchains: Clang 18.1.2 or newer (with libc++ or libstdc++), MSVC toolset 14.36
(Visual Studio 17.6) or newer, and GCC 15 or newer (GCC 16 on macOS). With older CMake versions, `import std;` needs
CMake's version-specific experimental opt-in.
## Example ## Example
When using modules rather than headers, the previous example for creating a `json` object through a JSON file, would instead be: When using modules rather than headers, the previous example for creating a `json` object through a JSON file, would instead be:
@@ -67,7 +79,7 @@ and should not be used directly:
## Known issues ## Known issues
C++20 modules support is exercised in CI against current GCC and Clang on Ubuntu, and the default MSVC toolset on Windows Server 2022 — there is no documented minimum compiler version, unlike feature-test-macro-gated features such as [`JSON_HAS_RANGES`](../api/macros/json_has_ranges.md). C++20 modules support is exercised in CI with CMake 4.5 and `import std;` against current GCC and Clang on Ubuntu, and the default MSVC toolset on Windows Server 2022 — there is no documented minimum compiler version, unlike feature-test-macro-gated features such as [`JSON_HAS_RANGES`](../api/macros/json_has_ranges.md).
!!! info "Known compiler issues" !!! info "Known compiler issues"
-2
View File
@@ -140,8 +140,6 @@ Exceptions have ids 1xx.
This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member `byte` indicates the error position. This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member `byte` indicates the error position.
The message ends with the last read token (`last read: '...'`), with control characters written as `<U+XXXX>`. A token of more than 8192 characters is shown by its first and its last 4096 characters, separated by `...`.
!!! failure "Example message" !!! failure "Example message"
Input ended prematurely: Input ended prematurely:
+3
View File
@@ -269,6 +269,9 @@ target_link_libraries(myproject PRIVATE nlohmann_json_modules)
target_compile_definitions(myproject PRIVATE NLOHMANN_JSON_BUILD_MODULES) target_compile_definitions(myproject PRIVATE NLOHMANN_JSON_BUILD_MODULES)
``` ```
If `main.cpp` also uses `import std;`, the project needs CMake 4.5 or newer and a Ninja generator; see
[Modules](../features/modules.md#requirements) for the supported toolchains.
## See also ## See also
- [Macros](../api/macros/index.md) - reference of all macros - [Macros](../api/macros/index.md) - reference of all macros
+36 -113
View File
@@ -110,164 +110,87 @@ inline const char* empty_input_message() noexcept
return "attempting to parse an empty input; check that your input string or stream contains the expected JSON"; return "attempting to parse an empty input; check that your input string or stream contains the expected JSON";
} }
////////////////////
// input adapters //
////////////////////
#ifndef JSON_NO_IO
/*! /*!
@brief the characters of the last read token, kept for error messages
A growable array of characters for the part of a token that an input adapter A growable array of characters for the part of a token that an input adapter
copied out of its window, or that the lexer copies while it scans input it copied out of its window: unlike std::vector<char>::push_back(), appending a
cannot read again: unlike std::vector::push_back(), appending a character character takes no reference to a local, and is a few instructions when there
takes no reference to a local, and is a few instructions when there is room. is room.
The token is only needed for error messages, which show its first head()
characters and its last head() characters (see lexer::get_token_string). The
buffer therefore keeps the first head() and at least the last tail()
characters: when it is full and holds more than limit() characters, it drops
the characters in between instead of growing. That happens where it would
otherwise reallocate, so appending stays as cheap as before, and a long string
or a long run of whitespace read from a stream is not kept a second time.
*/ */
template<typename CharType> class token_carry_buffer
class token_text_buffer
{ {
public: public:
static constexpr std::size_t head() noexcept token_carry_buffer() = default;
{ token_carry_buffer(const token_carry_buffer&) = delete;
return 4096; token_carry_buffer& operator=(const token_carry_buffer&) = delete;
} token_carry_buffer(token_carry_buffer&& rhs) noexcept
/// a little more than head(), as the last character may be handed back
static constexpr std::size_t tail() noexcept
{
return head() + 16;
}
static constexpr std::size_t limit() noexcept
{
return head() + (2 * tail());
}
token_text_buffer() = default;
token_text_buffer(const token_text_buffer&) = delete;
token_text_buffer& operator=(const token_text_buffer&) = delete;
token_text_buffer(token_text_buffer&& rhs) noexcept
: m_data(std::move(rhs.m_data)), m_end(rhs.m_end), m_capacity_end(rhs.m_capacity_end) : 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; rhs.m_end = rhs.m_capacity_end = nullptr;
} }
token_text_buffer& operator=(token_text_buffer&& rhs) noexcept token_carry_buffer& operator=(token_carry_buffer&&) = delete;
{ ~token_carry_buffer() = default;
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;
return *this;
}
~token_text_buffer() = default;
void clear() noexcept void clear() noexcept
{ {
m_end = m_data.get(); m_end = m_data.get();
} }
bool empty() const noexcept void push_back(char c)
{
return m_end == m_data.get();
}
void push_back(CharType c)
{ {
if (JSON_HEDLEY_UNLIKELY(m_end == m_capacity_end)) if (JSON_HEDLEY_UNLIKELY(m_end == m_capacity_end))
{ {
make_room(1); grow(1);
} }
*m_end++ = c; *m_end++ = c;
} }
/// drop the last character void append(const char* first, const char* last)
void pop_back() noexcept
{
JSON_ASSERT(!empty());
--m_end;
}
void append(const CharType* first, const CharType* last)
{ {
const auto n = static_cast<std::size_t>(last - first); const auto n = static_cast<std::size_t>(last - first);
const auto room = static_cast<std::size_t>(m_capacity_end - m_end); const auto room = static_cast<std::size_t>(m_capacity_end - m_end);
if (JSON_HEDLEY_UNLIKELY(n > room)) if (n > room)
{ {
append_long(first, n); grow(n);
return;
} }
if (n != 0) if (n != 0)
{ {
std::memcpy(m_end, first, n * sizeof(CharType)); std::memcpy(m_end, first, n);
} }
m_end += n; m_end += n;
} }
/// replace the contents of @a out /// replace the contents of @a out
void copy_to(std::vector<CharType>& out) const void copy_to(std::vector<char>& out) const
{ {
out.assign(m_data.get(), m_end); out.assign(m_data.get(), m_end);
} }
private: private:
/// append @a n characters that do not fit, in pieces of at most tail() /// make room for @a extra more characters
/// characters, so that dropping the middle keeps the last tail() of them JSON_HEDLEY_NEVER_INLINE void grow(std::size_t extra)
JSON_HEDLEY_NEVER_INLINE void append_long(const CharType* first, std::size_t n)
{ {
while (n != 0) 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)
{ {
const std::size_t piece = n < tail() ? n : tail(); std::memcpy(data.get(), m_data.get(), size);
make_room(piece);
std::memcpy(m_end, first, piece * sizeof(CharType));
m_end += piece;
first += piece;
n -= piece;
} }
m_data = std::move(data);
m_end = m_data.get() + size;
m_capacity_end = m_data.get() + new_capacity;
} }
/// make room for @a extra (at most tail()) more characters: drop the std::unique_ptr<char[]> m_data{}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,readability-redundant-member-init)
/// middle of a long token instead of growing beyond limit() char* m_end = nullptr;
JSON_HEDLEY_NEVER_INLINE void make_room(std::size_t extra) char* m_capacity_end = nullptr;
{
// nothing is allocated before the first character
std::size_t size = m_data ? static_cast<std::size_t>(m_end - m_data.get()) : 0;
if (size + extra > limit() && size > head() + tail())
{
std::memmove(m_data.get() + head(), m_end - tail(), tail() * sizeof(CharType));
m_end = m_data.get() + head() + tail();
size = head() + tail();
}
const std::size_t capacity = m_data ? static_cast<std::size_t>(m_capacity_end - m_data.get()) : 0;
if (size + extra > capacity)
{
// never more than limit(): from there on, the middle is dropped
const std::size_t doubled = capacity < 32 ? static_cast<std::size_t>(32) : 2 * capacity;
const std::size_t new_capacity = (std::max)(doubled < limit() ? doubled : limit(), size + extra);
std::unique_ptr<CharType[]> data(new CharType[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 * sizeof(CharType));
}
m_data = std::move(data);
m_end = m_data.get() + size;
m_capacity_end = m_data.get() + new_capacity;
}
}
std::unique_ptr<CharType[]> m_data{}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,readability-redundant-member-init)
CharType* m_end = nullptr;
CharType* m_capacity_end = nullptr;
}; };
/// the part of a token that an input adapter copied out of its window
using token_carry_buffer = token_text_buffer<char>;
////////////////////
// input adapters //
////////////////////
#ifndef JSON_NO_IO
/*! /*!
Input adapter for stdio file access. The file is locked for the lifetime of the Input adapter for stdio file access. The file is locked for the lifetime of the
adapter and read with the unlocked stdio functions (see adapter and read with the unlocked stdio functions (see
+9 -47
View File
@@ -494,10 +494,7 @@ class lexer : public lexer_base<BasicJsonType>
/// streaming adapter: capture the characters /// streaming adapter: capture the characters
void bulk_track_token(const char* data, std::size_t n, std::false_type /*lazy*/) void bulk_track_token(const char* data, std::size_t n, std::false_type /*lazy*/)
{ {
for (std::size_t i = 0; i < n; ++i) token_string.insert(token_string.end(), data, data + n);
{
token_string.push_back(static_cast<char_type>(data[i]));
}
} }
/// streaming input: no bulk fast path /// streaming input: no bulk fast path
@@ -2240,28 +2237,15 @@ scan_number_done:
const std::size_t stop = ia.get_consumed_count() - (pending_real_unget ? 1u : 0u); const std::size_t stop = ia.get_consumed_count() - (pending_real_unget ? 1u : 0u);
if (JSON_HEDLEY_LIKELY(stop >= token_string_start)) if (JSON_HEDLEY_LIKELY(stop >= token_string_start))
{ {
// only the beginning and the end of a long token are shown (see ia.copy_consumed_range(token_string_start, stop, out);
// get_token_string), so copy only these
constexpr std::size_t head = token_text_buffer<char_type>::head();
constexpr std::size_t tail = token_text_buffer<char_type>::tail();
if (stop - token_string_start > head + tail)
{
ia.copy_consumed_range(token_string_start, token_string_start + head, out);
ia.copy_consumed_range(stop - tail, stop, out);
}
else
{
ia.copy_consumed_range(token_string_start, stop, out);
}
} }
return out; return out;
} }
/// streaming adapter: the token was copied eagerly while scanning /// streaming adapter: the token was copied eagerly while scanning
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::false_type /*lazy*/) const const std::vector<char_type>& collect_token_chars(std::vector<char_type>& /*out*/, std::false_type /*lazy*/) const
{ {
token_string.copy_to(out); return token_string;
return out;
} }
/// return the last read token (for errors only). Will never contain EOF /// return the last read token (for errors only). Will never contain EOF
@@ -2272,32 +2256,10 @@ scan_number_done:
std::vector<char_type> reconstructed; std::vector<char_type> reconstructed;
const std::vector<char_type>& chars = collect_token_chars(reconstructed, std::integral_constant<bool, lazy_token_string> {}); const std::vector<char_type>& chars = collect_token_chars(reconstructed, std::integral_constant<bool, lazy_token_string> {});
// a long token is shown by its first and its last head() characters; // escape control characters
// the buffers that keep it while it is read keep no more than these
// and a few more (see token_text_buffer)
constexpr std::size_t shown = token_text_buffer<char_type>::head();
std::string result; std::string result;
if (chars.size() > 2 * shown) for (const auto c : chars)
{ {
append_escaped(result, chars.begin(), chars.begin() + static_cast<std::ptrdiff_t>(shown));
result += "...";
append_escaped(result, chars.end() - static_cast<std::ptrdiff_t>(shown), chars.end());
}
else
{
append_escaped(result, chars.begin(), chars.end());
}
return result;
}
private:
/// append the characters [first, last) to @a result, escaping control characters
template<typename Iterator>
static void append_escaped(std::string& result, Iterator first, Iterator last)
{
for (; first != last; ++first)
{
const auto c = *first;
if (static_cast<unsigned char>(c) <= '\x1F') if (static_cast<unsigned char>(c) <= '\x1F')
{ {
// escape control characters // escape control characters
@@ -2311,9 +2273,9 @@ scan_number_done:
result.push_back(static_cast<std::string::value_type>(c)); result.push_back(static_cast<std::string::value_type>(c));
} }
} }
}
public: return result;
}
/// return syntax error message /// return syntax error message
JSON_HEDLEY_RETURNS_NON_NULL JSON_HEDLEY_RETURNS_NON_NULL
@@ -2563,7 +2525,7 @@ scan_number_done:
/// raw input token string for error messages; only populated for streaming /// raw input token string for error messages; only populated for streaming
/// adapters (seekable adapters reconstruct it lazily via token_string_start) /// adapters (seekable adapters reconstruct it lazily via token_string_start)
token_text_buffer<char_type> token_string {}; std::vector<char_type> token_string {};
/// start offset of the current token within the input, used to reconstruct /// start offset of the current token within the input, used to reconstruct
/// the last read token on error for seekable adapters (see collect_token_chars) /// the last read token on error for seekable adapters (see collect_token_chars)
+45 -160
View File
@@ -9323,164 +9323,87 @@ inline const char* empty_input_message() noexcept
return "attempting to parse an empty input; check that your input string or stream contains the expected JSON"; return "attempting to parse an empty input; check that your input string or stream contains the expected JSON";
} }
////////////////////
// input adapters //
////////////////////
#ifndef JSON_NO_IO
/*! /*!
@brief the characters of the last read token, kept for error messages
A growable array of characters for the part of a token that an input adapter A growable array of characters for the part of a token that an input adapter
copied out of its window, or that the lexer copies while it scans input it copied out of its window: unlike std::vector<char>::push_back(), appending a
cannot read again: unlike std::vector::push_back(), appending a character character takes no reference to a local, and is a few instructions when there
takes no reference to a local, and is a few instructions when there is room. is room.
The token is only needed for error messages, which show its first head()
characters and its last head() characters (see lexer::get_token_string). The
buffer therefore keeps the first head() and at least the last tail()
characters: when it is full and holds more than limit() characters, it drops
the characters in between instead of growing. That happens where it would
otherwise reallocate, so appending stays as cheap as before, and a long string
or a long run of whitespace read from a stream is not kept a second time.
*/ */
template<typename CharType> class token_carry_buffer
class token_text_buffer
{ {
public: public:
static constexpr std::size_t head() noexcept token_carry_buffer() = default;
{ token_carry_buffer(const token_carry_buffer&) = delete;
return 4096; token_carry_buffer& operator=(const token_carry_buffer&) = delete;
} token_carry_buffer(token_carry_buffer&& rhs) noexcept
/// a little more than head(), as the last character may be handed back
static constexpr std::size_t tail() noexcept
{
return head() + 16;
}
static constexpr std::size_t limit() noexcept
{
return head() + (2 * tail());
}
token_text_buffer() = default;
token_text_buffer(const token_text_buffer&) = delete;
token_text_buffer& operator=(const token_text_buffer&) = delete;
token_text_buffer(token_text_buffer&& rhs) noexcept
: m_data(std::move(rhs.m_data)), m_end(rhs.m_end), m_capacity_end(rhs.m_capacity_end) : 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; rhs.m_end = rhs.m_capacity_end = nullptr;
} }
token_text_buffer& operator=(token_text_buffer&& rhs) noexcept token_carry_buffer& operator=(token_carry_buffer&&) = delete;
{ ~token_carry_buffer() = default;
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;
return *this;
}
~token_text_buffer() = default;
void clear() noexcept void clear() noexcept
{ {
m_end = m_data.get(); m_end = m_data.get();
} }
bool empty() const noexcept void push_back(char c)
{
return m_end == m_data.get();
}
void push_back(CharType c)
{ {
if (JSON_HEDLEY_UNLIKELY(m_end == m_capacity_end)) if (JSON_HEDLEY_UNLIKELY(m_end == m_capacity_end))
{ {
make_room(1); grow(1);
} }
*m_end++ = c; *m_end++ = c;
} }
/// drop the last character void append(const char* first, const char* last)
void pop_back() noexcept
{
JSON_ASSERT(!empty());
--m_end;
}
void append(const CharType* first, const CharType* last)
{ {
const auto n = static_cast<std::size_t>(last - first); const auto n = static_cast<std::size_t>(last - first);
const auto room = static_cast<std::size_t>(m_capacity_end - m_end); const auto room = static_cast<std::size_t>(m_capacity_end - m_end);
if (JSON_HEDLEY_UNLIKELY(n > room)) if (n > room)
{ {
append_long(first, n); grow(n);
return;
} }
if (n != 0) if (n != 0)
{ {
std::memcpy(m_end, first, n * sizeof(CharType)); std::memcpy(m_end, first, n);
} }
m_end += n; m_end += n;
} }
/// replace the contents of @a out /// replace the contents of @a out
void copy_to(std::vector<CharType>& out) const void copy_to(std::vector<char>& out) const
{ {
out.assign(m_data.get(), m_end); out.assign(m_data.get(), m_end);
} }
private: private:
/// append @a n characters that do not fit, in pieces of at most tail() /// make room for @a extra more characters
/// characters, so that dropping the middle keeps the last tail() of them JSON_HEDLEY_NEVER_INLINE void grow(std::size_t extra)
JSON_HEDLEY_NEVER_INLINE void append_long(const CharType* first, std::size_t n)
{ {
while (n != 0) 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)
{ {
const std::size_t piece = n < tail() ? n : tail(); std::memcpy(data.get(), m_data.get(), size);
make_room(piece);
std::memcpy(m_end, first, piece * sizeof(CharType));
m_end += piece;
first += piece;
n -= piece;
} }
m_data = std::move(data);
m_end = m_data.get() + size;
m_capacity_end = m_data.get() + new_capacity;
} }
/// make room for @a extra (at most tail()) more characters: drop the std::unique_ptr<char[]> m_data{}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,readability-redundant-member-init)
/// middle of a long token instead of growing beyond limit() char* m_end = nullptr;
JSON_HEDLEY_NEVER_INLINE void make_room(std::size_t extra) char* m_capacity_end = nullptr;
{
// nothing is allocated before the first character
std::size_t size = m_data ? static_cast<std::size_t>(m_end - m_data.get()) : 0;
if (size + extra > limit() && size > head() + tail())
{
std::memmove(m_data.get() + head(), m_end - tail(), tail() * sizeof(CharType));
m_end = m_data.get() + head() + tail();
size = head() + tail();
}
const std::size_t capacity = m_data ? static_cast<std::size_t>(m_capacity_end - m_data.get()) : 0;
if (size + extra > capacity)
{
// never more than limit(): from there on, the middle is dropped
const std::size_t doubled = capacity < 32 ? static_cast<std::size_t>(32) : 2 * capacity;
const std::size_t new_capacity = (std::max)(doubled < limit() ? doubled : limit(), size + extra);
std::unique_ptr<CharType[]> data(new CharType[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 * sizeof(CharType));
}
m_data = std::move(data);
m_end = m_data.get() + size;
m_capacity_end = m_data.get() + new_capacity;
}
}
std::unique_ptr<CharType[]> m_data{}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,readability-redundant-member-init)
CharType* m_end = nullptr;
CharType* m_capacity_end = nullptr;
}; };
/// the part of a token that an input adapter copied out of its window
using token_carry_buffer = token_text_buffer<char>;
////////////////////
// input adapters //
////////////////////
#ifndef JSON_NO_IO
/*! /*!
Input adapter for stdio file access. The file is locked for the lifetime of the Input adapter for stdio file access. The file is locked for the lifetime of the
adapter and read with the unlocked stdio functions (see adapter and read with the unlocked stdio functions (see
@@ -13975,10 +13898,7 @@ class lexer : public lexer_base<BasicJsonType>
/// streaming adapter: capture the characters /// streaming adapter: capture the characters
void bulk_track_token(const char* data, std::size_t n, std::false_type /*lazy*/) void bulk_track_token(const char* data, std::size_t n, std::false_type /*lazy*/)
{ {
for (std::size_t i = 0; i < n; ++i) token_string.insert(token_string.end(), data, data + n);
{
token_string.push_back(static_cast<char_type>(data[i]));
}
} }
/// streaming input: no bulk fast path /// streaming input: no bulk fast path
@@ -15721,28 +15641,15 @@ scan_number_done:
const std::size_t stop = ia.get_consumed_count() - (pending_real_unget ? 1u : 0u); const std::size_t stop = ia.get_consumed_count() - (pending_real_unget ? 1u : 0u);
if (JSON_HEDLEY_LIKELY(stop >= token_string_start)) if (JSON_HEDLEY_LIKELY(stop >= token_string_start))
{ {
// only the beginning and the end of a long token are shown (see ia.copy_consumed_range(token_string_start, stop, out);
// get_token_string), so copy only these
constexpr std::size_t head = token_text_buffer<char_type>::head();
constexpr std::size_t tail = token_text_buffer<char_type>::tail();
if (stop - token_string_start > head + tail)
{
ia.copy_consumed_range(token_string_start, token_string_start + head, out);
ia.copy_consumed_range(stop - tail, stop, out);
}
else
{
ia.copy_consumed_range(token_string_start, stop, out);
}
} }
return out; return out;
} }
/// streaming adapter: the token was copied eagerly while scanning /// streaming adapter: the token was copied eagerly while scanning
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::false_type /*lazy*/) const const std::vector<char_type>& collect_token_chars(std::vector<char_type>& /*out*/, std::false_type /*lazy*/) const
{ {
token_string.copy_to(out); return token_string;
return out;
} }
/// return the last read token (for errors only). Will never contain EOF /// return the last read token (for errors only). Will never contain EOF
@@ -15753,32 +15660,10 @@ scan_number_done:
std::vector<char_type> reconstructed; std::vector<char_type> reconstructed;
const std::vector<char_type>& chars = collect_token_chars(reconstructed, std::integral_constant<bool, lazy_token_string> {}); const std::vector<char_type>& chars = collect_token_chars(reconstructed, std::integral_constant<bool, lazy_token_string> {});
// a long token is shown by its first and its last head() characters; // escape control characters
// the buffers that keep it while it is read keep no more than these
// and a few more (see token_text_buffer)
constexpr std::size_t shown = token_text_buffer<char_type>::head();
std::string result; std::string result;
if (chars.size() > 2 * shown) for (const auto c : chars)
{ {
append_escaped(result, chars.begin(), chars.begin() + static_cast<std::ptrdiff_t>(shown));
result += "...";
append_escaped(result, chars.end() - static_cast<std::ptrdiff_t>(shown), chars.end());
}
else
{
append_escaped(result, chars.begin(), chars.end());
}
return result;
}
private:
/// append the characters [first, last) to @a result, escaping control characters
template<typename Iterator>
static void append_escaped(std::string& result, Iterator first, Iterator last)
{
for (; first != last; ++first)
{
const auto c = *first;
if (static_cast<unsigned char>(c) <= '\x1F') if (static_cast<unsigned char>(c) <= '\x1F')
{ {
// escape control characters // escape control characters
@@ -15792,9 +15677,9 @@ scan_number_done:
result.push_back(static_cast<std::string::value_type>(c)); result.push_back(static_cast<std::string::value_type>(c));
} }
} }
}
public: return result;
}
/// return syntax error message /// return syntax error message
JSON_HEDLEY_RETURNS_NON_NULL JSON_HEDLEY_RETURNS_NON_NULL
@@ -16044,7 +15929,7 @@ scan_number_done:
/// raw input token string for error messages; only populated for streaming /// raw input token string for error messages; only populated for streaming
/// adapters (seekable adapters reconstruct it lazily via token_string_start) /// adapters (seekable adapters reconstruct it lazily via token_string_start)
token_text_buffer<char_type> token_string {}; std::vector<char_type> token_string {};
/// start offset of the current token within the input, used to reconstruct /// start offset of the current token within the input, used to reconstruct
/// the last read token on error for seekable adapters (see collect_token_chars) /// the last read token on error for seekable adapters (see collect_token_chars)
+3 -9
View File
@@ -1,11 +1,6 @@
cmake_minimum_required(VERSION 3.30) # `import std;` (used by main.cpp) is supported without experimental opt-in
# since CMake 4.5: it is available to every target that scans for C++ modules.
# `import std;` (used by main.cpp) needs CMake's experimental C++ import-std cmake_minimum_required(VERSION 4.5)
# support. The accepted token is CMake-version-specific; this value matches the
# CMake version pinned in the ci_module_cpp20 CI jobs (4.3.x). If that pin is
# bumped, this token must be updated to match (configuration fails loudly
# otherwise).
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "451f2fe2-a8a2-47c3-bc32-94786d8fc91b")
project(json_test CXX) project(json_test CXX)
@@ -13,7 +8,6 @@ project(json_test CXX)
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_MODULE_STD ON)
set(NLOHMANN_JSON_BUILD_MODULES ON CACHE BOOL "Enable nlohmann.json module support") set(NLOHMANN_JSON_BUILD_MODULES ON CACHE BOOL "Enable nlohmann.json module support")
+2 -2
View File
@@ -17,8 +17,8 @@ using namespace nlohmann::literals;
// Standard-library facilities are pulled in via `import std;` rather than // Standard-library facilities are pulled in via `import std;` rather than
// textual `#include`s: mixing `import` with textual standard headers does not // textual `#include`s: mixing `import` with textual standard headers does not
// compile under GCC's C++20 modules implementation, whereas `import std;` works // compile under GCC's C++20 modules implementation, whereas `import std;` works
// across GCC, Clang, and MSVC. This requires C++23 and CMake's (experimental) // across GCC, Clang, and MSVC. This requires C++23 and CMake 4.5 or newer;
// import-std support; see CMakeLists.txt. // see CMakeLists.txt.
int main() int main()
{ {
// basic_json / json: parsing and value access // basic_json / json: parsing and value access
-121
View File
@@ -12,7 +12,6 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
using nlohmann::json; using nlohmann::json;
#include <algorithm> // min
#include <array> // array #include <array> // array
#include <cctype> // toupper #include <cctype> // toupper
#include <cstdint> // uint32_t, uint64_t #include <cstdint> // uint32_t, uint64_t
@@ -2234,123 +2233,3 @@ TEST_CASE("serialization of short strings")
CHECK(json("caf\xC3\xA9").dump() == "\"caf\xC3\xA9\""); CHECK(json("caf\xC3\xA9").dump() == "\"caf\xC3\xA9\"");
CHECK(json("caf\xC3\xA9").dump(-1, ' ', true) == "\"caf\\u00e9\""); CHECK(json("caf\xC3\xA9").dump(-1, ' ', true) == "\"caf\\u00e9\"");
} }
TEST_CASE("token_text_buffer keeps the beginning and the end of a long token")
{
using buffer_t = nlohmann::detail::token_text_buffer<char>;
constexpr std::size_t head = buffer_t::head();
constexpr std::size_t tail = buffer_t::tail();
// the characters 0, 1, ..., 250, 0, 1, ...
const auto at = [](std::size_t i)
{
return static_cast<char>(i % 251);
};
// whether out holds the first head and the last tail characters of a
// token of n characters (and maybe more characters of its middle)
const auto keeps_ends = [&](const std::vector<char>& out, std::size_t n)
{
if (out.size() < head + tail || out.size() > buffer_t::limit())
{
return false;
}
for (std::size_t i = 0; i < head; ++i)
{
if (out[i] != at(i))
{
return false;
}
}
for (std::size_t i = 0; i < tail; ++i)
{
if (out[out.size() - tail + i] != at(n - tail + i))
{
return false;
}
}
return true;
};
SECTION("a short token is kept as it is")
{
buffer_t buffer;
CHECK(buffer.empty());
for (std::size_t i = 0; i < head + tail; ++i)
{
buffer.push_back(at(i));
}
std::vector<char> out;
buffer.copy_to(out);
REQUIRE(out.size() == head + tail);
bool same = true;
for (std::size_t i = 0; i < out.size(); ++i)
{
same = same && out[i] == at(i);
}
CHECK(same);
}
SECTION("characters added one by one")
{
for (const std::size_t n :
{
head + tail + 1, buffer_t::limit(), buffer_t::limit() + 1, static_cast<std::size_t>(100000)
})
{
CAPTURE(n)
buffer_t buffer;
for (std::size_t i = 0; i < n; ++i)
{
buffer.push_back(at(i));
}
std::vector<char> out;
buffer.copy_to(out);
CHECK(keeps_ends(out, n));
// the last character can be handed back
buffer.pop_back();
buffer.copy_to(out);
CHECK(keeps_ends(out, n - 1));
}
}
SECTION("characters added in pieces of different sizes")
{
std::vector<char> input(200000);
for (std::size_t i = 0; i < input.size(); ++i)
{
input[i] = at(i);
}
for (const std::size_t piece :
{
static_cast<std::size_t>(1), static_cast<std::size_t>(100), tail - 1, tail, tail + 1, static_cast<std::size_t>(65536), input.size()
})
{
CAPTURE(piece)
buffer_t buffer;
for (std::size_t pos = 0; pos < input.size(); pos += piece)
{
const std::size_t n = (std::min)(piece, input.size() - pos);
buffer.append(input.data() + pos, input.data() + pos + n);
}
std::vector<char> out;
buffer.copy_to(out);
CHECK(keeps_ends(out, input.size()));
}
}
SECTION("clear starts a new token")
{
buffer_t buffer;
for (std::size_t i = 0; i < 100000; ++i)
{
buffer.push_back('x');
}
buffer.clear();
CHECK(buffer.empty());
buffer.push_back('a');
std::vector<char> out;
buffer.copy_to(out);
CHECK(out == std::vector<char> {'a'});
}
}
-113
View File
@@ -3269,116 +3269,3 @@ TEST_CASE("a plain function is accepted as parser callback for containers")
CHECK((nlohmann::detail::can_compare_ne<std::string::const_iterator, std::string::const_iterator>::value)); CHECK((nlohmann::detail::can_compare_ne<std::string::const_iterator, std::string::const_iterator>::value));
CHECK((nlohmann::detail::can_compare_ne<const char*, const char*>::value)); CHECK((nlohmann::detail::can_compare_ne<const char*, const char*>::value));
} }
#if !defined(JSON_NOEXCEPTION)
TEST_CASE("parse error message of a long token")
{
// the message of a parse error shows the last read token; a long token is
// shown by its first and its last 4096 characters, and the message is the
// same for every kind of input
const auto message = [](const std::string & text, int kind) -> std::string
{
json _;
try
{
if (kind == 0)
{
_ = json::parse(text);
}
else if (kind == 1)
{
std::istringstream stream(text);
_ = json::parse(stream);
}
else if (kind == 2)
{
FILE* const file = std::tmpfile(); // NOLINT(cppcoreguidelines-owning-memory)
REQUIRE(file != nullptr);
CHECK(std::fwrite(text.data(), 1, text.size(), file) == text.size());
CHECK(std::fseek(file, 0, SEEK_SET) == 0);
try
{
_ = json::parse(file);
}
catch (...)
{
std::fclose(file); // NOLINT(cert-err33-c,cppcoreguidelines-owning-memory)
throw;
}
std::fclose(file); // NOLINT(cert-err33-c,cppcoreguidelines-owning-memory)
}
else
{
const std::list<char> chars(text.begin(), text.end());
_ = json::parse(chars.begin(), chars.end());
}
}
catch (const json::parse_error& e)
{
return e.what();
}
return "no error";
};
SECTION("a short token is shown completely")
{
const std::string text = "[\"" + std::string(8000, 'a') + "\x01\"]";
const std::string expected = "last read: '\"" + std::string(8000, 'a') + "<U+0001>'";
for (int kind = 0; kind < 4; ++kind)
{
CAPTURE(kind)
const std::string m = message(text, kind);
CHECK(m.size() > expected.size());
CHECK(m.compare(m.size() - expected.size(), expected.size(), expected) == 0);
}
}
SECTION("a long string")
{
const std::string text = "[\"" + std::string(100000, 'a') + "b\x01\"]";
// the token is '"', 100,000 times 'a', 'b', and the control character
const std::string expected = "last read: '\"" + std::string(4095, 'a') + "..." + std::string(4094, 'a') + "b<U+0001>'";
for (int kind = 0; kind < 4; ++kind)
{
CAPTURE(kind)
const std::string m = message(text, kind);
CHECK(m.size() > expected.size());
CHECK(m.compare(m.size() - expected.size(), expected.size(), expected) == 0);
}
}
SECTION("a long run of whitespace after a number")
{
// the token begins with the number and ends with the invalid character
const std::string text = "[1," + std::string(100000, '\n') + "?]";
std::string expected = "last read: '1,";
for (int i = 0; i < 4094; ++i)
{
expected += "<U+000A>";
}
expected += "...";
for (int i = 0; i < 4095; ++i)
{
expected += "<U+000A>";
}
expected += "?'";
for (int kind = 0; kind < 4; ++kind)
{
CAPTURE(kind)
const std::string m = message(text, kind);
CHECK(m.size() > expected.size());
CHECK(m.compare(m.size() - expected.size(), expected.size(), expected) == 0);
}
}
SECTION("valid input with a long run of whitespace")
{
const std::string text = "1" + std::string(100000, ' ');
for (int kind = 0; kind < 4; ++kind)
{
CAPTURE(kind)
CHECK(message(text, kind) == "no error");
}
}
}
#endif