mirror of
https://github.com/nlohmann/json.git
synced 2026-10-24 14:58:54 +00:00
Compare commits
80
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abd3c275d1 | ||
|
|
3267a6239d | ||
|
|
89b762aa35 | ||
|
|
9c50bdd140 | ||
|
|
dd7686a269 | ||
|
|
e9c4fe3cdf | ||
|
|
1e9222f955 | ||
|
|
469e29334b | ||
|
|
cf0c72cd95 | ||
|
|
6ee6351361 | ||
|
|
4f332cbb9e | ||
|
|
80c5fe8a5a | ||
|
|
f8df3df021 | ||
|
|
21bc758b89 | ||
|
|
0e5a906cd5 | ||
|
|
fc347b5779 | ||
|
|
fce5a54722 | ||
|
|
96b5c82496 | ||
|
|
bf27f0ba99 | ||
|
|
7506a1b0bf | ||
|
|
ae59fbb5a4 | ||
|
|
bd7878f899 | ||
|
|
0092111f4d | ||
|
|
c533a107a1 | ||
|
|
d556800512 | ||
|
|
0721d3c389 | ||
|
|
daf104b549 | ||
|
|
643acf23e2 | ||
|
|
df98274f6c | ||
|
|
f503b67ac7 | ||
|
|
e8ab313dde | ||
|
|
727911bc80 | ||
|
|
210a398ce8 | ||
|
|
f266ce92e5 | ||
|
|
92e2e84722 | ||
|
|
0c804e0ad7 | ||
|
|
99aee52002 | ||
|
|
5275930814 | ||
|
|
38147ab676 | ||
|
|
390ebc91b9 | ||
|
|
3a4285ad88 | ||
|
|
8b19b94fd6 | ||
|
|
2e92fe8d26 | ||
|
|
d52cfb7221 | ||
|
|
8262acca0e | ||
|
|
4b3fcd0740 | ||
|
|
35930392d8 | ||
|
|
f8d6aa95eb | ||
|
|
48bd677617 | ||
|
|
379bf8eddb | ||
|
|
c9c9b90f1f | ||
|
|
058d6fd23d | ||
|
|
25e7edad54 | ||
|
|
ffcd392be0 | ||
|
|
e28506dd86 | ||
|
|
03a0595c1e | ||
|
|
4e72af9d54 | ||
|
|
0f5d07310c | ||
|
|
0b924f5375 | ||
|
|
6fa14d8f95 | ||
|
|
dec0e9b749 | ||
|
|
86d5c56c54 | ||
|
|
aa0b371f7c | ||
|
|
ace1030107 | ||
|
|
2dc1e54856 | ||
|
|
9855db81f3 | ||
|
|
533cbfee51 | ||
|
|
9bf531436f | ||
|
|
3e031b06a5 | ||
|
|
7c26e99ac5 | ||
|
|
6aefd8c662 | ||
|
|
52fecc1fab | ||
|
|
fa37b952df | ||
|
|
e9970066c5 | ||
|
|
3f8677d028 | ||
|
|
ef3d350639 | ||
|
|
5e13f834c1 | ||
|
|
e13e976801 | ||
|
|
898e6ee81d | ||
|
|
ef1cdb59ac |
No files matched your search
@@ -18,7 +18,8 @@ environment:
|
|||||||
matrix:
|
matrix:
|
||||||
# The Visual Studio 2017 jobs compile everything with /std:c++17, so they
|
# The Visual Studio 2017 jobs compile everything with /std:c++17, so they
|
||||||
# only build the C++17 variant of each test, split into two jobs each to
|
# only build the C++17 variant of each test, split into two jobs each to
|
||||||
# stay below AppVeyor's 60-minute limit per job.
|
# stay below AppVeyor's 60-minute limit per job. The Visual Studio 2019
|
||||||
|
# job builds the tests for all standards and is split for the same reason.
|
||||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
||||||
configuration: Debug
|
configuration: Debug
|
||||||
platform: x86
|
platform: x86
|
||||||
@@ -50,7 +51,13 @@ environment:
|
|||||||
configuration: Release
|
configuration: Release
|
||||||
platform: x86
|
platform: x86
|
||||||
CXX_FLAGS: "/W4 /WX"
|
CXX_FLAGS: "/W4 /WX"
|
||||||
CMAKE_OPTIONS: "-DJSON_ImplicitConversions=OFF"
|
CMAKE_OPTIONS: "-DJSON_ImplicitConversions=OFF -DJSON_TestShard=0/2"
|
||||||
|
GENERATOR: Visual Studio 16 2019
|
||||||
|
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||||
|
configuration: Release
|
||||||
|
platform: x86
|
||||||
|
CXX_FLAGS: "/W4 /WX"
|
||||||
|
CMAKE_OPTIONS: "-DJSON_ImplicitConversions=OFF -DJSON_TestShard=1/2"
|
||||||
GENERATOR: Visual Studio 16 2019
|
GENERATOR: Visual Studio 16 2019
|
||||||
|
|
||||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ labels:
|
|||||||
- "tests/src/unit-json_view.*"
|
- "tests/src/unit-json_view.*"
|
||||||
- "tests/src/json_view_test_helpers\\.hpp"
|
- "tests/src/json_view_test_helpers\\.hpp"
|
||||||
- "tests/src/fuzzer-(parse_json_view|json_view_image)\\.cpp"
|
- "tests/src/fuzzer-(parse_json_view|json_view_image)\\.cpp"
|
||||||
|
- "tests/benchmarks/json_view/.*"
|
||||||
- "tools/amalgamate/config_json_view\\.json"
|
- "tools/amalgamate/config_json_view\\.json"
|
||||||
- "docs/mkdocs/docs/features/json_view\\.md"
|
- "docs/mkdocs/docs/features/json_view\\.md"
|
||||||
- "docs/mkdocs/docs/api/basic_json_(document|view)/.*"
|
- "docs/mkdocs/docs/api/basic_json_(document|view)/.*"
|
||||||
|
|||||||
@@ -154,6 +154,28 @@ jobs:
|
|||||||
# PR content in a job without credentials.
|
# PR content in a job without credentials.
|
||||||
python3 tools/generate_natvis/generate_natvis.py $MAIN_DIR
|
python3 tools/generate_natvis/generate_natvis.py $MAIN_DIR
|
||||||
|
|
||||||
|
- name: Check the nlohmann_json.natvis generator options
|
||||||
|
run: |
|
||||||
|
cd $MAIN_DIR
|
||||||
|
# The options for configurations outside the checked-in file write
|
||||||
|
# to a temporary file, so they do not affect the diff below.
|
||||||
|
GENERATOR=tools/generate_natvis/generate_natvis.py
|
||||||
|
python3 $GENERATOR --list-tags
|
||||||
|
TAG_COUNT=$(python3 $GENERATOR --list-tags | wc -l)
|
||||||
|
|
||||||
|
python3 $GENERATOR --abi-tags diag,_dp,_vns --abi-tags _diag --output $RUNNER_TEMP/abi_tags.natvis
|
||||||
|
grep -q '<!-- Namespace nlohmann::json_abi_diag_dp_vns -->' $RUNNER_TEMP/abi_tags.natvis
|
||||||
|
grep -q '<!-- Namespace nlohmann::json_abi_diag_dp_vns_v[0-9_]* -->' $RUNNER_TEMP/abi_tags.natvis
|
||||||
|
test $(grep -c '<!-- Namespace ' $RUNNER_TEMP/abi_tags.natvis) -eq $(( $(grep -c '<!-- Namespace ' nlohmann_json.natvis) + 2 ))
|
||||||
|
|
||||||
|
python3 $GENERATOR --all --output $RUNNER_TEMP/all.natvis
|
||||||
|
test $(grep -c '<!-- Namespace ' $RUNNER_TEMP/all.natvis) -eq $(( (1 << TAG_COUNT) * 2 + 1 ))
|
||||||
|
|
||||||
|
if python3 $GENERATOR --abi-tags _unknown --output $RUNNER_TEMP/unknown.natvis; then
|
||||||
|
echo "generate_natvis.py accepted an unknown ABI tag"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Build patch and check for differences
|
- name: Build patch and check for differences
|
||||||
id: diff
|
id: diff
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -38,14 +38,14 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||||
with:
|
with:
|
||||||
languages: c-cpp
|
languages: c-cpp
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
uses: github/codeql-action/autobuild@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||||
@@ -37,6 +37,14 @@ jobs:
|
|||||||
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||||
return artifact.name == "pr"
|
return artifact.name == "pr"
|
||||||
})[0];
|
})[0];
|
||||||
|
// a run that failed before its jobs started (e.g. a stale queued
|
||||||
|
// run cancelled long after its push) has no artifact to report on
|
||||||
|
if (!matchArtifact) {
|
||||||
|
core.notice('Run ${{ github.event.workflow_run.id }} has no "pr" artifact; nothing to comment on.');
|
||||||
|
core.setOutput('found', 'false');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
core.setOutput('found', 'true');
|
||||||
var download = await github.rest.actions.downloadArtifact({
|
var download = await github.rest.actions.downloadArtifact({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
@@ -51,9 +59,11 @@ jobs:
|
|||||||
# Extract the untrusted PR artifact into a dedicated empty directory and
|
# Extract the untrusted PR artifact into a dedicated empty directory and
|
||||||
# read only the two expected files by fixed path afterwards. This avoids a
|
# read only the two expected files by fixed path afterwards. This avoids a
|
||||||
# malicious archive overwriting workspace files or escaping via ../ paths.
|
# malicious archive overwriting workspace files or escaping via ../ paths.
|
||||||
- run: unzip -o pr.zip -d ./pr_artifact
|
- if: steps.download.outputs.found == 'true'
|
||||||
|
run: unzip -o pr.zip -d ./pr_artifact
|
||||||
|
|
||||||
- name: 'Comment on PR'
|
- name: 'Comment on PR'
|
||||||
|
if: steps.download.outputs.found == 'true'
|
||||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -47,6 +47,6 @@ jobs:
|
|||||||
output: 'flawfinder_results.sarif'
|
output: 'flawfinder_results.sarif'
|
||||||
|
|
||||||
- name: Upload analysis results to GitHub Security tab
|
- name: Upload analysis results to GitHub Security tab
|
||||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||||
with:
|
with:
|
||||||
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
name: "json_view benchmarks"
|
||||||
|
|
||||||
|
# On demand only: runs the comparison of json_view with yyjson, simdjson, and
|
||||||
|
# Boost.JSON (tests/benchmarks/json_view/compare.py) on GitHub-hosted runners,
|
||||||
|
# for numbers from x86-64 and AArch64 Linux. It runs when started by hand, or
|
||||||
|
# when a pull request gets the label "benchmark" (on both architectures, with
|
||||||
|
# GCC and the default settings). Shared runners are noisy: the results show
|
||||||
|
# where json_view stands, but published numbers need a quiet machine (see
|
||||||
|
# tests/benchmarks/json_view/README.md).
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [labeled]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
runner:
|
||||||
|
description: "Runner image"
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- ubuntu-24.04
|
||||||
|
- ubuntu-24.04-arm
|
||||||
|
default: ubuntu-24.04
|
||||||
|
compiler:
|
||||||
|
description: "Compiler"
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- g++
|
||||||
|
- clang++
|
||||||
|
default: g++
|
||||||
|
native:
|
||||||
|
description: "Compile for the runner's CPU (-march=native)"
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
rounds:
|
||||||
|
description: "Rounds of bench_view"
|
||||||
|
type: number
|
||||||
|
default: 30
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
# (the label is part of the group: adding an unrelated label must not cancel a running benchmark)
|
||||||
|
group: json-view-benchmarks-${{ github.workflow }}-${{ github.ref }}-${{ github.event.label.name }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
compare:
|
||||||
|
if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'benchmark'
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
runner: ${{ fromJSON(github.event_name == 'workflow_dispatch' && format('["{0}"]', inputs.runner) || '["ubuntu-24.04", "ubuntu-24.04-arm"]') }}
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
timeout-minutes: 60
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Download test data
|
||||||
|
run: |
|
||||||
|
cmake -S . -B build -DJSON_BuildTests=On
|
||||||
|
cmake --build build --target download_test_data
|
||||||
|
|
||||||
|
- name: Run the comparison
|
||||||
|
env:
|
||||||
|
CXX: ${{ inputs.compiler || 'g++' }}
|
||||||
|
CC: ${{ inputs.compiler == 'clang++' && 'clang' || 'gcc' }}
|
||||||
|
ROUNDS: ${{ inputs.rounds || 30 }}
|
||||||
|
NATIVE: ${{ inputs.native && '--native' || '' }}
|
||||||
|
run: python3 tests/benchmarks/json_view/compare.py --data build/test_files --download --rounds "$ROUNDS" $NATIVE
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
run: cat tests/benchmarks/json_view/results/*.md >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
|
with:
|
||||||
|
name: json_view-benchmarks-${{ matrix.runner }}-${{ inputs.compiler || 'g++' }}
|
||||||
|
path: tests/benchmarks/json_view/results/
|
||||||
@@ -80,6 +80,6 @@ jobs:
|
|||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard.
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
- name: "Upload to code-scanning"
|
- name: "Upload to code-scanning"
|
||||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
@@ -65,7 +65,7 @@ jobs:
|
|||||||
|
|
||||||
# Upload SARIF file generated in previous step
|
# Upload SARIF file generated in previous step
|
||||||
- name: Upload SARIF file
|
- name: Upload SARIF file
|
||||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||||
with:
|
with:
|
||||||
sarif_file: semgrep.sarif
|
sarif_file: semgrep.sarif
|
||||||
if: always()
|
if: always()
|
||||||
@@ -37,6 +37,7 @@ cc_library(
|
|||||||
"include/nlohmann/detail/input/parser.hpp",
|
"include/nlohmann/detail/input/parser.hpp",
|
||||||
"include/nlohmann/detail/input/position_t.hpp",
|
"include/nlohmann/detail/input/position_t.hpp",
|
||||||
"include/nlohmann/detail/input/pow5_table.hpp",
|
"include/nlohmann/detail/input/pow5_table.hpp",
|
||||||
|
"include/nlohmann/detail/input/simd_scan.hpp",
|
||||||
"include/nlohmann/detail/input/string_scan.hpp",
|
"include/nlohmann/detail/input/string_scan.hpp",
|
||||||
"include/nlohmann/detail/iterators/internal_iterator.hpp",
|
"include/nlohmann/detail/iterators/internal_iterator.hpp",
|
||||||
"include/nlohmann/detail/iterators/iter_impl.hpp",
|
"include/nlohmann/detail/iterators/iter_impl.hpp",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ This file describes the source for supporting files; that is, files that are not
|
|||||||
- [GitHub](#github)
|
- [GitHub](#github)
|
||||||
- [REUSE](#reuse)
|
- [REUSE](#reuse)
|
||||||
- [Package Managers](#package-managers)
|
- [Package Managers](#package-managers)
|
||||||
|
- [Debugging](#debugging)
|
||||||
|
|
||||||
## Continuous Integration
|
## Continuous Integration
|
||||||
|
|
||||||
@@ -289,3 +290,29 @@ The module definition for [Bazel](https://bazel.build)'s [Bzlmod](https://bazel.
|
|||||||
Further documentation:
|
Further documentation:
|
||||||
|
|
||||||
- [Bazel modules](https://bazel.build/external/module)
|
- [Bazel modules](https://bazel.build/external/module)
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
### `nlohmann_json.natvis`
|
||||||
|
|
||||||
|
The [Natvis](https://learn.microsoft.com/en-us/visualstudio/debugger/create-custom-views-of-native-objects) file that
|
||||||
|
gives `json` values a friendly view in the Visual Studio debugger. The file is generated by
|
||||||
|
`tools/generate_natvis/generate_natvis.py` from the template `tools/generate_natvis/nlohmann_json.natvis.j2` and the ABI
|
||||||
|
tags and version in `include/nlohmann/detail/abi_macros.hpp`; change the script or the template rather than editing
|
||||||
|
the file by hand. The file can be updated by calling
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make natvis
|
||||||
|
```
|
||||||
|
|
||||||
|
The "Check amalgamation" workflow fails if the file is out of date.
|
||||||
|
|
||||||
|
The file covers the namespace variants with up to two ABI tags. A Natvis file for other configurations can be
|
||||||
|
generated with the `--abi-tags` or `--all` options, for example
|
||||||
|
`make natvis NATVIS_ARGS="--abi-tags _diag,_dp,_vns --output my_nlohmann_json.natvis"`; see
|
||||||
|
[`tools/generate_natvis`](tools/generate_natvis/README.md). With MSVC, CMake adds the file to the `nlohmann_json`
|
||||||
|
target and installs it.
|
||||||
|
|
||||||
|
Further documentation:
|
||||||
|
|
||||||
|
- [Create custom views of C++ objects in the debugger using the Natvis framework](https://learn.microsoft.com/en-us/visualstudio/debugger/create-custom-views-of-native-objects)
|
||||||
@@ -46,7 +46,7 @@ all:
|
|||||||
@echo "clean - remove built files"
|
@echo "clean - remove built files"
|
||||||
@echo "(fuzzers) - see tests/fuzzing.md, or run 'make -C tests fuzzers' to build the AFL fuzzers"
|
@echo "(fuzzers) - see tests/fuzzing.md, or run 'make -C tests fuzzers' to build the AFL fuzzers"
|
||||||
@echo "macro_builder_check - check that macro_scope.hpp matches tools/macro_builder's output"
|
@echo "macro_builder_check - check that macro_scope.hpp matches tools/macro_builder's output"
|
||||||
@echo "natvis - regenerate nlohmann_json.natvis from the current ABI tags and version"
|
@echo "natvis - regenerate nlohmann_json.natvis from the current ABI tags and version (pass generator options via NATVIS_ARGS, see tools/generate_natvis/README.md)"
|
||||||
@echo "pretty - beautify code with Artistic Style"
|
@echo "pretty - beautify code with Artistic Style"
|
||||||
@echo "pvs_studio - run the PVS-Studio static analyzer and open the report"
|
@echo "pvs_studio - run the PVS-Studio static analyzer and open the report"
|
||||||
@echo "release - create the release files (include.zip, json.tar.xz, single headers, signatures, hashes)"
|
@echo "release - create the release files (include.zip, json.tar.xz, single headers, signatures, hashes)"
|
||||||
@@ -120,13 +120,15 @@ $(AMALGAMATED_VIEW_FILE): $(SRCS)
|
|||||||
tools/amalgamate/amalgamate.py -c tools/amalgamate/config_json_view.json -s . --verbose=yes
|
tools/amalgamate/amalgamate.py -c tools/amalgamate/config_json_view.json -s . --verbose=yes
|
||||||
|
|
||||||
NATVIS_VENV=tools/generate_natvis/venv
|
NATVIS_VENV=tools/generate_natvis/venv
|
||||||
|
# extra options for generate_natvis.py, e.g. NATVIS_ARGS="--abi-tags _diag,_dp,_vns --output my.natvis"
|
||||||
|
NATVIS_ARGS=
|
||||||
|
|
||||||
# regenerate nlohmann_json.natvis from the ABI tags and version in include/nlohmann/detail/abi_macros.hpp
|
# regenerate nlohmann_json.natvis from the ABI tags and version in include/nlohmann/detail/abi_macros.hpp
|
||||||
# (the dependencies are installed into a virtual environment first)
|
# (the dependencies are installed into a virtual environment first)
|
||||||
natvis:
|
natvis:
|
||||||
@test -d $(NATVIS_VENV) || python3 -mvenv $(NATVIS_VENV)
|
@test -d $(NATVIS_VENV) || python3 -mvenv $(NATVIS_VENV)
|
||||||
@$(NATVIS_VENV)/bin/python3 -c 'import jinja2' 2>/dev/null || $(NATVIS_VENV)/bin/pip3 install --quiet -r tools/generate_natvis/requirements.txt
|
@$(NATVIS_VENV)/bin/python3 -c 'import jinja2' 2>/dev/null || $(NATVIS_VENV)/bin/pip3 install --quiet -r tools/generate_natvis/requirements.txt
|
||||||
$(NATVIS_VENV)/bin/python3 tools/generate_natvis/generate_natvis.py .
|
$(NATVIS_VENV)/bin/python3 tools/generate_natvis/generate_natvis.py $(NATVIS_ARGS) .
|
||||||
|
|
||||||
# regenerate the two tools/macro_builder blocks of $(MACRO_SCOPE_HPP) (see its README.md) and diff against the
|
# regenerate the two tools/macro_builder blocks of $(MACRO_SCOPE_HPP) (see its README.md) and diff against the
|
||||||
# checked-in header; phony, because it never writes $(MACRO_SCOPE_HPP) itself
|
# checked-in header; phony, because it never writes $(MACRO_SCOPE_HPP) itself
|
||||||
|
|||||||
@@ -341,13 +341,14 @@ def download(url, docs) -> str:
|
|||||||
raise ValueError(f'not an http(s) URL: {url}')
|
raise ValueError(f'not an http(s) URL: {url}')
|
||||||
req = urllib.request.Request(u.geturl(), headers={'User-Agent': USER_AGENT})
|
req = urllib.request.Request(u.geturl(), headers={'User-Agent': USER_AGENT})
|
||||||
# (the scheme is checked above)
|
# (the scheme is checked above)
|
||||||
|
# nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected
|
||||||
with urllib.request.urlopen(req, timeout=20) as r: # nosec B310
|
with urllib.request.urlopen(req, timeout=20) as r: # nosec B310
|
||||||
data = r.read()
|
data = r.read()
|
||||||
ctype = r.headers.get_content_type()
|
ctype = r.headers.get_content_type()
|
||||||
path = urllib.parse.unquote(u.path).lstrip('/')
|
path = urllib.parse.unquote(u.path).lstrip('/')
|
||||||
ext = os.path.splitext(path)[1]
|
ext = os.path.splitext(path)[1]
|
||||||
if u.query or not ext or path.endswith('/'):
|
if u.query or not ext or path.endswith('/'):
|
||||||
digest = hashlib.sha1(url.encode(), usedforsecurity=False).hexdigest()[:12]
|
digest = hashlib.sha256(url.encode()).hexdigest()[:12]
|
||||||
path = os.path.join(os.path.dirname(path), digest + CONTENT_TYPE_EXT.get(ctype, ext or '.bin'))
|
path = os.path.join(os.path.dirname(path), digest + CONTENT_TYPE_EXT.get(ctype, ext or '.bin'))
|
||||||
rel = os.path.normpath(os.path.join('assets', 'external', u.hostname, path))
|
rel = os.path.normpath(os.path.join('assets', 'external', u.hostname, path))
|
||||||
out = os.path.join(docs, rel)
|
out = os.path.join(docs, rel)
|
||||||
|
|||||||
@@ -80,7 +80,8 @@ 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).
|
index (unlike `BasicJsonType`'s array, which is random-access). Constant if no element of the array is an array
|
||||||
|
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.).
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ 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.
|
index. Constant if no element of the array is an array or object, since the elements are then adjacent
|
||||||
|
in the index.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ 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).
|
index (unlike `BasicJsonType`'s array, which is random-access). Constant if no element of the array is an array
|
||||||
|
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.).
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ header. See also the [macro overview page](../../features/macros.md).
|
|||||||
- [**JSON_HAS_THREE_WAY_COMPARISON**](json_has_three_way_comparison.md) - control 3-way comparison support
|
- [**JSON_HAS_THREE_WAY_COMPARISON**](json_has_three_way_comparison.md) - control 3-way comparison support
|
||||||
- [**JSON_NO_AUTOMATIC_UDLS**](json_no_automatic_udls.md) - do not include the user-defined string literals (UDLs) automatically
|
- [**JSON_NO_AUTOMATIC_UDLS**](json_no_automatic_udls.md) - do not include the user-defined string literals (UDLs) automatically
|
||||||
- [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers
|
- [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers
|
||||||
|
- [**JSON_NO_SIMD**](json_no_simd.md) - use only portable code instead of vector instructions (SSE2, SSSE3, NEON)
|
||||||
- [**JSON_NO_THREAD_LOCAL**](json_no_thread_local.md) - switch off the use of `thread_local` storage
|
- [**JSON_NO_THREAD_LOCAL**](json_no_thread_local.md) - switch off the use of `thread_local` storage
|
||||||
- [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers
|
- [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers
|
||||||
- [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace
|
- [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# JSON_NO_SIMD
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define JSON_NO_SIMD
|
||||||
|
```
|
||||||
|
|
||||||
|
When defined, the library uses only portable C++ code where it would otherwise use vector instructions: when scanning
|
||||||
|
strings during parsing, and when writing floating-point numbers during serialization.
|
||||||
|
|
||||||
|
The macro is an escape hatch, for example for toolchains that cannot compile the vector code, for sanitizers or
|
||||||
|
analyzers that do not understand it, or to compare the speed of both variants. It does not change any result: values,
|
||||||
|
exceptions, and error positions are the same either way. Only the speed differs.
|
||||||
|
|
||||||
|
## Default definition
|
||||||
|
|
||||||
|
By default, `#!cpp JSON_NO_SIMD` is not defined, and vector instructions are used where the target has them.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#undef JSON_NO_SIMD
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
!!! note "Which instructions are used"
|
||||||
|
|
||||||
|
- **x86-64:** SSE2, which every x86-64 CPU has, finds the end of runs of plain string characters 16 bytes at a time
|
||||||
|
and writes the digits of floating-point numbers. The UTF-8 check of non-ASCII text needs SSSE3, which is not part
|
||||||
|
of x86-64: it is compiled for SSSE3 with a function attribute and used when the CPU has SSSE3 (every x86-64 CPU
|
||||||
|
since about 2011), else the portable check. Code compiled for SSSE3 anyway (e.g., with `-mssse3` or `-march=native`)
|
||||||
|
skips this check of the CPU. 32-bit x86 uses SSE2 for strings when the compiler targets it.
|
||||||
|
- **AArch64:** NEON, which every 64-bit Arm CPU has, checks UTF-8 text and long runs of plain string characters 16
|
||||||
|
bytes at a time, and writes the digits of floating-point numbers. With MSVC, only the floating-point digits use
|
||||||
|
NEON.
|
||||||
|
- **Other targets:** portable code only.
|
||||||
|
|
||||||
|
Strings are only scanned this way for contiguous input and for the characters a `#!cpp std::istream` holds in its
|
||||||
|
stream buffer (see [`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md)); other inputs are read one
|
||||||
|
character at a time.
|
||||||
|
|
||||||
|
!!! note "Interaction with `JSON_USE_SIMDUTF`"
|
||||||
|
|
||||||
|
With [`JSON_USE_SIMDUTF`](json_use_simdutf.md), runs of string characters are first validated by simdutf; the code
|
||||||
|
described here is used for what simdutf does not accept, so that errors are reported at the same position.
|
||||||
|
`JSON_NO_SIMD` does not affect simdutf.
|
||||||
|
|
||||||
|
!!! note "ABI compatibility"
|
||||||
|
|
||||||
|
The macro selects between two definitions of the library's internal scanning and formatting functions. Whether it
|
||||||
|
is defined is encoded in the [namespace](../../features/namespace.md) (tag `_ns`), resulting in distinct symbol
|
||||||
|
names. Translation units compiled with and without it can therefore be linked into the same program without One
|
||||||
|
Definition Rule (ODR) violations, but they cannot exchange instances of library types.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
??? example "Example: switching off vector instructions"
|
||||||
|
|
||||||
|
The program parses and serializes the same values with and without the macro:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define JSON_NO_SIMD
|
||||||
|
#include <iostream>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
const json j = json::parse(R"({"name": "日本語 text", "pi": 3.141592653589793})");
|
||||||
|
std::cout << j.dump() << '\n'; // {"name":"日本語 text","pi":3.141592653589793}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [**parse**](../basic_json/parse.md) - deserialize from a compatible input
|
||||||
|
- [**dump**](../basic_json/dump.md) - serialization
|
||||||
|
- [**JSON_USE_SIMDUTF**](json_use_simdutf.md) - use the simdutf library to accelerate UTF-8 validation
|
||||||
|
- [**JSON_DISABLE_STREAM_BULK_SCAN**](json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a time
|
||||||
|
- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance)
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.13.0.
|
||||||
@@ -6,8 +6,9 @@
|
|||||||
|
|
||||||
When defined, the parser validates the UTF-8 content of JSON strings that come from a **contiguous byte input**
|
When defined, the parser validates the UTF-8 content of JSON strings that come from a **contiguous byte input**
|
||||||
(`std::string`, `std::vector<char>`/`<std::uint8_t>`, string literals, `const char*` ranges, …) using the
|
(`std::string`, `std::vector<char>`/`<std::uint8_t>`, string literals, `const char*` ranges, …) using the
|
||||||
[simdutf](https://github.com/simdutf/simdutf) library instead of the built-in scalar validator. On text with many
|
[simdutf](https://github.com/simdutf/simdutf) library instead of the built-in validator (which uses SSSE3 or NEON where
|
||||||
non-ASCII characters (e.g. CJK or emoji) this can validate several times faster.
|
available, see [`JSON_NO_SIMD`](json_no_simd.md)). On text with many non-ASCII characters (e.g. CJK or emoji) this can
|
||||||
|
validate faster, depending on the CPU.
|
||||||
|
|
||||||
This is an **opt-in external dependency**. The library itself remains header-only and its behavior is unchanged: the
|
This is an **opt-in external dependency**. The library itself remains header-only and its behavior is unchanged: the
|
||||||
same input is accepted or rejected either way, and every parse error is reported at the same position with the same
|
same input is accepted or rejected either way, and every parse error is reported at the same position with the same
|
||||||
|
|||||||
@@ -39,6 +39,6 @@ the library.
|
|||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
- Added in version 3.11.0. Changed inline namespace name in version 3.11.2. Added the ABI tag `_dp` in
|
- Added in version 3.11.0. Changed inline namespace name in version 3.11.2. Added the ABI tag `_dp` in
|
||||||
version 3.12.0, and the ABI tags `_bics`, `_psp`, `_snul`, `_sbu8`, `_ekmo`, `_dsbs`, `_dufio`, `_vns`, and
|
version 3.12.0, and the ABI tags `_bics`, `_psp`, `_snul`, `_sbu8`, `_ekmo`, `_dsbs`, `_dufio`, `_ns`, `_vns`,
|
||||||
`_vssse3` in version 3.13.0; see
|
and `_vssse3` in version 3.13.0; see
|
||||||
[`nlohmann` Namespace](../../features/namespace.md#structure).
|
[`nlohmann` Namespace](../../features/namespace.md#structure).
|
||||||
@@ -174,6 +174,13 @@ security reasons (e.g., Intel Software Guard Extensions (SGX)).
|
|||||||
|
|
||||||
See [full documentation of `JSON_NO_IO`](../api/macros/json_no_io.md).
|
See [full documentation of `JSON_NO_IO`](../api/macros/json_no_io.md).
|
||||||
|
|
||||||
|
## `JSON_NO_SIMD`
|
||||||
|
|
||||||
|
When defined, the library uses only portable code instead of vector instructions (SSE2, SSSE3, NEON) for scanning
|
||||||
|
strings and writing floating-point numbers. Results are the same either way. It is not defined by default.
|
||||||
|
|
||||||
|
See [full documentation of `JSON_NO_SIMD`](../api/macros/json_no_simd.md).
|
||||||
|
|
||||||
## `JSON_NO_THREAD_LOCAL`
|
## `JSON_NO_THREAD_LOCAL`
|
||||||
|
|
||||||
When defined, the library does not use `#!cpp thread_local` storage. Copying a value and comparing two values then
|
When defined, the library does not use `#!cpp thread_local` storage. Copying a value and comparing two values then
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ The complete default namespace name is derived as follows:
|
|||||||
`_dsbs`.
|
`_dsbs`.
|
||||||
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md) defined non-zero appends
|
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md) defined non-zero appends
|
||||||
`_dufio`.
|
`_dufio`.
|
||||||
|
- [`JSON_NO_SIMD`](../api/macros/json_no_simd.md) defined appends `_ns`.
|
||||||
- [`JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md) defined appends `_vns`.
|
- [`JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md) defined appends `_vns`.
|
||||||
- [`JSON_VIEW_USE_SSSE3`](../api/macros/json_view_use_ssse3.md) defined appends `_vssse3`.
|
- [`JSON_VIEW_USE_SSSE3`](../api/macros/json_view_use_ssse3.md) defined appends `_vssse3`.
|
||||||
- The inline namespace ends with the suffix `_v` followed by the 3 components of the version number separated by
|
- The inline namespace ends with the suffix `_v` followed by the 3 components of the version number separated by
|
||||||
@@ -114,6 +115,7 @@ follows:
|
|||||||
([`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md)), `_ekmo`
|
([`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md)), `_ekmo`
|
||||||
([`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md)), `_dsbs`
|
([`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md)), `_dsbs`
|
||||||
([`JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md)), `_dufio`
|
([`JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md)), `_dufio`
|
||||||
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md)), `_vns`
|
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md)), `_ns`
|
||||||
|
([`JSON_NO_SIMD`](../api/macros/json_no_simd.md)), `_vns`
|
||||||
([`JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md)), and `_vssse3`
|
([`JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md)), and `_vssse3`
|
||||||
([`JSON_VIEW_USE_SSSE3`](../api/macros/json_view_use_ssse3.md)) in version 3.13.0.
|
([`JSON_VIEW_USE_SSSE3`](../api/macros/json_view_use_ssse3.md)) in version 3.13.0.
|
||||||
@@ -79,6 +79,10 @@ follow the usual `std::streambuf` contract, and
|
|||||||
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../../api/macros/json_disable_unlocked_file_io.md) for programs in which another
|
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../../api/macros/json_disable_unlocked_file_io.md) for programs in which another
|
||||||
thread must use the same `#!cpp FILE` object while it is parsed.
|
thread must use the same `#!cpp FILE` object while it is parsed.
|
||||||
|
|
||||||
|
Where the target has them, strings are scanned with vector instructions: SSE2 on x86-64 (and SSSE3 for UTF-8 text, when
|
||||||
|
the CPU has it) and NEON on AArch64. [`JSON_NO_SIMD`](../../api/macros/json_no_simd.md) selects portable code instead,
|
||||||
|
again without changing any result.
|
||||||
|
|
||||||
## SAX vs. DOM parsing
|
## SAX vs. DOM parsing
|
||||||
|
|
||||||
The library offers two parsing models:
|
The library offers two parsing models:
|
||||||
|
|||||||
@@ -20,32 +20,39 @@ below apply on every supported standard. This adapter exposes two optimizations
|
|||||||
|
|
||||||
- it can reconstruct already-consumed input on demand for error messages, instead of copying every character as it is
|
- it can reconstruct already-consumed input on demand for error messages, instead of copying every character as it is
|
||||||
read, and
|
read, and
|
||||||
- the lexer can scan ordinary string characters directly out of the buffer, several bytes at a time, rather than one
|
- the lexer can scan ordinary string characters, numbers, and indentation directly out of the buffer, several bytes at
|
||||||
character (and one function call) at a time.
|
a time, rather than one character (and one function call) at a time.
|
||||||
|
|
||||||
A `#!cpp std::istream` (including `#!cpp std::ifstream`) or `#!cpp FILE*`, by contrast, is read through
|
A `#!cpp std::istream` (including `#!cpp std::ifstream`) gets the same bulk scanning: `input_stream_adapter` scans the
|
||||||
`input_stream_adapter` or `file_input_adapter`, which read one character (or one block, for binary formats) at a time
|
characters its stream buffer already holds (the *get area*) and asks the stream buffer for more only when they are used
|
||||||
and expose neither optimization -- the lexer falls back to the same byte-at-a-time path it uses for any
|
up. Nothing is read ahead, so the stream is left exactly where it would be after reading one character at a time, and
|
||||||
non-contiguous, general-purpose iterator range. An iterator pair over non-contiguous but random-access storage (e.g.
|
[`operator>>`](../api/operator_gtgt.md) can still read several values from one stream. A stream buffer without a get
|
||||||
`#!cpp std::deque<char>::iterator`) gets the first optimization but not the second, since the byte-scanning fast path
|
area is read one character at a time. A `#!cpp FILE*` is still read one character at a time, but `file_input_adapter`
|
||||||
additionally requires contiguous storage.
|
locks the file once per parse instead of once per character. See
|
||||||
|
[input sources and performance](parsing/index.md#input-sources-and-performance) for the full table. According to the
|
||||||
|
description of [#5828](https://github.com/nlohmann/json/pull/5828), parsing from a stream is within about 10 to 20 % of
|
||||||
|
parsing an equivalent `#!cpp std::string`.
|
||||||
|
|
||||||
Practically: if the JSON text is already in memory, or small enough to read into memory, prefer passing a
|
Inputs that are still read one character at a time: iterator ranges that are not contiguous (e.g.
|
||||||
`#!cpp std::string`, a `#!cpp std::vector<char>`, or a pointer range to `parse` over a `#!cpp std::istream`. For a
|
`#!cpp std::list<char>` or `#!cpp std::istreambuf_iterator<char>`), wide strings, and user-defined adapters. An
|
||||||
file, that means reading it into a string first and then parsing the string, rather than passing a
|
iterator pair over non-contiguous but random-access storage (e.g. `#!cpp std::deque<char>::iterator`) gets the first
|
||||||
`#!cpp std::ifstream` directly to `parse` -- the latter never benefits from either optimization:
|
optimization but not the second, since the byte-scanning fast path requires contiguous storage.
|
||||||
|
|
||||||
|
Practically: a file can be passed directly to `parse`, which also avoids the memory for a copy of the file. Reading it
|
||||||
|
into a string first is no longer necessary for speed:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// gets the contiguous fast paths
|
// fast: the stream buffer's get area is scanned in bulk
|
||||||
std::ifstream f("example.json");
|
std::ifstream f("example.json");
|
||||||
std::string contents((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
json j = json::parse(f);
|
||||||
json j = json::parse(contents);
|
|
||||||
|
|
||||||
// does not: input_stream_adapter has no fast path
|
|
||||||
std::ifstream f2("example.json");
|
|
||||||
json j2 = json::parse(f2);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Two macros switch the stream optimizations off without changing any result:
|
||||||
|
[`JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md) for stream buffers that do not follow
|
||||||
|
the usual `#!cpp std::streambuf` contract, and
|
||||||
|
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md) for programs in which another thread
|
||||||
|
must use the same `#!cpp FILE` object while it is parsed.
|
||||||
|
|
||||||
For contiguous input with many non-ASCII characters, [`JSON_USE_SIMDUTF`](../api/macros/json_use_simdutf.md) can
|
For contiguous input with many non-ASCII characters, [`JSON_USE_SIMDUTF`](../api/macros/json_use_simdutf.md) can
|
||||||
additionally speed up UTF-8 validation by using the [simdutf](https://github.com/simdutf/simdutf) library instead of
|
additionally speed up UTF-8 validation by using the [simdutf](https://github.com/simdutf/simdutf) library instead of
|
||||||
the built-in scalar validator; streaming inputs (files, `#!cpp std::istream`, wide strings, user-defined adapters)
|
the built-in scalar validator; streaming inputs (files, `#!cpp std::istream`, wide strings, user-defined adapters)
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ below are relative to `include/nlohmann`.
|
|||||||
|-----------|----------|
|
|-----------|----------|
|
||||||
| Value type enumeration | [`detail/value_t.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/value_t.hpp) |
|
| Value type enumeration | [`detail/value_t.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/value_t.hpp) |
|
||||||
| Input adapters | [`detail/input/input_adapters.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/input_adapters.hpp) |
|
| Input adapters | [`detail/input/input_adapters.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/input_adapters.hpp) |
|
||||||
| Lexer | [`detail/input/lexer.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/lexer.hpp), [`detail/input/number_parse.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/number_parse.hpp), [`detail/input/string_scan.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/string_scan.hpp) |
|
| Lexer | [`detail/input/lexer.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/lexer.hpp), [`detail/input/number_parse.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/number_parse.hpp), [`detail/input/string_scan.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/string_scan.hpp), [`detail/input/simd_scan.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/simd_scan.hpp) |
|
||||||
| Parser | [`detail/input/parser.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/parser.hpp) |
|
| Parser | [`detail/input/parser.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/parser.hpp) |
|
||||||
| SAX interface and DOM builders | [`detail/input/json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp) |
|
| SAX interface and DOM builders | [`detail/input/json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp) |
|
||||||
| Binary format readers | [`detail/input/binary_reader.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/binary_reader.hpp) |
|
| Binary format readers | [`detail/input/binary_reader.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/binary_reader.hpp) |
|
||||||
@@ -339,6 +339,11 @@ The `serializer` (used by [`dump`](../api/basic_json/dump.md) and [`operator<<`]
|
|||||||
- `output_stream_adapter` writes to a `std::ostream`.
|
- `output_stream_adapter` writes to a `std::ostream`.
|
||||||
- `output_string_adapter` appends to a string.
|
- `output_string_adapter` appends to a string.
|
||||||
|
|
||||||
|
The `binary_writer` does not call an adapter for every byte. The `to_*` functions that return a `std::vector` write
|
||||||
|
into the vector through a sink (`output_vector_sink`) that is not virtual. The functions that take an output adapter
|
||||||
|
write through a sink (`output_adapter_sink`) that collects the output in a buffer of one kibibyte and passes it to the
|
||||||
|
adapter in blocks, so that the adapter is called (and a stream locked) once per block instead of once per value.
|
||||||
|
|
||||||
## Value conversion
|
## Value conversion
|
||||||
|
|
||||||
Values are converted from and to other types with the `JSONSerializer` template parameter. The default,
|
Values are converted from and to other types with the `JSONSerializer` template parameter. The default,
|
||||||
|
|||||||
@@ -10,6 +10,29 @@ at its root, a [Natvis](https://learn.microsoft.com/en-us/visualstudio/debugger/
|
|||||||
file that gives `json`/`ordered_json` values a friendly, key/value debugger view instead of showing raw internal
|
file that gives `json`/`ordered_json` values a friendly, key/value debugger view instead of showing raw internal
|
||||||
fields, when debugging with the MSVC debug engine (`cppvsdbg`) in Visual Studio or VS Code.
|
fields, when debugging with the MSVC debug engine (`cppvsdbg`) in Visual Studio or VS Code.
|
||||||
|
|
||||||
|
The checked-in visualizers cover namespace variants with zero, one, or two ABI tags. Configurations with three or more
|
||||||
|
ABI tags are not covered and may show raw internal fields instead. To generate a Natvis file for such a configuration:
|
||||||
|
|
||||||
|
1. Check out the release tag of the library version you use.
|
||||||
|
2. List the ABI tags and the macros that enable them:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make natvis NATVIS_ARGS="--list-tags"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Generate a Natvis file with the tags of your configuration, for example for `JSON_DIAGNOSTICS`,
|
||||||
|
`JSON_DIAGNOSTIC_POSITIONS`, and `JSON_VIEW_NO_SIMD`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make natvis NATVIS_ARGS="--abi-tags _diag,_dp,_vns --output my_nlohmann_json.natvis"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Use the generated file instead of `nlohmann_json.natvis`.
|
||||||
|
|
||||||
|
`--abi-tags` can be repeated for several configurations, and `--all` generates every combination (about 60 MB for 12
|
||||||
|
ABI tags, doubling with each further tag). See
|
||||||
|
[`tools/generate_natvis`](https://github.com/nlohmann/json/tree/develop/tools/generate_natvis) for all options.
|
||||||
|
|
||||||
Debug engines that wrap LLDB instead of the MSVC debug engine (for example, `codelldb` in VS Code) only have
|
Debug engines that wrap LLDB instead of the MSVC debug engine (for example, `codelldb` in VS Code) only have
|
||||||
partial/experimental Natvis support, and commonly fall back to showing raw internal fields even with the
|
partial/experimental Natvis support, and commonly fall back to showing raw internal fields even with the
|
||||||
`.natvis` file present. Switching to `cppvsdbg` where available, or checking your debug extension's own Natvis
|
`.natvis` file present. Switching to `cppvsdbg` where available, or checking your debug extension's own Natvis
|
||||||
|
|||||||
@@ -383,6 +383,7 @@ nav:
|
|||||||
- 'JSON_NO_AUTOMATIC_UDLS': api/macros/json_no_automatic_udls.md
|
- 'JSON_NO_AUTOMATIC_UDLS': api/macros/json_no_automatic_udls.md
|
||||||
- 'JSON_NOEXCEPTION': api/macros/json_noexception.md
|
- 'JSON_NOEXCEPTION': api/macros/json_noexception.md
|
||||||
- 'JSON_NO_IO': api/macros/json_no_io.md
|
- 'JSON_NO_IO': api/macros/json_no_io.md
|
||||||
|
- 'JSON_NO_SIMD': api/macros/json_no_simd.md
|
||||||
- 'JSON_NO_THREAD_LOCAL': api/macros/json_no_thread_local.md
|
- 'JSON_NO_THREAD_LOCAL': api/macros/json_no_thread_local.md
|
||||||
- 'JSON_PRECISE_STREAM_POSITION': api/macros/json_precise_stream_position.md
|
- 'JSON_PRECISE_STREAM_POSITION': api/macros/json_precise_stream_position.md
|
||||||
- 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md
|
- 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md
|
||||||
|
|||||||
+15
-15
@@ -6,7 +6,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "check-mermaid",
|
"name": "check-mermaid",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jsdom": "30.1.2",
|
"jsdom": "30.1.3",
|
||||||
"mermaid": "11.17.2"
|
"mermaid": "11.17.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -40,9 +40,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@asamuzakjp/dom-selector": {
|
"node_modules/@asamuzakjp/dom-selector": {
|
||||||
"version": "9.2.4",
|
"version": "10.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-9.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-10.0.3.tgz",
|
||||||
"integrity": "sha512-YnVzxDxLaqL0t7Q4wfcgHZjg55Wm6y5EaO7Bhu3q9J7wq/8iw3PhsnWiOQhYekx4VexnbkgWpTT6n+QIV7kczw==",
|
"integrity": "sha512-3+777rEPsOOjoy5/sRmIzHnoRDaLpm3XWY2dmWn/N7f/XwDG0bcpnFaKL3X9FYJUbNwLmpcJC1bUYexJRxsFlw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bidi-js": "^1.1.0",
|
"bidi-js": "^1.1.0",
|
||||||
@@ -1105,9 +1105,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dompurify": {
|
"node_modules/dompurify": {
|
||||||
"version": "3.4.16",
|
"version": "3.4.17",
|
||||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.16.tgz",
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.17.tgz",
|
||||||
"integrity": "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==",
|
"integrity": "sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==",
|
||||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@types/trusted-types": "^2.0.7"
|
"@types/trusted-types": "^2.0.7"
|
||||||
@@ -1202,13 +1202,13 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/jsdom": {
|
"node_modules/jsdom": {
|
||||||
"version": "30.1.2",
|
"version": "30.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.3.tgz",
|
||||||
"integrity": "sha512-0FFE/jE1rppmVfUrJUgxqXjcwolZYIGtAgj1pTussMhNZxIxi7W/PvfWaMNroGi2B36X1IKHbexpZ9DhkoOPiQ==",
|
"integrity": "sha512-hQMUPvRgN7KxsfIytDQji7bimuJeRQa6Qo5+iuiZQk5CafeRxLIA10/cG5YCBFNpD9RO3oqIiN4CO1Y44kxqXg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@asamuzakjp/css-color": "^7.1.3",
|
"@asamuzakjp/css-color": "^7.1.3",
|
||||||
"@asamuzakjp/dom-selector": "^9.2.4",
|
"@asamuzakjp/dom-selector": "^10.0.3",
|
||||||
"@bramus/specificity": "^2.4.2",
|
"@bramus/specificity": "^2.4.2",
|
||||||
"@csstools/css-syntax-patches-for-csstree": "^1.1.15",
|
"@csstools/css-syntax-patches-for-csstree": "^1.1.15",
|
||||||
"@exodus/bytes": "^1.16.0",
|
"@exodus/bytes": "^1.16.0",
|
||||||
@@ -1221,7 +1221,7 @@
|
|||||||
"parse5": "^8.0.1",
|
"parse5": "^8.0.1",
|
||||||
"saxes": "^6.0.0",
|
"saxes": "^6.0.0",
|
||||||
"tough-cookie": "^6.0.2",
|
"tough-cookie": "^6.0.2",
|
||||||
"undici": "^8.11.2",
|
"undici": "^8.12.1",
|
||||||
"w3c-xmlserializer": "^6.0.0",
|
"w3c-xmlserializer": "^6.0.0",
|
||||||
"webidl-conversions": "^8.0.1",
|
"webidl-conversions": "^8.0.1",
|
||||||
"whatwg-mimetype": "^5.0.0",
|
"whatwg-mimetype": "^5.0.0",
|
||||||
@@ -1521,9 +1521,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici": {
|
"node_modules/undici": {
|
||||||
"version": "8.11.2",
|
"version": "8.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-8.12.2.tgz",
|
||||||
"integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==",
|
"integrity": "sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.19.0"
|
"node": ">=22.19.0"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"description": "Validate the Mermaid diagrams of the documentation (see check_mermaid.mjs)",
|
"description": "Validate the Mermaid diagrams of the documentation (see check_mermaid.mjs)",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jsdom": "30.1.2",
|
"jsdom": "30.1.3",
|
||||||
"mermaid": "11.17.2"
|
"mermaid": "11.17.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,6 +122,12 @@
|
|||||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO
|
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_NO_SIMD
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_NO_SIMD _ns
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_NO_SIMD
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef JSON_VIEW_NO_SIMD
|
#ifdef JSON_VIEW_NO_SIMD
|
||||||
#define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD _vns
|
#define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD _vns
|
||||||
#else
|
#else
|
||||||
@@ -139,9 +145,9 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Construct the namespace ABI tags component
|
// Construct the namespace ABI tags component
|
||||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k ## l
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l, m) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k ## l ## m
|
||||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j, k, l) \
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j, k, l, m) \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l)
|
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l, m)
|
||||||
|
|
||||||
#define NLOHMANN_JSON_ABI_TAGS \
|
#define NLOHMANN_JSON_ABI_TAGS \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||||
@@ -155,6 +161,7 @@
|
|||||||
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS, \
|
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS, \
|
||||||
NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN, \
|
NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN, \
|
||||||
NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO, \
|
NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO, \
|
||||||
|
NLOHMANN_JSON_ABI_TAG_NO_SIMD, \
|
||||||
NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD, \
|
NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD, \
|
||||||
NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3)
|
NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3)
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,24 @@
|
|||||||
#include <intrin0.h> // __umulh, _umul128, _BitScanForward64, _BitScanReverse64
|
#include <intrin0.h> // __umulh, _umul128, _BitScanForward64, _BitScanReverse64
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64
|
||||||
|
#endif
|
||||||
|
#if defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || defined(_M_ARM))
|
||||||
|
#include <intrin.h> // _BitScanForward, _BitScanReverse
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <nlohmann/detail/macro_scope.hpp> // JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN
|
#include <nlohmann/detail/macro_scope.hpp> // JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
#if defined(__cpp_lib_bit_cast) || defined(__cpp_lib_bitops) || defined(__cpp_lib_byteswap)
|
||||||
|
#include <bit> // bit_cast, byteswap, countl_zero, countr_zero
|
||||||
|
#endif
|
||||||
|
|
||||||
// Portable bit-level helpers for the number and string scanners. They use
|
// Portable bit-level helpers for the number and string scanners. They use
|
||||||
// compiler builtins or platform-specific intrinsics where available and plain
|
// compiler builtins or platform-specific intrinsics where available, the
|
||||||
// C++ otherwise, so they work regardless of byte order.
|
// C++20 <bit> functions next, and plain C++ otherwise, so they work regardless
|
||||||
|
// of byte order. (The builtins and intrinsics come first: MSVC's
|
||||||
|
// std::countl_zero checks for LZCNT at run time unless AVX2 is enabled.)
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -33,6 +46,16 @@ inline int count_leading_zeros(std::uint64_t x) noexcept
|
|||||||
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
||||||
_BitScanReverse64(&index, x);
|
_BitScanReverse64(&index, x);
|
||||||
return 63 - static_cast<int>(index);
|
return 63 - static_cast<int>(index);
|
||||||
|
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM))
|
||||||
|
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes
|
||||||
|
if (_BitScanReverse(&index, static_cast<unsigned long>(x >> 32u))) // NOLINT(runtime/int)
|
||||||
|
{
|
||||||
|
return 31 - static_cast<int>(index);
|
||||||
|
}
|
||||||
|
_BitScanReverse(&index, static_cast<unsigned long>(x)); // NOLINT(runtime/int)
|
||||||
|
return 63 - static_cast<int>(index);
|
||||||
|
#elif defined(__cpp_lib_bitops)
|
||||||
|
return std::countl_zero(x);
|
||||||
#else
|
#else
|
||||||
int n = 0;
|
int n = 0;
|
||||||
for (int shift = 32; shift != 0; shift >>= 1)
|
for (int shift = 32; shift != 0; shift >>= 1)
|
||||||
@@ -56,6 +79,16 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept
|
|||||||
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
||||||
_BitScanForward64(&index, x);
|
_BitScanForward64(&index, x);
|
||||||
return static_cast<int>(index);
|
return static_cast<int>(index);
|
||||||
|
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM))
|
||||||
|
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes
|
||||||
|
if (_BitScanForward(&index, static_cast<unsigned long>(x))) // NOLINT(runtime/int)
|
||||||
|
{
|
||||||
|
return static_cast<int>(index);
|
||||||
|
}
|
||||||
|
_BitScanForward(&index, static_cast<unsigned long>(x >> 32u)); // NOLINT(runtime/int)
|
||||||
|
return 32 + static_cast<int>(index);
|
||||||
|
#elif defined(__cpp_lib_bitops)
|
||||||
|
return std::countr_zero(x);
|
||||||
#else
|
#else
|
||||||
int n = 0;
|
int n = 0;
|
||||||
for (int shift = 32; shift != 0; shift >>= 1)
|
for (int shift = 32; shift != 0; shift >>= 1)
|
||||||
@@ -70,6 +103,67 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// the object representation of @a from as a @a To (std::bit_cast in C++20)
|
||||||
|
template<typename To, typename From>
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE To bit_cast(const From& from) noexcept
|
||||||
|
{
|
||||||
|
static_assert(sizeof(To) == sizeof(From), "bit_cast needs types of the same size");
|
||||||
|
#if defined(__cpp_lib_bit_cast)
|
||||||
|
return std::bit_cast<To>(from);
|
||||||
|
#else
|
||||||
|
To to {};
|
||||||
|
std::memcpy(&to, &from, sizeof(To));
|
||||||
|
return to;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// x with its bytes in reverse order (std::byteswap in C++23); compilers lower
|
||||||
|
/// it to a single bswap/rev/movbe
|
||||||
|
inline std::uint16_t byteswap(std::uint16_t x) noexcept
|
||||||
|
{
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
return __builtin_bswap16(x);
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
return _byteswap_ushort(x);
|
||||||
|
#elif defined(__cpp_lib_byteswap)
|
||||||
|
return std::byteswap(x);
|
||||||
|
#else
|
||||||
|
return static_cast<std::uint16_t>((x >> 8u) | (x << 8u));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// x with its bytes in reverse order (std::byteswap in C++23)
|
||||||
|
inline std::uint32_t byteswap(std::uint32_t x) noexcept
|
||||||
|
{
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
return __builtin_bswap32(x);
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
return _byteswap_ulong(x);
|
||||||
|
#elif defined(__cpp_lib_byteswap)
|
||||||
|
return std::byteswap(x);
|
||||||
|
#else
|
||||||
|
return ((x & 0x000000FFu) << 24u) | ((x & 0x0000FF00u) << 8u)
|
||||||
|
| ((x & 0x00FF0000u) >> 8u) | ((x & 0xFF000000u) >> 24u);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// x with its bytes in reverse order (std::byteswap in C++23)
|
||||||
|
inline std::uint64_t byteswap(std::uint64_t x) noexcept
|
||||||
|
{
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
return __builtin_bswap64(x);
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
return _byteswap_uint64(x);
|
||||||
|
#elif defined(__cpp_lib_byteswap)
|
||||||
|
return std::byteswap(x);
|
||||||
|
#else
|
||||||
|
x = ((x & 0x00000000FFFFFFFFull) << 32u) | ((x & 0xFFFFFFFF00000000ull) >> 32u);
|
||||||
|
x = ((x & 0x0000FFFF0000FFFFull) << 16u) | ((x & 0xFFFF0000FFFF0000ull) >> 16u);
|
||||||
|
x = ((x & 0x00FF00FF00FF00FFull) << 8u) | ((x & 0xFF00FF00FF00FF00ull) >> 8u);
|
||||||
|
return x;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/// the 128-bit product of two 64-bit numbers
|
/// the 128-bit product of two 64-bit numbers
|
||||||
struct uint128_parts
|
struct uint128_parts
|
||||||
{
|
{
|
||||||
@@ -107,8 +201,8 @@ inline uint128_parts full_multiplication(std::uint64_t a, std::uint64_t b) noexc
|
|||||||
/// targets; always inlined, as GCC otherwise calls it in the number loops)
|
/// targets; always inlined, as GCC otherwise calls it in the number loops)
|
||||||
JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const unsigned char* b) noexcept
|
JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const unsigned char* b) noexcept
|
||||||
{
|
{
|
||||||
#if defined(_MSC_VER) || defined(__x86_64__) || defined(__i386__) || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
#if JSON_LITTLE_ENDIAN_TARGET
|
||||||
// the byte order already matches (all MSVC targets are little-endian)
|
// the byte order already matches
|
||||||
std::uint64_t result = 0;
|
std::uint64_t result = 0;
|
||||||
std::memcpy(&result, b, sizeof(result));
|
std::memcpy(&result, b, sizeof(result));
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm> // transform
|
#include <algorithm> // min, transform
|
||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <forward_list> // forward_list
|
#include <forward_list> // forward_list
|
||||||
#include <iterator> // inserter, front_inserter, end
|
#include <iterator> // inserter, front_inserter, end
|
||||||
@@ -69,12 +69,10 @@ void from_json(const BasicJsonType& j, std::optional<T>& opt)
|
|||||||
}
|
}
|
||||||
#endif // JSON_HAS_CPP_17
|
#endif // JSON_HAS_CPP_17
|
||||||
|
|
||||||
// overloads for basic_json template parameters
|
// read a number value of any of the three number types into val;
|
||||||
template < typename BasicJsonType, typename ArithmeticType,
|
// returns false (leaving val untouched) if j is not a number
|
||||||
enable_if_t < std::is_arithmetic<ArithmeticType>::value&&
|
template<typename BasicJsonType, typename ArithmeticType>
|
||||||
!std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
|
bool get_number_value(const BasicJsonType& j, ArithmeticType& val)
|
||||||
int > = 0 >
|
|
||||||
void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
|
|
||||||
{
|
{
|
||||||
switch (static_cast<value_t>(j))
|
switch (static_cast<value_t>(j))
|
||||||
{
|
{
|
||||||
@@ -85,17 +83,17 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
|
|||||||
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
|
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
|
||||||
JSON_ASSERT(ptr != nullptr);
|
JSON_ASSERT(ptr != nullptr);
|
||||||
val = static_cast<ArithmeticType>(*ptr);
|
val = static_cast<ArithmeticType>(*ptr);
|
||||||
break;
|
return true;
|
||||||
}
|
}
|
||||||
case value_t::number_integer:
|
case value_t::number_integer:
|
||||||
{
|
{
|
||||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
|
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
|
||||||
break;
|
return true;
|
||||||
}
|
}
|
||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
{
|
{
|
||||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
|
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
|
||||||
break;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::null:
|
case value_t::null:
|
||||||
@@ -106,6 +104,19 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
|
|||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// overloads for basic_json template parameters
|
||||||
|
template < typename BasicJsonType, typename ArithmeticType,
|
||||||
|
enable_if_t < std::is_arithmetic<ArithmeticType>::value&&
|
||||||
|
!std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
|
||||||
|
int > = 0 >
|
||||||
|
void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!get_number_value(j, val)))
|
||||||
|
{
|
||||||
throw_type_must_be("number", j);
|
throw_type_must_be("number", j);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,29 +256,44 @@ inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType:
|
|||||||
arr = *j.template get_ptr<const typename BasicJsonType::array_t*>();
|
arr = *j.template get_ptr<const typename BasicJsonType::array_t*>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fill a std::array from the JSON array j: the size is checked once and the
|
||||||
|
// elements are read in order, so a short array converts the elements it has and
|
||||||
|
// then throws the out_of_range error at() would throw for the first missing one
|
||||||
|
template<typename BasicJsonType, typename T, std::size_t N>
|
||||||
|
void from_json_std_array(const BasicJsonType& j, std::array<T, N>& arr)
|
||||||
|
{
|
||||||
|
const auto& inner_array = *j.template get_ptr<const typename BasicJsonType::array_t*>();
|
||||||
|
const std::size_t size = (std::min)(N, inner_array.size());
|
||||||
|
for (std::size_t i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
arr[i] = inner_array[i].template get<T>();
|
||||||
|
}
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(size < N))
|
||||||
|
{
|
||||||
|
static_cast<void>(j.at(size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType, typename T, std::size_t N>
|
template<typename BasicJsonType, typename T, std::size_t N>
|
||||||
auto from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr,
|
auto from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr,
|
||||||
priority_tag<2> /*unused*/)
|
priority_tag<2> /*unused*/)
|
||||||
-> decltype(j.template get<T>(), void())
|
-> decltype(j.template get<T>(), void())
|
||||||
{
|
{
|
||||||
for (std::size_t i = 0; i < N; ++i)
|
from_json_std_array(j, arr);
|
||||||
{
|
|
||||||
arr[i] = j.at(i).template get<T>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// reserve() is called through this pair (modeled on from_json_object_reserve)
|
// reserve() is called through this pair (used for arrays and objects alike)
|
||||||
// so from_json_array_impl below has a single body for both ConstructibleArrayType
|
// so from_json_array_impl and the object from_json have a single body for both
|
||||||
// that support reserve() and those that don't.
|
// containers that support reserve() and those that don't.
|
||||||
template<typename ConstructibleArrayType>
|
template<typename ConstructibleContainerType>
|
||||||
auto from_json_array_reserve(ConstructibleArrayType& arr, typename ConstructibleArrayType::size_type size, priority_tag<1> /*unused*/)
|
auto reserve_if_possible(ConstructibleContainerType& c, typename ConstructibleContainerType::size_type size, priority_tag<1> /*unused*/)
|
||||||
-> decltype(arr.reserve(size), void())
|
-> decltype(c.reserve(size), void())
|
||||||
{
|
{
|
||||||
arr.reserve(size);
|
c.reserve(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename ConstructibleArrayType>
|
template<typename ConstructibleContainerType>
|
||||||
void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
|
void reserve_if_possible(ConstructibleContainerType& /*c*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
// append through push_back() where available; containers without it (such as
|
// append through push_back() where available; containers without it (such as
|
||||||
@@ -297,7 +323,7 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p
|
|||||||
{
|
{
|
||||||
ConstructibleArrayType ret;
|
ConstructibleArrayType ret;
|
||||||
const auto* inner_array = j.template get_ptr<const typename BasicJsonType::array_t*>();
|
const auto* inner_array = j.template get_ptr<const typename BasicJsonType::array_t*>();
|
||||||
from_json_array_reserve(ret, inner_array->size(), priority_tag<1> {});
|
reserve_if_possible(ret, inner_array->size(), priority_tag<1> {});
|
||||||
for (const auto& elem : *inner_array)
|
for (const auto& elem : *inner_array)
|
||||||
{
|
{
|
||||||
// get<BasicJsonType>() returns *this, this won't call a from_json
|
// get<BasicJsonType>() returns *this, this won't call a from_json
|
||||||
@@ -336,6 +362,37 @@ std::array<T, sizeof...(Idx)> from_json_inplace_array_impl(const BasicJsonType&
|
|||||||
return { { j.at(Idx).template get<T>()... } };
|
return { { j.at(Idx).template get<T>()... } };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// std::is_trivial is deprecated in C++26 (see binary_writer::to_char_type);
|
||||||
|
// a struct rather than an alias, as astyle indents an #ifdef block of aliases
|
||||||
|
template<typename T>
|
||||||
|
struct is_trivial_element
|
||||||
|
#ifdef JSON_HAS_CPP_26
|
||||||
|
: conjunction<std::is_trivially_copyable<T>, std::is_trivially_default_constructible<T>>
|
||||||
|
#else
|
||||||
|
: std::is_trivial<T>
|
||||||
|
#endif
|
||||||
|
{};
|
||||||
|
|
||||||
|
// the element type may not be default constructible: construct every element
|
||||||
|
// in place (a pack expansion that is unrolled N times)
|
||||||
|
template < typename BasicJsonType, typename T, std::size_t N >
|
||||||
|
std::array<T, N> from_json_inplace_array(const BasicJsonType& j, identity_tag<std::array<T, N>> tag,
|
||||||
|
std::false_type /*is_trivial_element*/)
|
||||||
|
{
|
||||||
|
return from_json_inplace_array_impl(j, tag, make_index_sequence<N> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// a trivial element type cannot tell default construction plus assignment from
|
||||||
|
// construction in place, so use a loop instead of unrolling N conversions
|
||||||
|
template < typename BasicJsonType, typename T, std::size_t N >
|
||||||
|
std::array<T, N> from_json_inplace_array(const BasicJsonType& j, identity_tag<std::array<T, N>> /*unused*/,
|
||||||
|
std::true_type /*is_trivial_element*/)
|
||||||
|
{
|
||||||
|
std::array<T, N> arr; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): every element is assigned or an exception is thrown
|
||||||
|
from_json_std_array(j, arr);
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
template < typename BasicJsonType, typename T, std::size_t N >
|
template < typename BasicJsonType, typename T, std::size_t N >
|
||||||
auto from_json(const BasicJsonType& j, identity_tag<std::array<T, N>> tag)
|
auto from_json(const BasicJsonType& j, identity_tag<std::array<T, N>> tag)
|
||||||
-> decltype(from_json_inplace_array_impl(j, tag, make_index_sequence<N> {}))
|
-> decltype(from_json_inplace_array_impl(j, tag, make_index_sequence<N> {}))
|
||||||
@@ -345,7 +402,7 @@ auto from_json(const BasicJsonType& j, identity_tag<std::array<T, N>> tag)
|
|||||||
throw_type_must_be("array", j);
|
throw_type_must_be("array", j);
|
||||||
}
|
}
|
||||||
|
|
||||||
return from_json_inplace_array_impl(j, tag, make_index_sequence<N> {});
|
return from_json_inplace_array(j, tag, is_trivial_element<T> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
@@ -378,17 +435,6 @@ inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename ConstructibleObjectType>
|
|
||||||
auto from_json_object_reserve(ConstructibleObjectType& obj, typename ConstructibleObjectType::size_type size, priority_tag<1> /*unused*/)
|
|
||||||
-> decltype(obj.reserve(size), void())
|
|
||||||
{
|
|
||||||
obj.reserve(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename ConstructibleObjectType>
|
|
||||||
inline void from_json_object_reserve(ConstructibleObjectType& /*obj*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
|
|
||||||
{}
|
|
||||||
|
|
||||||
// the keys of the source are sorted for the usual object types, so hinting the
|
// the keys of the source are sorted for the usual object types, so hinting the
|
||||||
// end makes every insertion constant-time (for containers that order their keys
|
// end makes every insertion constant-time (for containers that order their keys
|
||||||
// the same way); containers without emplace_hint() (such as ordered_map) fall
|
// the same way); containers without emplace_hint() (such as ordered_map) fall
|
||||||
@@ -417,7 +463,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
|
|||||||
|
|
||||||
ConstructibleObjectType ret;
|
ConstructibleObjectType ret;
|
||||||
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
|
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
|
||||||
from_json_object_reserve(ret, inner_object->size(), priority_tag<1> {});
|
reserve_if_possible(ret, inner_object->size(), priority_tag<1> {});
|
||||||
for (const auto& p : *inner_object)
|
for (const auto& p : *inner_object)
|
||||||
{
|
{
|
||||||
from_json_object_emplace(ret, p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>(), priority_tag<1> {});
|
from_json_object_emplace(ret, p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>(), priority_tag<1> {});
|
||||||
@@ -437,42 +483,17 @@ template < typename BasicJsonType, typename ArithmeticType,
|
|||||||
int > = 0 >
|
int > = 0 >
|
||||||
inline void from_json(const BasicJsonType& j, ArithmeticType& val)
|
inline void from_json(const BasicJsonType& j, ArithmeticType& val)
|
||||||
{
|
{
|
||||||
switch (static_cast<value_t>(j))
|
if (get_number_value(j, val))
|
||||||
{
|
{
|
||||||
case value_t::number_unsigned:
|
return;
|
||||||
{
|
|
||||||
// never null: a basic_json whose number_unsigned_t is number_integer_t
|
|
||||||
// stores no number_unsigned values (#2573)
|
|
||||||
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
|
|
||||||
JSON_ASSERT(ptr != nullptr);
|
|
||||||
val = static_cast<ArithmeticType>(*ptr);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case value_t::number_integer:
|
|
||||||
{
|
|
||||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case value_t::number_float:
|
|
||||||
{
|
|
||||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case value_t::boolean:
|
|
||||||
{
|
|
||||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>());
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::null:
|
// unlike get_arithmetic_value(), a boolean is accepted here
|
||||||
case value_t::object:
|
if (JSON_HEDLEY_UNLIKELY(!j.is_boolean()))
|
||||||
case value_t::array:
|
{
|
||||||
case value_t::string:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
throw_type_must_be("number", j);
|
throw_type_must_be("number", j);
|
||||||
}
|
}
|
||||||
|
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>());
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType, typename Type>
|
template<typename BasicJsonType, typename Type>
|
||||||
|
|||||||
@@ -18,17 +18,14 @@
|
|||||||
#include <limits> // numeric_limits
|
#include <limits> // numeric_limits
|
||||||
#include <type_traits> // conditional
|
#include <type_traits> // conditional
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
#include <cstdlib> // _byteswap_uint64
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16
|
// SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16
|
||||||
// digits of a double at once
|
// digits of a double at once (unless JSON_NO_SIMD selects the portable code)
|
||||||
#if defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC))
|
#if !defined(JSON_NO_SIMD) && (defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC)))
|
||||||
#include <emmintrin.h>
|
#include <emmintrin.h>
|
||||||
#define JSON_DTOA_SSE2 1
|
#define JSON_DTOA_SSE2 1
|
||||||
#define JSON_DTOA_NEON 0
|
#define JSON_DTOA_NEON 0
|
||||||
#elif (defined(__aarch64__) || defined(_M_ARM64)) && !defined(_M_ARM64EC) && !defined(__ARM_BIG_ENDIAN)
|
#elif !defined(JSON_NO_SIMD) && (defined(__aarch64__) || defined(_M_ARM64)) && !defined(_M_ARM64EC) && !defined(__ARM_BIG_ENDIAN)
|
||||||
#include <arm_neon.h>
|
#include <arm_neon.h>
|
||||||
#define JSON_DTOA_SSE2 0
|
#define JSON_DTOA_SSE2 0
|
||||||
#define JSON_DTOA_NEON 1
|
#define JSON_DTOA_NEON 1
|
||||||
@@ -37,6 +34,7 @@
|
|||||||
#define JSON_DTOA_NEON 0
|
#define JSON_DTOA_NEON 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <nlohmann/detail/bit_ops.hpp>
|
||||||
#include <nlohmann/detail/conversions/zmij.hpp>
|
#include <nlohmann/detail/conversions/zmij.hpp>
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
|
||||||
@@ -66,16 +64,6 @@ For a detailed description of the algorithm see:
|
|||||||
namespace dtoa_impl
|
namespace dtoa_impl
|
||||||
{
|
{
|
||||||
|
|
||||||
template<typename Target, typename Source>
|
|
||||||
Target reinterpret_bits(const Source source)
|
|
||||||
{
|
|
||||||
static_assert(sizeof(Target) == sizeof(Source), "size mismatch");
|
|
||||||
|
|
||||||
Target target;
|
|
||||||
std::memcpy(&target, &source, sizeof(Source));
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct diyfp // f * 2^e
|
struct diyfp // f * 2^e
|
||||||
{
|
{
|
||||||
static constexpr int kPrecision = 64; // = q
|
static constexpr int kPrecision = 64; // = q
|
||||||
@@ -230,7 +218,7 @@ boundaries compute_boundaries(FloatType value)
|
|||||||
|
|
||||||
using bits_type = typename std::conditional<kPrecision == 24, std::uint32_t, std::uint64_t >::type;
|
using bits_type = typename std::conditional<kPrecision == 24, std::uint32_t, std::uint64_t >::type;
|
||||||
|
|
||||||
const auto bits = static_cast<std::uint64_t>(reinterpret_bits<bits_type>(value));
|
const auto bits = static_cast<std::uint64_t>(bit_cast<bits_type>(value));
|
||||||
const std::uint64_t E = bits >> (kPrecision - 1);
|
const std::uint64_t E = bits >> (kPrecision - 1);
|
||||||
const std::uint64_t F = bits & (kHiddenBit - 1);
|
const std::uint64_t F = bits & (kHiddenBit - 1);
|
||||||
|
|
||||||
@@ -1084,13 +1072,10 @@ inline std::uint64_t eight_digit_bytes(std::uint64_t abcdefgh) noexcept
|
|||||||
/// the byte stores once this is inlined)
|
/// the byte stores once this is inlined)
|
||||||
inline void store_msb_first(char* p, std::uint64_t v) noexcept
|
inline void store_msb_first(char* p, std::uint64_t v) noexcept
|
||||||
{
|
{
|
||||||
#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
#if JSON_LITTLE_ENDIAN_TARGET
|
||||||
v = __builtin_bswap64(v);
|
v = byteswap(v);
|
||||||
std::memcpy(p, &v, sizeof(v));
|
std::memcpy(p, &v, sizeof(v));
|
||||||
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
#elif JSON_BIG_ENDIAN_TARGET
|
||||||
std::memcpy(p, &v, sizeof(v));
|
|
||||||
#elif defined(_MSC_VER) // (little-endian on all its targets)
|
|
||||||
v = _byteswap_uint64(v);
|
|
||||||
std::memcpy(p, &v, sizeof(v));
|
std::memcpy(p, &v, sizeof(v));
|
||||||
#else
|
#else
|
||||||
for (unsigned i = 0; i < 8; ++i)
|
for (unsigned i = 0; i < 8; ++i)
|
||||||
@@ -1462,8 +1447,7 @@ char* write_positive_zmij(char* first, const char* last, FloatType value)
|
|||||||
{
|
{
|
||||||
static_assert(is_binary64<FloatType>::value,
|
static_assert(is_binary64<FloatType>::value,
|
||||||
"internal error: the conversion of Zmij needs IEEE 754 binary64 numbers");
|
"internal error: the conversion of Zmij needs IEEE 754 binary64 numbers");
|
||||||
std::uint64_t bits = 0;
|
const auto bits = bit_cast<std::uint64_t>(value);
|
||||||
std::memcpy(&bits, &value, sizeof(bits));
|
|
||||||
const zmij::shortest_decimal d = zmij::to_shortest(bits);
|
const zmij::shortest_decimal d = zmij::to_shortest(bits);
|
||||||
if (JSON_HEDLEY_LIKELY(last - first >= 41))
|
if (JSON_HEDLEY_LIKELY(last - first >= 41))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ enum class exception_id : int
|
|||||||
invalid_string_or_size = 113, ///< a binary input contains an invalid string or size specification
|
invalid_string_or_size = 113, ///< a binary input contains an invalid string or size specification
|
||||||
bson_unsupported_type = 114, ///< a BSON record type is not supported
|
bson_unsupported_type = 114, ///< a BSON record type is not supported
|
||||||
invalid_high_precision_number = 115, ///< a UBJSON/BJData high-precision number cannot be parsed
|
invalid_high_precision_number = 115, ///< a UBJSON/BJData high-precision number cannot be parsed
|
||||||
|
invalid_document_image = 116, ///< json_document::load() rejects an image
|
||||||
// invalid_iterator
|
// invalid_iterator
|
||||||
iterators_incompatible = 201, ///< the iterators of a range belong to different values
|
iterators_incompatible = 201, ///< the iterators of a range belong to different values
|
||||||
iterator_from_other_value = 202, ///< an iterator does not belong to the value it is used with
|
iterator_from_other_value = 202, ///< an iterator does not belong to the value it is used with
|
||||||
@@ -100,6 +101,8 @@ enum class exception_id : int
|
|||||||
invalid_utf8 = 316, ///< dump() finds a string that is not valid UTF-8
|
invalid_utf8 = 316, ///< dump() finds a string that is not valid UTF-8
|
||||||
type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format
|
type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format
|
||||||
enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string
|
enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string
|
||||||
|
document_binary_value = 319, ///< a binary value is stored in a json_document
|
||||||
|
document_image_unsupported = 320, ///< a json_document image cannot be saved or loaded (discarded document, big-endian target)
|
||||||
discarded_value_used = 321, ///< a discarded value is used
|
discarded_value_used = 321, ///< a discarded value is used
|
||||||
// out_of_range
|
// out_of_range
|
||||||
array_index_out_of_range = 401, ///< an array index is out of range
|
array_index_out_of_range = 401, ///< an array index is out of range
|
||||||
@@ -117,6 +120,7 @@ enum class exception_id : int
|
|||||||
patch_remove_parent_not_container = 413, ///< the parent of a JSON Patch 'remove' target is not a container
|
patch_remove_parent_not_container = 413, ///< the parent of a JSON Patch 'remove' target is not a container
|
||||||
patch_move_into_child = 414, ///< a JSON Patch 'move' moves a value into one of its children
|
patch_move_into_child = 414, ///< a JSON Patch 'move' moves a value into one of its children
|
||||||
subtype_out_of_range = 415, ///< a binary subtype does not fit into one byte
|
subtype_out_of_range = 415, ///< a binary subtype does not fit into one byte
|
||||||
|
document_too_large = 416, ///< a json_document input, its edit storage, or an image reaches the 32-bit (4 GiB) limit
|
||||||
// other_error
|
// other_error
|
||||||
internal_error = 500, ///< unreachable code was reached
|
internal_error = 500, ///< unreachable code was reached
|
||||||
patch_test_failed = 501, ///< a JSON Patch 'test' operation failed
|
patch_test_failed = 501, ///< a JSON Patch 'test' operation failed
|
||||||
@@ -146,6 +150,13 @@ class exception : public std::exception
|
|||||||
return concat("[json.exception.", ename, '.', std::to_string(id_), "] ");
|
return concat("[json.exception.", ename, '.', std::to_string(id_), "] ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// build the message "[json.exception.<ename>.<id>] <diagnostics><what_arg>"
|
||||||
|
template<typename BasicJsonContext>
|
||||||
|
static std::string what_impl(const std::string& ename, int id_, const std::string& what_arg, BasicJsonContext context)
|
||||||
|
{
|
||||||
|
return concat(name(ename, id_), diagnostics(context), what_arg);
|
||||||
|
}
|
||||||
|
|
||||||
static std::string diagnostics(std::nullptr_t /*leaf_element*/)
|
static std::string diagnostics(std::nullptr_t /*leaf_element*/)
|
||||||
{
|
{
|
||||||
return "";
|
return "";
|
||||||
@@ -158,24 +169,21 @@ class exception : public std::exception
|
|||||||
std::vector<std::string> tokens;
|
std::vector<std::string> tokens;
|
||||||
for (const auto* current = leaf_element; current != nullptr && current->m_parent != nullptr; current = current->m_parent)
|
for (const auto* current = leaf_element; current != nullptr && current->m_parent != nullptr; current = current->m_parent)
|
||||||
{
|
{
|
||||||
switch (current->m_parent->type())
|
const auto* parent = current->m_parent;
|
||||||
|
if (parent->is_array())
|
||||||
{
|
{
|
||||||
case value_t::array:
|
for (std::size_t i = 0; i < parent->m_data.m_value.array->size(); ++i)
|
||||||
{
|
{
|
||||||
for (std::size_t i = 0; i < current->m_parent->m_data.m_value.array->size(); ++i)
|
if (&parent->m_data.m_value.array->operator[](i) == current)
|
||||||
{
|
|
||||||
if (¤t->m_parent->m_data.m_value.array->operator[](i) == current)
|
|
||||||
{
|
{
|
||||||
tokens.emplace_back(std::to_string(i));
|
tokens.emplace_back(std::to_string(i));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
else if (parent->is_object())
|
||||||
case value_t::object:
|
|
||||||
{
|
{
|
||||||
for (const auto& element : *current->m_parent->m_data.m_value.object)
|
for (const auto& element : *parent->m_data.m_value.object)
|
||||||
{
|
{
|
||||||
if (&element.second == current)
|
if (&element.second == current)
|
||||||
{
|
{
|
||||||
@@ -186,20 +194,8 @@ class exception : public std::exception
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::null: // LCOV_EXCL_LINE
|
|
||||||
case value_t::string: // LCOV_EXCL_LINE
|
|
||||||
case value_t::boolean: // LCOV_EXCL_LINE
|
|
||||||
case value_t::number_integer: // LCOV_EXCL_LINE
|
|
||||||
case value_t::number_unsigned: // LCOV_EXCL_LINE
|
|
||||||
case value_t::number_float: // LCOV_EXCL_LINE
|
|
||||||
case value_t::binary: // LCOV_EXCL_LINE
|
|
||||||
case value_t::discarded: // LCOV_EXCL_LINE
|
|
||||||
default: // LCOV_EXCL_LINE
|
|
||||||
break; // LCOV_EXCL_LINE
|
|
||||||
}
|
}
|
||||||
|
// all other parent types (primitives) contribute no token
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tokens.empty())
|
if (tokens.empty())
|
||||||
@@ -315,7 +311,7 @@ class invalid_iterator : public exception
|
|||||||
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
||||||
static invalid_iterator create(int id_, const std::string& what_arg, BasicJsonContext context)
|
static invalid_iterator create(int id_, const std::string& what_arg, BasicJsonContext context)
|
||||||
{
|
{
|
||||||
const std::string w = concat(exception::name("invalid_iterator", id_), exception::diagnostics(context), what_arg);
|
const std::string w = exception::what_impl("invalid_iterator", id_, what_arg, context);
|
||||||
return {id_, w.c_str()};
|
return {id_, w.c_str()};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,7 +335,7 @@ class type_error : public exception
|
|||||||
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
||||||
static type_error create(int id_, const std::string& what_arg, BasicJsonContext context)
|
static type_error create(int id_, const std::string& what_arg, BasicJsonContext context)
|
||||||
{
|
{
|
||||||
const std::string w = concat(exception::name("type_error", id_), exception::diagnostics(context), what_arg);
|
const std::string w = exception::what_impl("type_error", id_, what_arg, context);
|
||||||
return {id_, w.c_str()};
|
return {id_, w.c_str()};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,7 +358,7 @@ class out_of_range : public exception
|
|||||||
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
||||||
static out_of_range create(int id_, const std::string& what_arg, BasicJsonContext context)
|
static out_of_range create(int id_, const std::string& what_arg, BasicJsonContext context)
|
||||||
{
|
{
|
||||||
const std::string w = concat(exception::name("out_of_range", id_), exception::diagnostics(context), what_arg);
|
const std::string w = exception::what_impl("out_of_range", id_, what_arg, context);
|
||||||
return {id_, w.c_str()};
|
return {id_, w.c_str()};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,7 +381,7 @@ class other_error : public exception
|
|||||||
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
||||||
static other_error create(int id_, const std::string& what_arg, BasicJsonContext context)
|
static other_error create(int id_, const std::string& what_arg, BasicJsonContext context)
|
||||||
{
|
{
|
||||||
const std::string w = concat(exception::name("other_error", id_), exception::diagnostics(context), what_arg);
|
const std::string w = exception::what_impl("other_error", id_, what_arg, context);
|
||||||
return {id_, w.c_str()};
|
return {id_, w.c_str()};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ JSON_INLINE_VARIABLE constexpr std::size_t max_valueless_container_size = 1 << 2
|
|||||||
/*!
|
/*!
|
||||||
@brief deserialization of BJData, BON8, BSON, CBOR, MessagePack, and UBJSON values
|
@brief deserialization of BJData, BON8, BSON, CBOR, MessagePack, and UBJSON values
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_parser<BasicJsonType, InputAdapterType>>
|
template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_binary_parser<BasicJsonType, InputAdapterType>>
|
||||||
class binary_reader
|
class binary_reader
|
||||||
{
|
{
|
||||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||||
@@ -293,6 +293,40 @@ class binary_reader
|
|||||||
return is_object ? sax->end_object() : sax->end_array();
|
return is_object ? sax->end_object() : sax->end_array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief close the innermost open container and report whether that
|
||||||
|
completed the value being read
|
||||||
|
|
||||||
|
The value begun by the driver is complete once its container is not
|
||||||
|
inside another one.
|
||||||
|
|
||||||
|
@param[out] value_complete whether no container is left open
|
||||||
|
@return whether the SAX parser accepted the end event
|
||||||
|
*/
|
||||||
|
bool leave_container_and_check(bool& value_complete)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!leave_container()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
value_complete = container_stack.empty();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read an object key and pass it to the SAX parser
|
||||||
|
|
||||||
|
@tparam GetKey member function that reads the key into a string
|
||||||
|
@param[in,out] key string the key is read into; cleared first
|
||||||
|
@return whether reading the key and the SAX event succeeded
|
||||||
|
*/
|
||||||
|
template<bool (binary_reader::*GetKey)(string_t&)>
|
||||||
|
bool read_object_key(string_t& key)
|
||||||
|
{
|
||||||
|
key.clear();
|
||||||
|
return (this->*GetKey)(key) && sax->key(key);
|
||||||
|
}
|
||||||
|
|
||||||
//////////
|
//////////
|
||||||
// BSON //
|
// BSON //
|
||||||
//////////
|
//////////
|
||||||
@@ -1064,6 +1098,34 @@ class binary_reader
|
|||||||
return last_byte_error(exception_id::invalid_string_or_size, concat("indefinite-length ", type_name, " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", get_token_string()), context);
|
return last_byte_error(exception_id::invalid_string_or_size, concat("indefinite-length ", type_name, " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", get_token_string()), context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief report an object key that is not a string
|
||||||
|
|
||||||
|
@param[in] found description of what was found instead of a string key
|
||||||
|
@return false
|
||||||
|
*/
|
||||||
|
bool unsupported_key_type(const char* found) const
|
||||||
|
{
|
||||||
|
return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read the length of a CBOR string or byte array and then its bytes
|
||||||
|
|
||||||
|
@tparam NumberType the type of the length field
|
||||||
|
@tparam ContainerType the destination container (string_t or binary_t)
|
||||||
|
@param[in] context further context information (for diagnostics)
|
||||||
|
@param[out] result container the bytes are appended to
|
||||||
|
|
||||||
|
@return whether reading completed
|
||||||
|
*/
|
||||||
|
template<typename NumberType, typename ContainerType>
|
||||||
|
bool get_cbor_sized_bytes(const char* context, ContainerType& result)
|
||||||
|
{
|
||||||
|
NumberType len{};
|
||||||
|
return get_number(len) && get_bytes(len, context, result);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief reads a definite-length CBOR string
|
@brief reads a definite-length CBOR string
|
||||||
|
|
||||||
@@ -1114,26 +1176,22 @@ class binary_reader
|
|||||||
|
|
||||||
case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
|
case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
|
||||||
{
|
{
|
||||||
std::uint8_t len{};
|
return get_cbor_sized_bytes<std::uint8_t>("string", result);
|
||||||
return get_number(len) && get_string(len, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
|
case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
|
||||||
{
|
{
|
||||||
std::uint16_t len{};
|
return get_cbor_sized_bytes<std::uint16_t>("string", result);
|
||||||
return get_number(len) && get_string(len, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)
|
case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)
|
||||||
{
|
{
|
||||||
std::uint32_t len{};
|
return get_cbor_sized_bytes<std::uint32_t>("string", result);
|
||||||
return get_number(len) && get_string(len, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)
|
case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)
|
||||||
{
|
{
|
||||||
std::uint64_t len{};
|
return get_cbor_sized_bytes<std::uint64_t>("string", result);
|
||||||
return get_number(len) && get_string(len, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -1268,7 +1326,7 @@ class binary_reader
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
|
return unsupported_key_type(found);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -1321,30 +1379,22 @@ class binary_reader
|
|||||||
|
|
||||||
case 0x58: // Binary data (one-byte uint8_t for n follows)
|
case 0x58: // Binary data (one-byte uint8_t for n follows)
|
||||||
{
|
{
|
||||||
std::uint8_t len{};
|
return get_cbor_sized_bytes<std::uint8_t>("binary", result);
|
||||||
return get_number(len) &&
|
|
||||||
get_binary(len, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x59: // Binary data (two-byte uint16_t for n follow)
|
case 0x59: // Binary data (two-byte uint16_t for n follow)
|
||||||
{
|
{
|
||||||
std::uint16_t len{};
|
return get_cbor_sized_bytes<std::uint16_t>("binary", result);
|
||||||
return get_number(len) &&
|
|
||||||
get_binary(len, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x5A: // Binary data (four-byte uint32_t for n follow)
|
case 0x5A: // Binary data (four-byte uint32_t for n follow)
|
||||||
{
|
{
|
||||||
std::uint32_t len{};
|
return get_cbor_sized_bytes<std::uint32_t>("binary", result);
|
||||||
return get_number(len) &&
|
|
||||||
get_binary(len, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x5B: // Binary data (eight-byte uint64_t for n follow)
|
case 0x5B: // Binary data (eight-byte uint64_t for n follow)
|
||||||
{
|
{
|
||||||
std::uint64_t len{};
|
return get_cbor_sized_bytes<std::uint64_t>("binary", result);
|
||||||
return get_number(len) &&
|
|
||||||
get_binary(len, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -1559,12 +1609,12 @@ class binary_reader
|
|||||||
|
|
||||||
if (at_end)
|
if (at_end)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!leave_container()))
|
bool value_complete = false;
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// the value begun here is complete once its container is
|
if (value_complete)
|
||||||
if (container_stack.empty())
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1573,8 +1623,7 @@ class binary_reader
|
|||||||
|
|
||||||
if (top.is_object)
|
if (top.is_object)
|
||||||
{
|
{
|
||||||
key.clear();
|
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_cbor_object_key>(key)))
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_object_key(key) || !sax->key(key)))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2045,20 +2094,17 @@ class binary_reader
|
|||||||
|
|
||||||
case 0xD9: // str 8
|
case 0xD9: // str 8
|
||||||
{
|
{
|
||||||
std::uint8_t len{};
|
return get_string_with_length<std::uint8_t>(result, context);
|
||||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0xDA: // str 16
|
case 0xDA: // str 16
|
||||||
{
|
{
|
||||||
std::uint16_t len{};
|
return get_string_with_length<std::uint16_t>(result, context);
|
||||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0xDB: // str 32
|
case 0xDB: // str 32
|
||||||
{
|
{
|
||||||
std::uint32_t len{};
|
return get_string_with_length<std::uint32_t>(result, context);
|
||||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -2155,7 +2201,7 @@ class binary_reader
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
|
return unsupported_key_type(found);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -2300,12 +2346,12 @@ class binary_reader
|
|||||||
|
|
||||||
if (container_stack.back().remaining == 0)
|
if (container_stack.back().remaining == 0)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!leave_container()))
|
bool value_complete = false;
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// the value begun here is complete once its container is
|
if (value_complete)
|
||||||
if (container_stack.empty())
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2318,8 +2364,7 @@ class binary_reader
|
|||||||
if (is_object)
|
if (is_object)
|
||||||
{
|
{
|
||||||
get();
|
get();
|
||||||
key.clear();
|
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_msgpack_object_key>(key)))
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_object_key(key) || !sax->key(key)))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2503,8 +2548,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
case 'U':
|
case 'U':
|
||||||
{
|
{
|
||||||
std::uint8_t len{};
|
return get_string_with_length<std::uint8_t>(result, context);
|
||||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'i':
|
case 'i':
|
||||||
@@ -2537,8 +2581,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
std::uint16_t len{};
|
return get_string_with_length<std::uint16_t>(result, context);
|
||||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'm':
|
case 'm':
|
||||||
@@ -2547,8 +2590,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
std::uint32_t len{};
|
return get_string_with_length<std::uint32_t>(result, context);
|
||||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'M':
|
case 'M':
|
||||||
@@ -2557,8 +2599,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
std::uint64_t len{};
|
return get_string_with_length<std::uint64_t>(result, context);
|
||||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -3374,12 +3415,12 @@ class binary_reader
|
|||||||
|
|
||||||
if (at_end)
|
if (at_end)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!leave_container()))
|
bool value_complete = false;
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// the value begun here is complete once its container is
|
if (value_complete)
|
||||||
if (container_stack.empty())
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -3388,8 +3429,7 @@ class binary_reader
|
|||||||
|
|
||||||
if (top.is_object)
|
if (top.is_object)
|
||||||
{
|
{
|
||||||
key.clear();
|
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_bon8_key>(key)))
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_bon8_key(key) || !sax->key(key)))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -4129,6 +4169,22 @@ class binary_reader
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read a length of type @a NumberType, then a string of that many bytes
|
||||||
|
|
||||||
|
@tparam NumberType the type of the length field
|
||||||
|
@param[out] result string the bytes are appended to
|
||||||
|
@param[in] context further context information (for diagnostics)
|
||||||
|
|
||||||
|
@return whether reading and validating the string succeeded
|
||||||
|
*/
|
||||||
|
template<typename NumberType>
|
||||||
|
bool get_string_with_length(string_t& result, const char* context)
|
||||||
|
{
|
||||||
|
NumberType len{};
|
||||||
|
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief create a byte array by reading bytes from the input
|
@brief create a byte array by reading bytes from the input
|
||||||
|
|
||||||
@@ -4338,37 +4394,7 @@ class binary_reader
|
|||||||
const std::string& context) const
|
const std::string& context) const
|
||||||
{
|
{
|
||||||
std::string error_msg = "syntax error while parsing ";
|
std::string error_msg = "syntax error while parsing ";
|
||||||
|
error_msg += input_format_name(input_format);
|
||||||
switch (input_format)
|
|
||||||
{
|
|
||||||
case input_format_t::cbor:
|
|
||||||
error_msg += "CBOR";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case input_format_t::msgpack:
|
|
||||||
error_msg += "MessagePack";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case input_format_t::ubjson:
|
|
||||||
error_msg += "UBJSON";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case input_format_t::bson:
|
|
||||||
error_msg += "BSON";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case input_format_t::bjdata:
|
|
||||||
error_msg += "BJData";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case input_format_t::bon8:
|
|
||||||
error_msg += "BON8";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case input_format_t::json: // LCOV_EXCL_LINE
|
|
||||||
default: // LCOV_EXCL_LINE
|
|
||||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
|
||||||
}
|
|
||||||
|
|
||||||
return concat(error_msg, ' ', context, ": ", detail);
|
return concat(error_msg, ' ', context, ": ", detail);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -66,12 +67,118 @@ namespace detail
|
|||||||
/// the supported input formats
|
/// the supported input formats
|
||||||
enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 };
|
enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 };
|
||||||
|
|
||||||
|
/// @return the name of a binary input format, as used in exception messages
|
||||||
|
inline const char* input_format_name(const input_format_t format) noexcept
|
||||||
|
{
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case input_format_t::cbor:
|
||||||
|
return "CBOR";
|
||||||
|
case input_format_t::msgpack:
|
||||||
|
return "MessagePack";
|
||||||
|
case input_format_t::ubjson:
|
||||||
|
return "UBJSON";
|
||||||
|
case input_format_t::bson:
|
||||||
|
return "BSON";
|
||||||
|
case input_format_t::bjdata:
|
||||||
|
return "BJData";
|
||||||
|
case input_format_t::bon8:
|
||||||
|
return "BON8";
|
||||||
|
case input_format_t::json: // LCOV_EXCL_LINE
|
||||||
|
default: // LCOV_EXCL_LINE
|
||||||
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||||
|
return ""; // LCOV_EXCL_LINE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @return the message of the parse_error thrown or reported for an empty input
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////
|
////////////////////
|
||||||
// input adapters //
|
// input adapters //
|
||||||
////////////////////
|
////////////////////
|
||||||
|
|
||||||
#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);
|
||||||
|
const auto room = static_cast<std::size_t>(m_capacity_end - m_end);
|
||||||
|
if (n > room)
|
||||||
|
{
|
||||||
|
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 +316,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 +403,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);
|
|
||||||
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);
|
const char* const next = streambuf_access::get_ptr(sb);
|
||||||
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
|
if (JSON_HEDLEY_LIKELY(next != streambuf_access::get_end(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 +425,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 +524,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 +537,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 +601,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 +626,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 +711,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
|
||||||
@@ -1109,7 +1298,7 @@ inline file_input_adapter input_adapter(std::FILE* file)
|
|||||||
{
|
{
|
||||||
if (file == nullptr)
|
if (file == nullptr)
|
||||||
{
|
{
|
||||||
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
|
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr));
|
||||||
}
|
}
|
||||||
return file_input_adapter(file);
|
return file_input_adapter(file);
|
||||||
}
|
}
|
||||||
@@ -1118,7 +1307,7 @@ inline input_stream_adapter input_adapter(std::istream& stream)
|
|||||||
{
|
{
|
||||||
if (stream.rdbuf() == nullptr)
|
if (stream.rdbuf() == nullptr)
|
||||||
{
|
{
|
||||||
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
|
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr));
|
||||||
}
|
}
|
||||||
return input_stream_adapter(stream);
|
return input_stream_adapter(stream);
|
||||||
}
|
}
|
||||||
@@ -1143,7 +1332,7 @@ contiguous_bytes_input_adapter input_adapter(CharT b)
|
|||||||
{
|
{
|
||||||
if (b == nullptr)
|
if (b == nullptr)
|
||||||
{
|
{
|
||||||
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
|
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr));
|
||||||
}
|
}
|
||||||
auto length = std::strlen(reinterpret_cast<const char*>(b));
|
auto length = std::strlen(reinterpret_cast<const char*>(b));
|
||||||
const auto* ptr = reinterpret_cast<const char*>(b);
|
const auto* ptr = reinterpret_cast<const char*>(b);
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -12,7 +12,9 @@
|
|||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint32_t
|
#include <cstdint> // uint32_t
|
||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
|
#include <cstring> // memcmp
|
||||||
#include <initializer_list> // initializer_list
|
#include <initializer_list> // initializer_list
|
||||||
|
#include <limits> // numeric_limits
|
||||||
#include <string> // char_traits, string
|
#include <string> // char_traits, string
|
||||||
#include <utility> // move
|
#include <utility> // move
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
@@ -417,31 +419,44 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// contiguous input: bulk-append the run of ordinary characters and complete
|
/// contiguous input: bulk-append the run of ordinary characters and complete
|
||||||
/// well-formed UTF-8 sequences starting at the current read position, leaving
|
/// well-formed UTF-8 sequences starting at the current read position, then
|
||||||
/// the first byte that needs individual handling (the closing quote, an
|
/// read the first byte that needs individual handling (the closing quote, an
|
||||||
/// escape, a control character, or an ill-formed UTF-8 byte) for get()
|
/// escape, a control character, or an ill-formed UTF-8 byte) like get().
|
||||||
void scan_string_bulk(std::true_type /*bulk*/)
|
/// That byte is taken from the buffer that was just scanned. Through get(),
|
||||||
|
/// Apple Clang merged this read with the other get() calls of the scan
|
||||||
|
/// function, which load the read position again right after it was stored
|
||||||
|
/// (3% of json::accept() on jeopardy.json on Apple M1).
|
||||||
|
char_int_type get_after_string_run(std::true_type /*bulk*/)
|
||||||
{
|
{
|
||||||
// a pending unget must be consumed through the normal path first
|
// a pending unget must be consumed through the normal path first
|
||||||
if (next_unget)
|
if (next_unget)
|
||||||
{
|
{
|
||||||
return;
|
return get();
|
||||||
}
|
}
|
||||||
const std::size_t remaining = ia.bulk_remaining();
|
const std::size_t remaining = ia.bulk_remaining();
|
||||||
if (remaining == 0)
|
if (remaining == 0)
|
||||||
{
|
{
|
||||||
return;
|
return get();
|
||||||
}
|
}
|
||||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
const auto* const raw = ia.bulk_data();
|
||||||
|
const auto* const data = reinterpret_cast<const unsigned char*>(raw);
|
||||||
|
|
||||||
const std::size_t pos = string_bulk_run(data, remaining);
|
const std::size_t pos = lexer_string_bulk_run(data, remaining);
|
||||||
if (pos == 0)
|
if (pos != 0)
|
||||||
{
|
{
|
||||||
return;
|
|
||||||
}
|
|
||||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
||||||
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
||||||
}
|
}
|
||||||
|
if (pos == remaining)
|
||||||
|
{
|
||||||
|
return get(); // end of the contiguous input
|
||||||
|
}
|
||||||
|
// what get() does, with the byte at hand
|
||||||
|
advance_position();
|
||||||
|
ia.bulk_skip(1);
|
||||||
|
current = char_traits<char_type>::to_int_type(raw[pos]);
|
||||||
|
return track_after_read();
|
||||||
|
}
|
||||||
|
|
||||||
/// consume @a n characters at @a data (the adapter's bulk_data()) and do
|
/// consume @a n characters at @a data (the adapter's bulk_data()) and do
|
||||||
/// the bookkeeping get() would have done for them; the characters are
|
/// the bookkeeping get() would have done for them; the characters are
|
||||||
@@ -473,7 +488,10 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// streaming input: no bulk fast path
|
/// streaming input: no bulk fast path
|
||||||
void scan_string_bulk(std::false_type /*bulk*/) const noexcept {}
|
char_int_type get_after_string_run(std::false_type /*bulk*/)
|
||||||
|
{
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief scan a string literal
|
@brief scan a string literal
|
||||||
@@ -501,11 +519,8 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
// bulk-consume ordinary characters from contiguous input, then
|
// bulk-consume ordinary characters from contiguous input, then
|
||||||
// handle the next special byte through the switch below
|
// get the next character, the one that ends the run
|
||||||
scan_string_bulk(std::integral_constant<bool, bulk_scan> {});
|
switch (get_after_string_run(std::integral_constant<bool, bulk_scan>()))
|
||||||
|
|
||||||
// get the next character
|
|
||||||
switch (get())
|
|
||||||
{
|
{
|
||||||
// end of file while parsing the string
|
// end of file while parsing the string
|
||||||
case char_traits<char_type>::eof():
|
case char_traits<char_type>::eof():
|
||||||
@@ -642,194 +657,74 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
|
|
||||||
// invalid control characters
|
// invalid control characters
|
||||||
case 0x00:
|
case 0x00:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x01:
|
case 0x01:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x02:
|
case 0x02:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x03:
|
case 0x03:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x04:
|
case 0x04:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x05:
|
case 0x05:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x06:
|
case 0x06:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x07:
|
case 0x07:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x08:
|
case 0x08:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x09:
|
case 0x09:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x0A:
|
case 0x0A:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x0B:
|
case 0x0B:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x0C:
|
case 0x0C:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x0D:
|
case 0x0D:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x0E:
|
case 0x0E:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x0F:
|
case 0x0F:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x10:
|
case 0x10:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x11:
|
case 0x11:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x12:
|
case 0x12:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x13:
|
case 0x13:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x14:
|
case 0x14:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x15:
|
case 0x15:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x16:
|
case 0x16:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x17:
|
case 0x17:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x18:
|
case 0x18:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x19:
|
case 0x19:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x1A:
|
case 0x1A:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x1B:
|
case 0x1B:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x1C:
|
case 0x1C:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x1D:
|
case 0x1D:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x1E:
|
case 0x1E:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x1F:
|
case 0x1F:
|
||||||
{
|
{
|
||||||
error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F";
|
static const std::array<const char*, 32> control_character_messages = {{
|
||||||
|
"invalid string: control character U+0000 (NUL) must be escaped to \\u0000",
|
||||||
|
"invalid string: control character U+0001 (SOH) must be escaped to \\u0001",
|
||||||
|
"invalid string: control character U+0002 (STX) must be escaped to \\u0002",
|
||||||
|
"invalid string: control character U+0003 (ETX) must be escaped to \\u0003",
|
||||||
|
"invalid string: control character U+0004 (EOT) must be escaped to \\u0004",
|
||||||
|
"invalid string: control character U+0005 (ENQ) must be escaped to \\u0005",
|
||||||
|
"invalid string: control character U+0006 (ACK) must be escaped to \\u0006",
|
||||||
|
"invalid string: control character U+0007 (BEL) must be escaped to \\u0007",
|
||||||
|
"invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b",
|
||||||
|
"invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t",
|
||||||
|
"invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n",
|
||||||
|
"invalid string: control character U+000B (VT) must be escaped to \\u000B",
|
||||||
|
"invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f",
|
||||||
|
"invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r",
|
||||||
|
"invalid string: control character U+000E (SO) must be escaped to \\u000E",
|
||||||
|
"invalid string: control character U+000F (SI) must be escaped to \\u000F",
|
||||||
|
"invalid string: control character U+0010 (DLE) must be escaped to \\u0010",
|
||||||
|
"invalid string: control character U+0011 (DC1) must be escaped to \\u0011",
|
||||||
|
"invalid string: control character U+0012 (DC2) must be escaped to \\u0012",
|
||||||
|
"invalid string: control character U+0013 (DC3) must be escaped to \\u0013",
|
||||||
|
"invalid string: control character U+0014 (DC4) must be escaped to \\u0014",
|
||||||
|
"invalid string: control character U+0015 (NAK) must be escaped to \\u0015",
|
||||||
|
"invalid string: control character U+0016 (SYN) must be escaped to \\u0016",
|
||||||
|
"invalid string: control character U+0017 (ETB) must be escaped to \\u0017",
|
||||||
|
"invalid string: control character U+0018 (CAN) must be escaped to \\u0018",
|
||||||
|
"invalid string: control character U+0019 (EM) must be escaped to \\u0019",
|
||||||
|
"invalid string: control character U+001A (SUB) must be escaped to \\u001A",
|
||||||
|
"invalid string: control character U+001B (ESC) must be escaped to \\u001B",
|
||||||
|
"invalid string: control character U+001C (FS) must be escaped to \\u001C",
|
||||||
|
"invalid string: control character U+001D (GS) must be escaped to \\u001D",
|
||||||
|
"invalid string: control character U+001E (RS) must be escaped to \\u001E",
|
||||||
|
"invalid string: control character U+001F (US) must be escaped to \\u001F"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
error_message = control_character_messages[static_cast<std::size_t>(current)];
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1625,9 +1520,12 @@ scan_number_done:
|
|||||||
const std::size_t avail = rem + 1;
|
const std::size_t avail = rem + 1;
|
||||||
|
|
||||||
// validate + classify the number extent (mirrors scan_number()'s grammar)
|
// validate + classify the number extent (mirrors scan_number()'s grammar)
|
||||||
|
// and accumulate the mantissa digits on the way, so that tokens with up
|
||||||
|
// to 19 digits need no second pass for their value
|
||||||
std::size_t i = 0;
|
std::size_t i = 0;
|
||||||
std::size_t dot_index = std::string::npos;
|
std::size_t dot_index = std::string::npos;
|
||||||
token_type number_type = token_type::value_unsigned;
|
token_type number_type = token_type::value_unsigned;
|
||||||
|
std::uint64_t w = 0;
|
||||||
if (data[0] == '-')
|
if (data[0] == '-')
|
||||||
{
|
{
|
||||||
number_type = token_type::value_integer;
|
number_type = token_type::value_integer;
|
||||||
@@ -1637,22 +1535,21 @@ scan_number_done:
|
|||||||
return token_type::uninitialized;
|
return token_type::uninitialized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const std::size_t integer_begin = i;
|
||||||
if (data[i] == '0')
|
if (data[i] == '0')
|
||||||
{
|
{
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
else if (data[i] >= '1' && data[i] <= '9')
|
else if (data[i] >= '1' && data[i] <= '9')
|
||||||
{
|
{
|
||||||
++i;
|
i = accumulate_digits(data, i, avail, w);
|
||||||
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
|
||||||
{
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return token_type::uninitialized;
|
return token_type::uninitialized;
|
||||||
}
|
}
|
||||||
|
std::size_t digit_count = i - integer_begin;
|
||||||
|
std::size_t fraction_digits = 0;
|
||||||
if (i < avail && data[i] == '.')
|
if (i < avail && data[i] == '.')
|
||||||
{
|
{
|
||||||
number_type = token_type::value_float;
|
number_type = token_type::value_float;
|
||||||
@@ -1662,29 +1559,39 @@ scan_number_done:
|
|||||||
{
|
{
|
||||||
return token_type::uninitialized;
|
return token_type::uninitialized;
|
||||||
}
|
}
|
||||||
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
const std::size_t fraction_begin = i;
|
||||||
{
|
i = accumulate_digits(data, i, avail, w);
|
||||||
++i;
|
fraction_digits = i - fraction_begin;
|
||||||
}
|
digit_count += fraction_digits;
|
||||||
}
|
}
|
||||||
// the mantissa ends here, whether or not an exponent part follows
|
// the mantissa ends here, whether or not an exponent part follows
|
||||||
const std::size_t mantissa_end = i;
|
const std::size_t mantissa_end = i;
|
||||||
|
std::int64_t exponent = 0;
|
||||||
if (i < avail && (data[i] == 'e' || data[i] == 'E'))
|
if (i < avail && (data[i] == 'e' || data[i] == 'E'))
|
||||||
{
|
{
|
||||||
number_type = token_type::value_float;
|
number_type = token_type::value_float;
|
||||||
++i;
|
++i;
|
||||||
|
bool negative_exponent = false;
|
||||||
if (i < avail && (data[i] == '+' || data[i] == '-'))
|
if (i < avail && (data[i] == '+' || data[i] == '-'))
|
||||||
{
|
{
|
||||||
|
negative_exponent = data[i] == '-';
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
|
if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
|
||||||
{
|
{
|
||||||
return token_type::uninitialized;
|
return token_type::uninitialized;
|
||||||
}
|
}
|
||||||
|
// saturated far beyond every range, see parse_float_exponent()
|
||||||
|
constexpr std::int64_t saturation = 100000000000000000; // 10^17
|
||||||
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||||
{
|
{
|
||||||
|
if (exponent < saturation)
|
||||||
|
{
|
||||||
|
exponent = (exponent * 10) + (data[i] - '0');
|
||||||
|
}
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
exponent = negative_exponent ? -exponent : exponent;
|
||||||
}
|
}
|
||||||
const std::size_t len = i;
|
const std::size_t len = i;
|
||||||
if (number_may_continue(len == avail, std::integral_constant<bool, bulk_window> {}))
|
if (number_may_continue(len == avail, std::integral_constant<bool, bulk_window> {}))
|
||||||
@@ -1705,7 +1612,10 @@ scan_number_done:
|
|||||||
#if !JSON_DIAGNOSTIC_POSITIONS
|
#if !JSON_DIAGNOSTIC_POSITIONS
|
||||||
if (number_type != token_type::value_float)
|
if (number_type != token_type::value_float)
|
||||||
{
|
{
|
||||||
const token_type integer_result = convert_integer(number_type, data, data + len);
|
// up to 18 digits always fit into std::uint64_t and std::int64_t
|
||||||
|
const token_type integer_result = (digit_count <= 18)
|
||||||
|
? store_integer(number_type, w)
|
||||||
|
: convert_integer(number_type, data, data + len);
|
||||||
if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized))
|
if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized))
|
||||||
{
|
{
|
||||||
bulk_consume(data + 1, len - 1);
|
bulk_consume(data + 1, len - 1);
|
||||||
@@ -1719,15 +1629,42 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// materialize the token exactly as scan_number() would. reset() already
|
|
||||||
// cleared token_buffer, so append() fills it (assign() is avoided
|
|
||||||
// because custom string_t types need not provide it)
|
|
||||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
|
||||||
decimal_point_position = dot_index;
|
decimal_point_position = dot_index;
|
||||||
|
|
||||||
bulk_consume(data + 1, len - 1);
|
bulk_consume(data + 1, len - 1);
|
||||||
read_number_terminator(std::integral_constant<bool, bulk_window> {});
|
read_number_terminator(std::integral_constant<bool, bulk_window> {});
|
||||||
|
|
||||||
|
// a float whose digits all fit into w converts from the accumulated
|
||||||
|
// value; longer ones (and other float formats) take the general path
|
||||||
|
if (number_type == token_type::value_float && digit_count <= 19)
|
||||||
|
{
|
||||||
|
float_significand s;
|
||||||
|
s.w = w;
|
||||||
|
s.exponent = exponent - static_cast<std::int64_t>(fraction_digits);
|
||||||
|
s.negative = data[0] == '-';
|
||||||
|
if (convert_accumulated_float(s, std::integral_constant<bool, has_native_float_format<number_float_t>::value> {}))
|
||||||
|
{
|
||||||
|
// the token text is only copied into token_buffer when
|
||||||
|
// get_string() asks for it; the library's own SAX handlers
|
||||||
|
// ignore it. A window into a stream may be refilled by the
|
||||||
|
// next read, so there the text is copied right away.
|
||||||
|
if (bulk_window)
|
||||||
|
{
|
||||||
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pending_number_text = data;
|
||||||
|
pending_number_length = len;
|
||||||
|
}
|
||||||
|
return token_type::value_float;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// materialize the token exactly as scan_number() would. reset() already
|
||||||
|
// cleared token_buffer, so append() fills it (assign() is avoided
|
||||||
|
// because custom string_t types need not provide it)
|
||||||
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
||||||
return convert_number(number_type, mantissa_end);
|
return convert_number(number_type, mantissa_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1756,6 +1693,44 @@ scan_number_done:
|
|||||||
/// the input is not handed back, so the terminator can stay unread
|
/// the input is not handed back, so the terminator can stay unread
|
||||||
void read_number_terminator(std::false_type /*window*/) const noexcept {}
|
void read_number_terminator(std::false_type /*window*/) const noexcept {}
|
||||||
|
|
||||||
|
/// binary32/binary64: the value of a float token from its accumulated
|
||||||
|
/// significand (see scan_number_bulk_contiguous())
|
||||||
|
bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept
|
||||||
|
{
|
||||||
|
// accept() only needs to know that the value is finite (see the parser):
|
||||||
|
// w < 10^19, so a value below 10^max_exponent10 cannot overflow, and a
|
||||||
|
// value that underflows is a finite number as well. Only a token that
|
||||||
|
// may overflow has to be converted to find out.
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(discard_number_values && s.exponent + 19 <= std::numeric_limits<number_float_t>::max_exponent10))
|
||||||
|
{
|
||||||
|
value_float = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
value_float = decimal_to_float<number_float_t>(s);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// other formats (e.g. x87 long double): not instantiated for them, as
|
||||||
|
/// decimal_to_float() only supports binary32/binary64
|
||||||
|
static bool convert_accumulated_float(const float_significand& /*s*/, std::false_type /*native*/) noexcept
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the value of an integer token with at most 18 digits, accumulated in @a w
|
||||||
|
token_type store_integer(token_type number_type, std::uint64_t w) noexcept
|
||||||
|
{
|
||||||
|
if (number_type == token_type::value_unsigned)
|
||||||
|
{
|
||||||
|
value_unsigned = static_cast<number_unsigned_t>(w);
|
||||||
|
// reject values that do not round-trip into a narrower type
|
||||||
|
return (static_cast<std::uint64_t>(value_unsigned) == w) ? token_type::value_unsigned : token_type::uninitialized;
|
||||||
|
}
|
||||||
|
const std::int64_t x = -static_cast<std::int64_t>(w);
|
||||||
|
value_integer = static_cast<number_integer_t>(x);
|
||||||
|
return (static_cast<std::int64_t>(value_integer) == x) ? token_type::value_integer : token_type::uninitialized;
|
||||||
|
}
|
||||||
|
|
||||||
/// contiguous input: try the number fast path, else the byte-path scanner
|
/// contiguous input: try the number fast path, else the byte-path scanner
|
||||||
token_type scan_number_dispatch(std::true_type /*bulk*/)
|
token_type scan_number_dispatch(std::true_type /*bulk*/)
|
||||||
{
|
{
|
||||||
@@ -1770,18 +1745,23 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] literal_text the literal text to expect
|
@param[in] rest the literal text after its first character, which
|
||||||
@param[in] length the length of the passed literal text
|
is current (its length is a template parameter, so
|
||||||
|
that the comparison of the contiguous fast path is
|
||||||
|
a fixed-size load, not a call of memcmp)
|
||||||
@param[in] return_type the token type to return on success
|
@param[in] return_type the token type to return on success
|
||||||
*/
|
*/
|
||||||
JSON_HEDLEY_NON_NULL(2)
|
template<std::size_t N>
|
||||||
token_type scan_literal(const char_type* literal_text, const std::size_t length,
|
JSON_HEDLEY_NEVER_INLINE token_type scan_literal(const char (&rest)[N], token_type return_type) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||||
token_type return_type)
|
|
||||||
{
|
{
|
||||||
JSON_ASSERT(char_traits<char_type>::to_char_type(current) == literal_text[0]);
|
constexpr std::size_t length = N - 1;
|
||||||
for (std::size_t i = 1; i < length; ++i)
|
if (scan_literal_bulk(rest, length, std::integral_constant<bool, bulk_scan> {}))
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(char_traits<char_type>::to_char_type(get()) != literal_text[i]))
|
return return_type;
|
||||||
|
}
|
||||||
|
for (std::size_t i = 0; i < length; ++i)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(char_traits<char_type>::to_char_type(get()) != static_cast<char_type>(rest[i])))
|
||||||
{
|
{
|
||||||
error_message = "invalid literal";
|
error_message = "invalid literal";
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
@@ -1790,6 +1770,27 @@ scan_number_done:
|
|||||||
return return_type;
|
return return_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// contiguous input: compare the rest of a literal at once; false (and
|
||||||
|
/// nothing consumed) if it does not match, so that the byte path reports
|
||||||
|
/// the error at the right character
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE bool scan_literal_bulk(const char* rest, const std::size_t length, std::true_type /*bulk*/)
|
||||||
|
{
|
||||||
|
if (next_unget || ia.bulk_remaining() < length
|
||||||
|
|| std::memcmp(ia.bulk_data(), rest, length) != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bulk_consume(reinterpret_cast<const char*>(ia.bulk_data()), length);
|
||||||
|
current = static_cast<char_int_type>(static_cast<unsigned char>(rest[length - 1]));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// streaming input: no bulk fast path
|
||||||
|
bool scan_literal_bulk(const char* /*rest*/, const std::size_t /*length*/, std::false_type /*bulk*/) const noexcept
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////
|
/////////////////////
|
||||||
// input management
|
// input management
|
||||||
/////////////////////
|
/////////////////////
|
||||||
@@ -1798,6 +1799,7 @@ scan_number_done:
|
|||||||
void reset() noexcept
|
void reset() noexcept
|
||||||
{
|
{
|
||||||
token_buffer.clear();
|
token_buffer.clear();
|
||||||
|
pending_number_text = nullptr;
|
||||||
decimal_point_position = std::string::npos;
|
decimal_point_position = std::string::npos;
|
||||||
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
@@ -2057,10 +2059,30 @@ scan_number_done:
|
|||||||
/// return current string value
|
/// return current string value
|
||||||
string_t& get_string()
|
string_t& get_string()
|
||||||
{
|
{
|
||||||
|
if (pending_number_text != nullptr)
|
||||||
|
{
|
||||||
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(pending_number_text), pending_number_length);
|
||||||
|
pending_number_text = nullptr;
|
||||||
|
}
|
||||||
// a number token holds '.' regardless of the locale (#4084)
|
// a number token holds '.' regardless of the locale (#4084)
|
||||||
return token_buffer;
|
return token_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// the value of a string token (key or string), which never has pending
|
||||||
|
/// number text, so get_string()'s check is not needed
|
||||||
|
string_t& get_string_token() noexcept
|
||||||
|
{
|
||||||
|
JSON_ASSERT(pending_number_text == nullptr);
|
||||||
|
return token_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the text of a float token for a SAX handler that ignores it (the
|
||||||
|
/// library's own handlers): empty, without copying the token
|
||||||
|
const string_t& get_ignored_number_text() const noexcept
|
||||||
|
{
|
||||||
|
return token_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////
|
/////////////////////
|
||||||
// diagnostics
|
// diagnostics
|
||||||
/////////////////////
|
/////////////////////
|
||||||
@@ -2394,21 +2416,14 @@ scan_number_done:
|
|||||||
return token_type::value_separator;
|
return token_type::value_separator;
|
||||||
|
|
||||||
// literals
|
// literals
|
||||||
|
// (the text after the first character; no local arrays, which
|
||||||
|
// would give this function a stack canary on every token)
|
||||||
case 't':
|
case 't':
|
||||||
{
|
return scan_literal("rue", token_type::literal_true);
|
||||||
std::array<char_type, 4> true_literal = {{static_cast<char_type>('t'), static_cast<char_type>('r'), static_cast<char_type>('u'), static_cast<char_type>('e')}};
|
|
||||||
return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true);
|
|
||||||
}
|
|
||||||
case 'f':
|
case 'f':
|
||||||
{
|
return scan_literal("alse", token_type::literal_false);
|
||||||
std::array<char_type, 5> false_literal = {{static_cast<char_type>('f'), static_cast<char_type>('a'), static_cast<char_type>('l'), static_cast<char_type>('s'), static_cast<char_type>('e')}};
|
|
||||||
return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false);
|
|
||||||
}
|
|
||||||
case 'n':
|
case 'n':
|
||||||
{
|
return scan_literal("ull", token_type::literal_null);
|
||||||
std::array<char_type, 4> null_literal = {{static_cast<char_type>('n'), static_cast<char_type>('u'), static_cast<char_type>('l'), static_cast<char_type>('l')}};
|
|
||||||
return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// string
|
// string
|
||||||
case '\"':
|
case '\"':
|
||||||
@@ -2456,6 +2471,11 @@ scan_number_done:
|
|||||||
/// whether the next get() call should just return current
|
/// whether the next get() call should just return current
|
||||||
bool next_unget = false;
|
bool next_unget = false;
|
||||||
|
|
||||||
|
/// the text of the current float token in the input, not yet copied into
|
||||||
|
/// token_buffer (see get_string()), or nullptr
|
||||||
|
const char* pending_number_text = nullptr;
|
||||||
|
std::size_t pending_number_length = 0;
|
||||||
|
|
||||||
/// the start position of the current token
|
/// the start position of the current token
|
||||||
position_t position {};
|
position_t position {};
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // int64_t, uint64_t
|
#include <cstdint> // int64_t, uint64_t
|
||||||
#include <cstdlib> // strtof, strtod, strtold
|
#include <cstdlib> // strtof, strtod, strtold
|
||||||
#include <cstring> // memcpy
|
|
||||||
#include <limits> // numeric_limits
|
#include <limits> // numeric_limits
|
||||||
#include <string> // string
|
#include <string> // string
|
||||||
#include <type_traits> // conditional, integral_constant, is_signed, true_type, false_type
|
#include <type_traits> // conditional, integral_constant, is_signed, true_type, false_type
|
||||||
@@ -275,13 +274,77 @@ JSON_HEDLEY_ALWAYS_INLINE std::uint32_t parse_eight_digits(std::uint64_t v) noex
|
|||||||
return static_cast<std::uint32_t>(((v & 0x0000FFFF0000FFFFu) * 42949672960001u) >> 32u);
|
return static_cast<std::uint32_t>(((v & 0x0000FFFF0000FFFFu) * 42949672960001u) >> 32u);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// whether [first, last) contains a digit other than '0'
|
/// whether the eight bytes in @a v (see read_eight_bytes()) are all ASCII
|
||||||
inline bool has_nonzero_digit(const char* first, const char* last) noexcept
|
/// digits (after fast_float)
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE bool is_eight_digits(std::uint64_t v) noexcept
|
||||||
{
|
{
|
||||||
return std::any_of(first, last, [](char c) noexcept
|
return ((v & 0xF0F0F0F0F0F0F0F0u) | (((v + 0x0606060606060606u) & 0xF0F0F0F0F0F0F0F0u) >> 4u)) == 0x3333333333333333u;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief skip the ASCII digits starting at data[i] and accumulate their value
|
||||||
|
|
||||||
|
Eight digits are taken at a time while at least eight bytes remain. @a w wraps
|
||||||
|
around after 19 digits; the caller only uses it for shorter digit runs.
|
||||||
|
|
||||||
|
@return the index of the first byte at or after @a i that is not a digit
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE std::size_t accumulate_digits(const char* data, std::size_t i, std::size_t avail, std::uint64_t& w) noexcept
|
||||||
|
{
|
||||||
|
while (avail - i >= 8)
|
||||||
{
|
{
|
||||||
return c != '0';
|
const std::uint64_t v = read_eight_bytes(data + i);
|
||||||
});
|
if (!is_eight_digits(v))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
w = (w * 100000000u) + parse_eight_digits(v);
|
||||||
|
i += 8;
|
||||||
|
}
|
||||||
|
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||||
|
{
|
||||||
|
w = (w * 10u) + static_cast<std::uint64_t>(data[i] - '0');
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// whether [first, last) contains a digit other than '0'; a plain loop, as
|
||||||
|
/// GCC calls std::any_of out of line even for the usual empty range
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE bool has_nonzero_digit(const char* first, const char* last) noexcept
|
||||||
|
{
|
||||||
|
for (; first != last; ++first)
|
||||||
|
{
|
||||||
|
if (*first != '0')
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief accumulate the validated digits [p, last) into @a w, as long as
|
||||||
|
@a remaining digits still fit into it
|
||||||
|
|
||||||
|
Eight digits are taken at a time while at least eight remain, and @a remaining
|
||||||
|
is decreased by the number of digits taken.
|
||||||
|
|
||||||
|
@return a pointer past the last digit taken
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE const char* accumulate_significant_digits(const char* p, const char* last, int& remaining, std::uint64_t& w) noexcept
|
||||||
|
{
|
||||||
|
while (remaining >= 8 && last - p >= 8)
|
||||||
|
{
|
||||||
|
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
|
||||||
|
p += 8;
|
||||||
|
remaining -= 8;
|
||||||
|
}
|
||||||
|
for (; remaining > 0 && p != last; ++p, --remaining)
|
||||||
|
{
|
||||||
|
w = (w * 10u) + static_cast<std::uint64_t>(*p - '0');
|
||||||
|
}
|
||||||
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the value of the validated exponent digits [+-]?[0-9]+ in [first, last),
|
/// the value of the validated exponent digits [+-]?[0-9]+ in [first, last),
|
||||||
@@ -327,7 +390,7 @@ truncated is set.
|
|||||||
std::string::npos if there is none
|
std::string::npos if there is none
|
||||||
@param[in] mantissa_end index of the 'e'/'E', or the token length
|
@param[in] mantissa_end index of the 'e'/'E', or the token length
|
||||||
*/
|
*/
|
||||||
inline float_significand parse_float_significand(const char* first, const char* last,
|
JSON_HEDLEY_ALWAYS_INLINE float_significand parse_float_significand(const char* first, const char* last,
|
||||||
std::size_t decimal_point_position, std::size_t mantissa_end) noexcept
|
std::size_t decimal_point_position, std::size_t mantissa_end) noexcept
|
||||||
{
|
{
|
||||||
float_significand s;
|
float_significand s;
|
||||||
@@ -342,16 +405,7 @@ inline float_significand parse_float_significand(const char* first, const char*
|
|||||||
int remaining = 19; // digits that still fit into w
|
int remaining = 19; // digits that still fit into w
|
||||||
if (*p != '0') // the integer part is "0" or [1-9][0-9]*
|
if (*p != '0') // the integer part is "0" or [1-9][0-9]*
|
||||||
{
|
{
|
||||||
while (remaining >= 8 && integer_last - p >= 8)
|
p = accumulate_significant_digits(p, integer_last, remaining, w);
|
||||||
{
|
|
||||||
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
|
|
||||||
p += 8;
|
|
||||||
remaining -= 8;
|
|
||||||
}
|
|
||||||
for (; remaining > 0 && p != integer_last; ++p, --remaining)
|
|
||||||
{
|
|
||||||
w = (w * 10u) + static_cast<std::uint64_t>(*p - '0');
|
|
||||||
}
|
|
||||||
s.exponent = integer_last - p;
|
s.exponent = integer_last - p;
|
||||||
s.truncated = has_nonzero_digit(p, integer_last);
|
s.truncated = has_nonzero_digit(p, integer_last);
|
||||||
}
|
}
|
||||||
@@ -370,16 +424,7 @@ inline float_significand parse_float_significand(const char* first, const char*
|
|||||||
s.exponent -= p - zeros;
|
s.exponent -= p - zeros;
|
||||||
}
|
}
|
||||||
const char* const digits = p;
|
const char* const digits = p;
|
||||||
while (remaining >= 8 && mantissa_last - p >= 8)
|
p = accumulate_significant_digits(p, mantissa_last, remaining, w);
|
||||||
{
|
|
||||||
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
|
|
||||||
p += 8;
|
|
||||||
remaining -= 8;
|
|
||||||
}
|
|
||||||
for (; remaining > 0 && p != mantissa_last; ++p, --remaining)
|
|
||||||
{
|
|
||||||
w = (w * 10u) + static_cast<std::uint64_t>(*p - '0');
|
|
||||||
}
|
|
||||||
s.exponent -= p - digits;
|
s.exponent -= p - digits;
|
||||||
s.truncated = s.truncated || has_nonzero_digit(p, mantissa_last);
|
s.truncated = s.truncated || has_nonzero_digit(p, mantissa_last);
|
||||||
}
|
}
|
||||||
@@ -712,15 +757,14 @@ std::uint64_t digit_comparison(const char* first, const char* last, std::uint64_
|
|||||||
inline void float_from_bits(std::uint64_t bits, double& value) noexcept
|
inline void float_from_bits(std::uint64_t bits, double& value) noexcept
|
||||||
{
|
{
|
||||||
static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits");
|
static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits");
|
||||||
std::memcpy(&value, &bits, sizeof(value));
|
value = bit_cast<double>(bits);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the float with the IEEE-754 bits @a bits (the lower 32)
|
/// the float with the IEEE-754 bits @a bits (the lower 32)
|
||||||
inline void float_from_bits(std::uint64_t bits, float& value) noexcept
|
inline void float_from_bits(std::uint64_t bits, float& value) noexcept
|
||||||
{
|
{
|
||||||
static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
|
static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
|
||||||
const auto bits32 = static_cast<std::uint32_t>(bits);
|
value = bit_cast<float>(static_cast<std::uint32_t>(bits));
|
||||||
std::memcpy(&value, &bits32, sizeof(bits32));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the powers of ten that are exact in binary64 (up to 10^22)
|
/// the powers of ten that are exact in binary64 (up to 10^22)
|
||||||
|
|||||||
@@ -51,6 +51,20 @@ template<typename BasicJsonType>
|
|||||||
using parser_callback_t =
|
using parser_callback_t =
|
||||||
std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>;
|
std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>;
|
||||||
|
|
||||||
|
/// whether a SAX handler ignores the text of float tokens (the library's own
|
||||||
|
/// handlers do), so that the parser need not materialize it
|
||||||
|
template<typename SAX>
|
||||||
|
struct sax_ignores_number_text : std::false_type {};
|
||||||
|
|
||||||
|
template<typename BasicJsonType, typename InputAdapterType, bool Collect>
|
||||||
|
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType, Collect>> : std::true_type {};
|
||||||
|
|
||||||
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
|
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
||||||
|
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
struct sax_ignores_number_text<json_sax_acceptor<BasicJsonType>> : std::true_type {};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief syntax analysis
|
@brief syntax analysis
|
||||||
|
|
||||||
@@ -117,7 +131,7 @@ class parser
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
json_sax_dom_parser<BasicJsonType, InputAdapterType, true> sdp(result, allow_exceptions, &m_lexer);
|
||||||
|
|
||||||
// in case of an error, return a discarded value
|
// in case of an error, return a discarded value
|
||||||
if (!parse_dom(sdp, strict))
|
if (!parse_dom(sdp, strict))
|
||||||
@@ -150,13 +164,52 @@ class parser
|
|||||||
const bool result = sax_parse_internal(sax);
|
const bool result = sax_parse_internal(sax);
|
||||||
|
|
||||||
if (result)
|
if (result)
|
||||||
|
{
|
||||||
|
return finish_input(*sax, strict);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// the text of the current float token for @a sax (see sax_ignores_number_text)
|
||||||
|
template<typename SAX>
|
||||||
|
const string_t& number_float_text(SAX* /*sax*/)
|
||||||
|
{
|
||||||
|
return number_float_text(std::integral_constant<bool, sax_ignores_number_text<SAX>::value> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
const string_t& number_float_text(std::true_type /*ignored*/) const noexcept
|
||||||
|
{
|
||||||
|
return m_lexer.get_ignored_number_text();
|
||||||
|
}
|
||||||
|
|
||||||
|
string_t& number_float_text(std::false_type /*ignored*/)
|
||||||
|
{
|
||||||
|
return m_lexer.get_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief apply the end-of-input handling after a value was parsed
|
||||||
|
|
||||||
|
In strict mode, the next token must be the end of the input; otherwise
|
||||||
|
parse_error.101 is reported through @a sax. In non-strict mode, the
|
||||||
|
lookahead is released so the caller can keep reading the input right after
|
||||||
|
the parsed value.
|
||||||
|
|
||||||
|
@param[in,out] sax the SAX parser to report an error to
|
||||||
|
@param[in] strict whether to expect the last token to be EOF
|
||||||
|
@return true, or what @a sax returned for the error
|
||||||
|
*/
|
||||||
|
template<typename SAX>
|
||||||
|
bool finish_input(SAX& sax, const bool strict)
|
||||||
{
|
{
|
||||||
if (strict)
|
if (strict)
|
||||||
{
|
{
|
||||||
// strict mode: next byte must be EOF
|
// strict mode: next byte must be EOF
|
||||||
if (get_token() != token_type::end_of_input)
|
if (get_token() != token_type::end_of_input)
|
||||||
{
|
{
|
||||||
return syntax_error(*sax, exception_message(token_type::end_of_input, "value"));
|
return syntax_error(sax, exception_message(token_type::end_of_input, "value"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -165,12 +218,10 @@ class parser
|
|||||||
// the value by leaving the character that terminated it
|
// the value by leaving the character that terminated it
|
||||||
m_lexer.release_lookahead();
|
m_lexer.release_lookahead();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
/*!
|
/*!
|
||||||
@brief run a DOM SAX parser to completion and position the lexer
|
@brief run a DOM SAX parser to completion and position the lexer
|
||||||
|
|
||||||
@@ -190,20 +241,7 @@ class parser
|
|||||||
{
|
{
|
||||||
sax_parse_internal(&sdp);
|
sax_parse_internal(&sdp);
|
||||||
|
|
||||||
if (strict)
|
static_cast<void>(finish_input(sdp, strict));
|
||||||
{
|
|
||||||
// in strict mode, input must be completely read
|
|
||||||
if (get_token() != token_type::end_of_input)
|
|
||||||
{
|
|
||||||
syntax_error(sdp, exception_message(token_type::end_of_input, "value"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// the caller keeps using the input: position it right after
|
|
||||||
// the value by leaving the character that terminated it
|
|
||||||
m_lexer.release_lookahead();
|
|
||||||
}
|
|
||||||
|
|
||||||
return !sdp.is_errored();
|
return !sdp.is_errored();
|
||||||
}
|
}
|
||||||
@@ -212,12 +250,20 @@ class parser
|
|||||||
JSON_HEDLEY_NON_NULL(2)
|
JSON_HEDLEY_NON_NULL(2)
|
||||||
bool sax_parse_internal(SAX* sax)
|
bool sax_parse_internal(SAX* sax)
|
||||||
{
|
{
|
||||||
// stack to remember the hierarchy of structured values we are parsing
|
|
||||||
// true = array; false = object
|
|
||||||
std::vector<bool> states;
|
|
||||||
// value to avoid a goto (see comment where set to true)
|
// value to avoid a goto (see comment where set to true)
|
||||||
bool skip_to_state_evaluation = false;
|
bool skip_to_state_evaluation = false;
|
||||||
|
|
||||||
|
// stack to remember the hierarchy of structured values we are parsing
|
||||||
|
// true = array; false = object (bytes, not std::vector<bool>, whose
|
||||||
|
// push_back() is a call with GCC)
|
||||||
|
std::vector<std::uint8_t> states;
|
||||||
|
// a document that starts with a container pushes onto the stack at
|
||||||
|
// once: allocate it in one step instead of growing it from 1 byte
|
||||||
|
if (last_token == token_type::begin_object || last_token == token_type::begin_array)
|
||||||
|
{
|
||||||
|
states.reserve(32);
|
||||||
|
}
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (!skip_to_state_evaluation)
|
if (!skip_to_state_evaluation)
|
||||||
@@ -247,7 +293,7 @@ class parser
|
|||||||
{
|
{
|
||||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
||||||
}
|
}
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -259,7 +305,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remember we are now inside an object
|
// remember we are now inside an object
|
||||||
states.push_back(false);
|
states.push_back(0);
|
||||||
|
|
||||||
// parse values
|
// parse values
|
||||||
get_token();
|
get_token();
|
||||||
@@ -284,7 +330,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remember we are now inside an array
|
// remember we are now inside an array
|
||||||
states.push_back(true);
|
states.push_back(1);
|
||||||
|
|
||||||
// parse values (no need to call get_token)
|
// parse values (no need to call get_token)
|
||||||
continue;
|
continue;
|
||||||
@@ -301,7 +347,7 @@ class parser
|
|||||||
out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
|
out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, number_float_text(sax))))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -347,7 +393,7 @@ class parser
|
|||||||
|
|
||||||
case token_type::value_string:
|
case token_type::value_string:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string_token())))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -372,7 +418,7 @@ class parser
|
|||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_chars_read_total() == 1))
|
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_chars_read_total() == 1))
|
||||||
{
|
{
|
||||||
return syntax_error(*sax, "attempting to parse an empty input; check that your input string or stream contains the expected JSON");
|
return syntax_error(*sax, empty_input_message());
|
||||||
}
|
}
|
||||||
|
|
||||||
return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
|
return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
|
||||||
@@ -401,7 +447,7 @@ class parser
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (states.back()) // array
|
if (states.back() != 0) // array
|
||||||
{
|
{
|
||||||
// comma -> next value
|
// comma -> next value
|
||||||
// or end of array (ignore_trailing_commas = true)
|
// or end of array (ignore_trailing_commas = true)
|
||||||
@@ -456,7 +502,7 @@ class parser
|
|||||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,321 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-FileCopyrightText: 2018-2025 The simdjson authors <https://github.com/simdjson/simdjson>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array> // array
|
||||||
|
#include <atomic> // atomic
|
||||||
|
#include <cstddef> // size_t
|
||||||
|
#include <cstdint> // uint8_t, uint64_t
|
||||||
|
|
||||||
|
#include <nlohmann/detail/bit_ops.hpp>
|
||||||
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
|
||||||
|
// Vector code for runs of string bytes, used by the lexer for contiguous input
|
||||||
|
// (see string_bulk_run()). NEON (AArch64) and SSE2 (x86-64) belong to the
|
||||||
|
// baseline instruction sets and are used by default. The vector UTF-8 check
|
||||||
|
// needs NEON or SSSE3. SSSE3 is not part of x86-64, and the code must not
|
||||||
|
// depend on the flags of a translation unit (two translation units with
|
||||||
|
// different flags would have different definitions of the same inline
|
||||||
|
// functions): the check is compiled for SSSE3 with a function attribute and
|
||||||
|
// used where the CPU has SSSE3 (all x86-64 CPUs since about 2011), else the
|
||||||
|
// portable check. Code compiled for SSSE3 anyway (-mssse3 or more) skips the
|
||||||
|
// CPU check. JSON_NO_SIMD selects the portable code everywhere.
|
||||||
|
// NLOHMANN_JSON_NO_TARGET_ATTRIBUTE (for compilers that ignore the target
|
||||||
|
// attribute and then reject the SSSE3 intrinsics) keeps the check portable
|
||||||
|
// without disabling the SSE2 code.
|
||||||
|
#if !defined(JSON_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
#include <arm_neon.h>
|
||||||
|
#define JSON_SCAN_NEON 1
|
||||||
|
#else
|
||||||
|
#define JSON_SCAN_NEON 0
|
||||||
|
#endif
|
||||||
|
// (x86 only: other targets can define __SSE2__ as well, e.g., WebAssembly with -msse2, but have no <cpuid.h>)
|
||||||
|
#if !defined(JSON_NO_SIMD) && !JSON_SCAN_NEON && (defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || defined(_M_IX86)) && !defined(_M_ARM64EC) && (defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2))
|
||||||
|
#include <emmintrin.h>
|
||||||
|
#define JSON_SCAN_SSE2 1
|
||||||
|
#else
|
||||||
|
#define JSON_SCAN_SSE2 0
|
||||||
|
#endif
|
||||||
|
#if JSON_SCAN_SSE2 && defined(__SSSE3__)
|
||||||
|
#include <tmmintrin.h>
|
||||||
|
#define JSON_SCAN_SSSE3 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#else
|
||||||
|
#define JSON_SCAN_SSSE3 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#endif
|
||||||
|
#if JSON_SCAN_SSE2 && !JSON_SCAN_SSSE3 && !defined(NLOHMANN_JSON_NO_TARGET_ATTRIBUTE) && ((defined(__clang__) && __clang_major__ >= 4) || (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) && !defined(__cpp_modules)))
|
||||||
|
// (GCC before 4.9 has no SSSE3 intrinsics without -mssse3; GCC with C++20
|
||||||
|
// modules drops the target attribute from functions it reads from a module
|
||||||
|
// interface and then rejects the intrinsics: "needs isa option -mssse3")
|
||||||
|
// <cpuid.h> of GCC before 11 has no include guard, and json.hpp and
|
||||||
|
// json_view.hpp both include it
|
||||||
|
#ifndef NLOHMANN_JSON_CPUID_INCLUDED
|
||||||
|
#define NLOHMANN_JSON_CPUID_INCLUDED
|
||||||
|
#include <cpuid.h>
|
||||||
|
#endif
|
||||||
|
#include <tmmintrin.h>
|
||||||
|
#define JSON_SCAN_SSSE3_DISPATCH 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#define JSON_SCAN_SSSE3_TARGET __attribute__((target("ssse3")))
|
||||||
|
#elif JSON_SCAN_SSE2 && !JSON_SCAN_SSSE3 && defined(_MSC_VER)
|
||||||
|
// (MSVC compiles intrinsics of any instruction set)
|
||||||
|
#include <intrin.h>
|
||||||
|
#include <tmmintrin.h>
|
||||||
|
#define JSON_SCAN_SSSE3_DISPATCH 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#define JSON_SCAN_SSSE3_TARGET
|
||||||
|
#else
|
||||||
|
#define JSON_SCAN_SSSE3_DISPATCH 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#define JSON_SCAN_SSSE3_TARGET
|
||||||
|
#endif
|
||||||
|
#define JSON_SCAN_VECTOR (JSON_SCAN_NEON || JSON_SCAN_SSE2)
|
||||||
|
#define JSON_SCAN_VECTOR_UTF8 (JSON_SCAN_NEON || JSON_SCAN_SSSE3 || JSON_SCAN_SSSE3_DISPATCH)
|
||||||
|
|
||||||
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
|
||||||
|
#if JSON_SCAN_VECTOR
|
||||||
|
/*!
|
||||||
|
@brief the first byte of a string run that is a quote, a backslash, a control
|
||||||
|
character, or not ASCII, 16 bytes per step
|
||||||
|
|
||||||
|
Stops at such a byte, or where fewer than 16 bytes are left (the caller tells
|
||||||
|
the two apart). A signed compare with 0x20 finds control characters and
|
||||||
|
non-ASCII bytes at once.
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned char* p, const unsigned char* e) noexcept
|
||||||
|
{
|
||||||
|
while (e - p >= 16)
|
||||||
|
{
|
||||||
|
#if JSON_SCAN_NEON
|
||||||
|
const uint8x16_t in = vld1q_u8(p);
|
||||||
|
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)));
|
||||||
|
// one nibble per byte (the usual NEON replacement of x86's movemask, see
|
||||||
|
// D. Kutenin, "Porting x86 vector bitmask optimizations to Arm NEON", 2022)
|
||||||
|
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
||||||
|
if (bits != 0)
|
||||||
|
{
|
||||||
|
return p + (count_trailing_zeros(bits) >> 2u);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // 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)));
|
||||||
|
if (bits != 0)
|
||||||
|
{
|
||||||
|
return p + count_trailing_zeros(bits);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
p += 16;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_SCAN_VECTOR
|
||||||
|
/// vector_plain_run() for runs that are already longer than a few words:
|
||||||
|
/// inline with SSE2, out of line with NEON, where a larger string loop slows
|
||||||
|
/// down the rest of the lexer
|
||||||
|
#if JSON_SCAN_NEON
|
||||||
|
JSON_HEDLEY_NEVER_INLINE inline
|
||||||
|
#else
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
|
#endif
|
||||||
|
const unsigned char* vector_plain_run_long(const unsigned char* p, const unsigned char* e) noexcept
|
||||||
|
{
|
||||||
|
return vector_plain_run(p, e);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_SCAN_SSSE3_DISPATCH
|
||||||
|
/// whether the CPU has SSSE3 (CPUID leaf 1, ECX bit 9)
|
||||||
|
inline bool cpu_ssse3() noexcept
|
||||||
|
{
|
||||||
|
#if defined(_MSC_VER) && !defined(__clang__)
|
||||||
|
std::array<int, 4> regs {{}};
|
||||||
|
__cpuid(regs.data(), 1);
|
||||||
|
return (static_cast<unsigned>(regs[2]) & (1u << 9u)) != 0;
|
||||||
|
#else
|
||||||
|
unsigned eax = 0;
|
||||||
|
unsigned ebx = 0;
|
||||||
|
unsigned ecx = 0;
|
||||||
|
unsigned edx = 0;
|
||||||
|
return __get_cpuid(1, &eax, &ebx, &ecx, &edx) != 0 && (ecx & (1u << 9u)) != 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// whether the CPU has SSSE3, asked once: the answer is kept in an atomic
|
||||||
|
/// that is initialized at compile time, so that neither a guard of a local
|
||||||
|
/// static nor a global constructor is needed (threads that ask at the same
|
||||||
|
/// time all store the same answer)
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE bool cpu_has_ssse3() noexcept
|
||||||
|
{
|
||||||
|
static std::atomic<int> known{0}; // 0: not asked yet, 1: no, 2: yes
|
||||||
|
int state = known.load(std::memory_order_relaxed);
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(state == 0))
|
||||||
|
{
|
||||||
|
state = cpu_ssse3() ? 2 : 1;
|
||||||
|
known.store(state, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
return state == 2;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_SCAN_VECTOR_UTF8
|
||||||
|
/// Tables of the UTF-8 check of J. Keiser and D. Lemire, "Validating UTF-8 In
|
||||||
|
/// Less Than One Instruction Per Byte" (2021), as in simdjson ("lookup4"): each
|
||||||
|
/// maps a nibble (high and low nibble of the previous byte, high nibble of the
|
||||||
|
/// current byte) to the errors it allows; a byte pair is ill-formed if all
|
||||||
|
/// three have an error bit in common.
|
||||||
|
template<typename Dummy = void>
|
||||||
|
struct utf8_lookup4
|
||||||
|
{
|
||||||
|
static constexpr std::uint8_t too_short = 1u << 0u, too_long = 1u << 1u, overlong_3 = 1u << 2u, too_large = 1u << 3u;
|
||||||
|
static constexpr std::uint8_t surrogate = 1u << 4u, overlong_2 = 1u << 5u, too_large_1000 = 1u << 6u, overlong_4 = 1u << 6u;
|
||||||
|
static constexpr std::uint8_t two_conts = 1u << 7u, carry = too_short | too_long | two_conts;
|
||||||
|
static const std::array<std::uint8_t, 16> byte_1_high;
|
||||||
|
static const std::array<std::uint8_t, 16> byte_1_low;
|
||||||
|
static const std::array<std::uint8_t, 16> byte_2_high;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Dummy>
|
||||||
|
const std::array<std::uint8_t, 16> utf8_lookup4<Dummy>::byte_1_high =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
too_long, too_long, too_long, too_long, too_long, too_long, too_long, too_long,
|
||||||
|
two_conts, two_conts, two_conts, two_conts,
|
||||||
|
too_short | overlong_2, too_short, too_short | overlong_3 | surrogate, too_short | too_large | too_large_1000 | overlong_4
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Dummy>
|
||||||
|
const std::array<std::uint8_t, 16> utf8_lookup4<Dummy>::byte_1_low =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
carry | overlong_3 | overlong_2 | overlong_4, carry | overlong_2, carry, carry,
|
||||||
|
carry | too_large, carry | too_large | too_large_1000, carry | too_large | too_large_1000, carry | too_large | too_large_1000,
|
||||||
|
carry | too_large | too_large_1000, carry | too_large | too_large_1000, carry | too_large | too_large_1000, carry | too_large | too_large_1000,
|
||||||
|
carry | too_large | too_large_1000, carry | too_large | too_large_1000 | surrogate, carry | too_large | too_large_1000, carry | too_large | too_large_1000
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Dummy>
|
||||||
|
const std::array<std::uint8_t, 16> utf8_lookup4<Dummy>::byte_2_high =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
too_short, too_short, too_short, too_short, too_short, too_short, too_short, too_short,
|
||||||
|
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | overlong_3 | too_large_1000 | overlong_4),
|
||||||
|
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | overlong_3 | too_large),
|
||||||
|
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | surrogate | too_large),
|
||||||
|
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | surrogate | too_large),
|
||||||
|
too_short, too_short, too_short, too_short
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief the run of plain string bytes and well-formed UTF-8 at p (a character
|
||||||
|
boundary), 16 bytes per step
|
||||||
|
|
||||||
|
The first quote, backslash, or control character is found with vector
|
||||||
|
compares, and the UTF-8 check of J. Keiser and D. Lemire covers the bytes up
|
||||||
|
to it. Returns true with @a block at that byte; returns false with @a block at
|
||||||
|
the start of the 16 bytes where the check found ill-formed UTF-8 or where
|
||||||
|
fewer than 16 bytes are left, for the caller to finish one sequence at a time.
|
||||||
|
Out of line, so that no constants of the check occupy registers in the
|
||||||
|
caller's loop. On x86-64, it is compiled for SSSE3 (see cpu_has_ssse3()).
|
||||||
|
*/
|
||||||
|
JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline bool vector_utf8_run(const unsigned char* p, const unsigned char* e, const unsigned char*& block_out) noexcept
|
||||||
|
{
|
||||||
|
using lookup = utf8_lookup4<>;
|
||||||
|
const unsigned char* block = p;
|
||||||
|
#if JSON_SCAN_NEON
|
||||||
|
const uint8x16_t t1h = vld1q_u8(lookup::byte_1_high.data());
|
||||||
|
const uint8x16_t t1l = vld1q_u8(lookup::byte_1_low.data());
|
||||||
|
const uint8x16_t t2h = vld1q_u8(lookup::byte_2_high.data());
|
||||||
|
uint8x16_t prev = vdupq_n_u8(0);
|
||||||
|
while (e - block >= 16)
|
||||||
|
{
|
||||||
|
const uint8x16_t in = vld1q_u8(block);
|
||||||
|
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))), vcltq_u8(in, vdupq_n_u8(0x20)));
|
||||||
|
const uint8x16_t prev1 = vextq_u8(prev, in, 15);
|
||||||
|
const uint8x16_t sc = vandq_u8(vandq_u8(vqtbl1q_u8(t1h, vshrq_n_u8(prev1, 4)), vqtbl1q_u8(t1l, vandq_u8(prev1, vdupq_n_u8(0x0F)))), vqtbl1q_u8(t2h, vshrq_n_u8(in, 4)));
|
||||||
|
const uint8x16_t must23 = vorrq_u8(vqsubq_u8(vextq_u8(prev, in, 14), vdupq_n_u8(0xE0 - 0x80)), vqsubq_u8(vextq_u8(prev, in, 13), vdupq_n_u8(0xF0 - 0x80)));
|
||||||
|
const uint8x16_t err = veorq_u8(vandq_u8(must23, vdupq_n_u8(0x80)), sc);
|
||||||
|
const std::uint64_t special_bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
||||||
|
const std::uint64_t err_bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(vtstq_u8(err, err)), 4)), 0);
|
||||||
|
if (special_bits != 0)
|
||||||
|
{
|
||||||
|
// errors up to the special byte count (an incomplete sequence
|
||||||
|
// before a quote shows at the quote); the bytes after it do not
|
||||||
|
const unsigned k = static_cast<unsigned>(count_trailing_zeros(special_bits)) >> 2u;
|
||||||
|
const std::uint64_t upto = k == 15 ? ~std::uint64_t{0} :
|
||||||
|
(std::uint64_t{1} << (4u * (k + 1u))) - 1u;
|
||||||
|
if ((err_bits & upto) == 0)
|
||||||
|
{
|
||||||
|
block_out = block + k;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (err_bits != 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prev = in;
|
||||||
|
block += 16;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// the same with SSSE3 (pshufb for the table lookups; nibbles from 16-bit
|
||||||
|
// shifts, as there are no byte shifts)
|
||||||
|
const __m128i t1h = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_1_high.data()))); // NOLINT(bugprone-casting-through-void)
|
||||||
|
const __m128i t1l = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_1_low.data()))); // NOLINT(bugprone-casting-through-void)
|
||||||
|
const __m128i t2h = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_2_high.data()))); // NOLINT(bugprone-casting-through-void)
|
||||||
|
const __m128i nibble = _mm_set1_epi8(0x0F);
|
||||||
|
const __m128i zero = _mm_setzero_si128();
|
||||||
|
__m128i prev = zero;
|
||||||
|
while (e - block >= 16)
|
||||||
|
{
|
||||||
|
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(block))); // 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_cmpeq_epi8(_mm_subs_epu8(in, _mm_set1_epi8(0x1F)), zero)); // in < 0x20
|
||||||
|
const __m128i prev1 = _mm_alignr_epi8(in, prev, 15);
|
||||||
|
const __m128i sc = _mm_and_si128(_mm_and_si128(_mm_shuffle_epi8(t1h, _mm_and_si128(_mm_srli_epi16(prev1, 4), nibble)),
|
||||||
|
_mm_shuffle_epi8(t1l, _mm_and_si128(prev1, nibble))),
|
||||||
|
_mm_shuffle_epi8(t2h, _mm_and_si128(_mm_srli_epi16(in, 4), nibble)));
|
||||||
|
const __m128i must23 = _mm_or_si128(_mm_subs_epu8(_mm_alignr_epi8(in, prev, 14), _mm_set1_epi8(0xE0 - 0x80)),
|
||||||
|
_mm_subs_epu8(_mm_alignr_epi8(in, prev, 13), _mm_set1_epi8(0xF0 - 0x80)));
|
||||||
|
const __m128i err = _mm_xor_si128(_mm_and_si128(must23, _mm_set1_epi8(static_cast<char>(-128))), sc);
|
||||||
|
const auto special_bits = static_cast<unsigned>(_mm_movemask_epi8(special));
|
||||||
|
const auto err_bits = ~static_cast<unsigned>(_mm_movemask_epi8(_mm_cmpeq_epi8(err, zero))) & 0xFFFFu;
|
||||||
|
if (special_bits != 0)
|
||||||
|
{
|
||||||
|
const auto k = static_cast<unsigned>(count_trailing_zeros(static_cast<std::uint64_t>(special_bits)));
|
||||||
|
if ((err_bits & ((2u << k) - 1u)) == 0)
|
||||||
|
{
|
||||||
|
block_out = block + k;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (err_bits != 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prev = in;
|
||||||
|
block += 16;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
block_out = block;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <cstring> // memcpy
|
#include <cstring> // memcpy
|
||||||
|
|
||||||
#include <nlohmann/detail/bit_ops.hpp>
|
#include <nlohmann/detail/bit_ops.hpp>
|
||||||
|
#include <nlohmann/detail/input/simd_scan.hpp>
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
|
||||||
// Optional SIMD backend for bulk UTF-8 validation. This is an opt-in external
|
// Optional SIMD backend for bulk UTF-8 validation. This is an opt-in external
|
||||||
@@ -34,7 +35,7 @@
|
|||||||
// This file contains the byte-level string-scanning helpers used by the lexer's
|
// This file contains the byte-level string-scanning helpers used by the lexer's
|
||||||
// contiguous fast path. They operate purely on raw bytes (no dependency on the
|
// contiguous fast path. They operate purely on raw bytes (no dependency on the
|
||||||
// lexer's template parameters) so they are free functions, keeping the lexer
|
// lexer's template parameters) so they are free functions, keeping the lexer
|
||||||
// itself focused on the state machine; see lexer::scan_string_bulk().
|
// itself focused on the state machine; see lexer::get_after_string_run().
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -66,7 +67,7 @@ inline std::uint64_t swar_string_special(std::uint64_t v) noexcept
|
|||||||
|
|
||||||
// return the index of the first is_string_special() byte in [data, data+n), or
|
// return the index of the first is_string_special() byte in [data, data+n), or
|
||||||
// n if every byte is ordinary; scans 8 bytes at a time
|
// n if every byte is ordinary; scans 8 bytes at a time
|
||||||
inline std::size_t find_string_special(const unsigned char* data, std::size_t n) noexcept
|
JSON_HEDLEY_ALWAYS_INLINE std::size_t find_string_special(const unsigned char* data, std::size_t n) noexcept
|
||||||
{
|
{
|
||||||
std::size_t i = 0;
|
std::size_t i = 0;
|
||||||
for (; i + 8 <= n; i += 8)
|
for (; i + 8 <= n; i += 8)
|
||||||
@@ -95,7 +96,7 @@ inline std::size_t find_string_special(const unsigned char* data, std::size_t n)
|
|||||||
// printable ASCII except '"' and '\\' - is emitted unchanged. Note this differs
|
// printable ASCII except '"' and '\\' - is emitted unchanged. Note this differs
|
||||||
// from is_string_special() only in that 0x7F is also a stop (it is escaped as
|
// from is_string_special() only in that 0x7F is also a stop (it is escaped as
|
||||||
// \u007f under ensure_ascii).
|
// \u007f under ensure_ascii).
|
||||||
inline bool is_ascii_copyable(unsigned char c) noexcept
|
constexpr bool is_ascii_copyable(unsigned char c) noexcept
|
||||||
{
|
{
|
||||||
return c >= 0x20u && c < 0x7Fu && c != '"' && c != '\\';
|
return c >= 0x20u && c < 0x7Fu && c != '"' && c != '\\';
|
||||||
}
|
}
|
||||||
@@ -235,35 +236,143 @@ inline std::size_t valid_utf8_prefix(const unsigned char* data, std::size_t n) n
|
|||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scalar (C++11) computation of the bulk run length: the number of leading
|
#if JSON_SCAN_VECTOR_UTF8
|
||||||
// bytes in [data, data+n) that are ordinary ASCII or complete well-formed UTF-8
|
/// the end of scan_string_vector from block, where the vector loop stopped
|
||||||
|
/// (ill-formed UTF-8, or fewer than 16 bytes left): one byte or sequence at a
|
||||||
|
/// time, from the start of a sequence that crosses into the block
|
||||||
|
inline const unsigned char* scan_string_finish(const unsigned char* p, const unsigned char* block, const unsigned char* e) noexcept
|
||||||
|
{
|
||||||
|
for (int i = 1; i <= 3 && block - i >= p; ++i)
|
||||||
|
{
|
||||||
|
const unsigned char c = block[-i];
|
||||||
|
if (c < 0x80)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (c >= 0xC0)
|
||||||
|
{
|
||||||
|
const int len = 2 + static_cast<int>(c >= 0xE0) + static_cast<int>(c >= 0xF0);
|
||||||
|
if (len > i)
|
||||||
|
{
|
||||||
|
block -= i;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (p = block; p != e;)
|
||||||
|
{
|
||||||
|
if (*p < 0x80)
|
||||||
|
{
|
||||||
|
if (is_string_special(*p))
|
||||||
|
{
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
++p;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::size_t n = validate_one_utf8(p, static_cast<std::size_t>(e - p));
|
||||||
|
if (n == 0)
|
||||||
|
{
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
p += n;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the rest of a string from p (a character boundary), with the vector UTF-8
|
||||||
|
/// check and the scalar check for the end of the block where it stopped
|
||||||
|
inline const unsigned char* scan_string_vector(const unsigned char* p, const unsigned char* e) noexcept
|
||||||
|
{
|
||||||
|
const unsigned char* block = p;
|
||||||
|
if (vector_utf8_run(p, e, block))
|
||||||
|
{
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
return scan_string_finish(p, block, e);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_SCAN_VECTOR
|
||||||
|
/// the end of the run of plain string characters at @a p: the first quote,
|
||||||
|
/// backslash, control character, or non-ASCII byte, or @a e. The first words
|
||||||
|
/// are checked with SWAR (one; four with NEON, where the vector mask costs
|
||||||
|
/// more), as most keys and short values end there; longer runs continue 16
|
||||||
|
/// bytes at a time.
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE const unsigned char* plain_run_end(const unsigned char* p, const unsigned char* e) noexcept
|
||||||
|
{
|
||||||
|
constexpr int prefix_words = JSON_SCAN_NEON ? 4 : 1;
|
||||||
|
for (int words = prefix_words; words > 0 && e - p >= 8; --words)
|
||||||
|
{
|
||||||
|
const std::uint64_t special = swar_string_special(read_eight_bytes(p));
|
||||||
|
if (special != 0)
|
||||||
|
{
|
||||||
|
return p + (static_cast<std::size_t>(count_trailing_zeros(special)) / 8);
|
||||||
|
}
|
||||||
|
p += 8;
|
||||||
|
}
|
||||||
|
// the vector loop only for 16 bytes or more: the serializer passes a
|
||||||
|
// string without its closing quote, so most of its runs end at e, and a
|
||||||
|
// call of the out-of-line NEON loop for a shorter rest made dump() up to
|
||||||
|
// 10% slower
|
||||||
|
if (e - p >= 16)
|
||||||
|
{
|
||||||
|
p = vector_plain_run_long(p, e);
|
||||||
|
if (e - p >= 16)
|
||||||
|
{
|
||||||
|
return p; // the vector loop stopped at a special byte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p + find_string_special(p, static_cast<std::size_t>(e - p));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Computation of the bulk run length: the number of leading bytes in
|
||||||
|
// [data, data+n) that are ordinary ASCII or complete well-formed UTF-8
|
||||||
// sequences, stopping before the first byte that needs individual handling (the
|
// sequences, stopping before the first byte that needs individual handling (the
|
||||||
// closing quote, an escape, a control character, or an ill-formed/truncated
|
// closing quote, an escape, a control character, or an ill-formed/truncated
|
||||||
// sequence). ASCII is skipped 8 bytes at a time.
|
// sequence). ASCII is skipped 16 bytes at a time with SSE2 and 8 bytes at a
|
||||||
inline std::size_t scalar_string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
// time otherwise; runs of non-ASCII text use the vector UTF-8 check where
|
||||||
|
// available.
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE std::size_t scalar_string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
||||||
{
|
{
|
||||||
std::size_t pos = 0;
|
const unsigned char* const e = data + n;
|
||||||
while (pos < n)
|
const unsigned char* p = data;
|
||||||
|
while (p != e)
|
||||||
{
|
{
|
||||||
pos += find_string_special(data + pos, n - pos);
|
#if JSON_SCAN_VECTOR
|
||||||
if (pos >= n || data[pos] < 0x80u)
|
p = plain_run_end(p, e);
|
||||||
|
#else
|
||||||
|
p += find_string_special(p, static_cast<std::size_t>(e - p));
|
||||||
|
#endif
|
||||||
|
if (p == e || *p < 0x80u)
|
||||||
{
|
{
|
||||||
break; // end of buffer, or a quote/escape/control byte
|
break; // end of buffer, or a quote/escape/control byte
|
||||||
}
|
}
|
||||||
|
#if JSON_SCAN_VECTOR_UTF8
|
||||||
|
#if JSON_SCAN_SSSE3_DISPATCH
|
||||||
|
if (JSON_HEDLEY_LIKELY(cpu_has_ssse3()))
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
return static_cast<std::size_t>(scan_string_vector(p, e) - data);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if !JSON_SCAN_VECTOR_UTF8 || JSON_SCAN_SSSE3_DISPATCH
|
||||||
// a run of multi-byte sequences (e.g. CJK text) is validated sequence
|
// a run of multi-byte sequences (e.g. CJK text) is validated sequence
|
||||||
// by sequence without searching for the next special byte in between
|
// by sequence without searching for the next special byte in between
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
const std::size_t seq = validate_one_utf8(data + pos, n - pos);
|
const std::size_t seq = validate_one_utf8(p, static_cast<std::size_t>(e - p));
|
||||||
if (seq == 0)
|
if (seq == 0)
|
||||||
{
|
{
|
||||||
return pos; // ill-formed or truncated: let the byte path diagnose it
|
return static_cast<std::size_t>(p - data); // ill-formed or truncated: let the byte path diagnose it
|
||||||
}
|
}
|
||||||
pos += seq;
|
p += seq;
|
||||||
}
|
}
|
||||||
while (pos < n && data[pos] >= 0x80u);
|
while (p != e && *p >= 0x80u);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
return pos;
|
return static_cast<std::size_t>(p - data);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
||||||
@@ -304,7 +413,7 @@ inline std::size_t find_string_delimiter(const unsigned char* data, std::size_t
|
|||||||
// next delimiter is validated in one shot by simdutf; on the rare failure the
|
// next delimiter is validated in one shot by simdutf; on the rare failure the
|
||||||
// scalar helper recomputes the exact valid prefix so the byte path still
|
// scalar helper recomputes the exact valid prefix so the byte path still
|
||||||
// produces the precise diagnostic. Without it, the pure scalar path is used.
|
// produces the precise diagnostic. Without it, the pure scalar path is used.
|
||||||
inline std::size_t string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
JSON_HEDLEY_ALWAYS_INLINE std::size_t string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
||||||
{
|
{
|
||||||
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
||||||
const std::size_t run = find_string_delimiter(data, n);
|
const std::size_t run = find_string_delimiter(data, n);
|
||||||
@@ -316,6 +425,42 @@ inline std::size_t string_bulk_run(const unsigned char* data, std::size_t n) noe
|
|||||||
return scalar_string_bulk_run(data, n);
|
return scalar_string_bulk_run(data, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if JSON_SCAN_NEON && !(defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17))
|
||||||
|
/// scalar_string_bulk_run() from @a start on, out of line (see
|
||||||
|
/// lexer_string_bulk_run())
|
||||||
|
JSON_HEDLEY_NEVER_INLINE inline std::size_t string_bulk_run_from(const unsigned char* data, std::size_t start, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
return start + scalar_string_bulk_run(data + start, n - start);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// string_bulk_run() for the lexer. With NEON, only the first four words are
|
||||||
|
// checked inline (most keys and short values end there), and longer runs and
|
||||||
|
// non-ASCII text continue out of line. The lexer's scan function runs once per
|
||||||
|
// token, and the vector code inlined into it needs more registers, which every
|
||||||
|
// token pays for: 5% of json::accept() on jeopardy.json with Apple Clang on
|
||||||
|
// Apple M1, even though no string of that file reaches the vector code. The
|
||||||
|
// serializer keeps string_bulk_run() inline, which is faster there.
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE std::size_t lexer_string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
#if JSON_SCAN_NEON && !(defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17))
|
||||||
|
std::size_t i = 0;
|
||||||
|
for (int words = 4; words > 0 && n - i >= 8; --words)
|
||||||
|
{
|
||||||
|
const std::uint64_t special = swar_string_special(read_eight_bytes(data + i));
|
||||||
|
if (special != 0)
|
||||||
|
{
|
||||||
|
const std::size_t pos = i + (static_cast<std::size_t>(count_trailing_zeros(special)) / 8);
|
||||||
|
return data[pos] < 0x80u ? pos : string_bulk_run_from(data, pos, n);
|
||||||
|
}
|
||||||
|
i += 8;
|
||||||
|
}
|
||||||
|
return string_bulk_run_from(data, i, n);
|
||||||
|
#else
|
||||||
|
return string_bulk_run(data, n);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// Decode the 4 hex digits at [data, data+4) - the digits following a `\u`
|
// Decode the 4 hex digits at [data, data+4) - the digits following a `\u`
|
||||||
// escape - into a codepoint 0x0000..0xFFFF via one table lookup per byte
|
// escape - into a codepoint 0x0000..0xFFFF via one table lookup per byte
|
||||||
// (after yyjson's read_hex_u16), or return -1 if any of the 4 bytes is not a
|
// (after yyjson's read_hex_u16), or return -1 if any of the 4 bytes is not a
|
||||||
|
|||||||
@@ -108,33 +108,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (m_object->is_object())
|
||||||
{
|
|
||||||
case value_t::object:
|
|
||||||
{
|
{
|
||||||
m_it.object_iterator = typename object_t::iterator();
|
m_it.object_iterator = typename object_t::iterator();
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
else if (m_object->is_array())
|
||||||
case value_t::array:
|
|
||||||
{
|
{
|
||||||
m_it.array_iterator = typename array_t::iterator();
|
m_it.array_iterator = typename array_t::iterator();
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
case value_t::null:
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
{
|
{
|
||||||
|
// primitive (is_primitive(), including discarded)
|
||||||
m_it.primitive_iterator = primitive_iterator_t();
|
m_it.primitive_iterator = primitive_iterator_t();
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,33 +234,28 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (m_object->is_object())
|
||||||
{
|
|
||||||
case value_t::object:
|
|
||||||
{
|
{
|
||||||
m_it.object_iterator = m_object->m_data.m_value.object->end();
|
m_it.object_iterator = m_object->m_data.m_value.object->end();
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
else if (m_object->is_array())
|
||||||
case value_t::array:
|
|
||||||
{
|
{
|
||||||
m_it.array_iterator = m_object->m_data.m_value.array->end();
|
m_it.array_iterator = m_object->m_data.m_value.array->end();
|
||||||
break;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// primitive (is_primitive(), including discarded)
|
||||||
|
m_it.primitive_iterator.set_end();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::null:
|
/// @throw invalid_iterator.212 if @a other points to another container
|
||||||
case value_t::string:
|
template<typename IterImpl>
|
||||||
case value_t::boolean:
|
JSON_HEDLEY_ALWAYS_INLINE void check_same_container(const IterImpl& other) const
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
{
|
{
|
||||||
m_it.primitive_iterator.set_end();
|
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
|
||||||
break;
|
{
|
||||||
}
|
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,10 +404,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
bool operator==(const IterImpl& other) const
|
bool operator==(const IterImpl& other) const
|
||||||
{
|
{
|
||||||
// if objects are not the same, the comparison is undefined
|
// if objects are not the same, the comparison is undefined
|
||||||
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
|
check_same_container(other);
|
||||||
{
|
|
||||||
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
|
|
||||||
}
|
|
||||||
|
|
||||||
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
|
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
|
||||||
if (m_object == nullptr)
|
if (m_object == nullptr)
|
||||||
@@ -466,10 +443,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
bool operator<(const iter_impl& other) const
|
bool operator<(const iter_impl& other) const
|
||||||
{
|
{
|
||||||
// if objects are not the same, the comparison is undefined
|
// if objects are not the same, the comparison is undefined
|
||||||
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
|
check_same_container(other);
|
||||||
{
|
|
||||||
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
|
|
||||||
}
|
|
||||||
|
|
||||||
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
|
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
|
||||||
if (m_object == nullptr)
|
if (m_object == nullptr)
|
||||||
@@ -478,25 +452,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (m_object->is_object())
|
||||||
{
|
{
|
||||||
case value_t::object:
|
|
||||||
throw_literal<invalid_iterator>(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object);
|
throw_literal<invalid_iterator>(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object);
|
||||||
|
|
||||||
case value_t::array:
|
|
||||||
return (m_it.array_iterator < other.m_it.array_iterator);
|
|
||||||
|
|
||||||
case value_t::null:
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
return (m_it.primitive_iterator < other.m_it.primitive_iterator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_object->is_array())
|
||||||
|
{
|
||||||
|
return (m_it.array_iterator < other.m_it.array_iterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// primitive (is_primitive(), including discarded)
|
||||||
|
return (m_it.primitive_iterator < other.m_it.primitive_iterator);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -534,30 +501,19 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (m_object->is_object())
|
||||||
{
|
{
|
||||||
case value_t::object:
|
|
||||||
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
|
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
|
||||||
|
}
|
||||||
|
|
||||||
case value_t::array:
|
if (m_object->is_array())
|
||||||
{
|
{
|
||||||
std::advance(m_it.array_iterator, i);
|
std::advance(m_it.array_iterator, i);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
case value_t::null:
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
{
|
{
|
||||||
|
// primitive (is_primitive(), including discarded)
|
||||||
m_it.primitive_iterator += i;
|
m_it.primitive_iterator += i;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
@@ -613,25 +569,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (m_object->is_object())
|
||||||
{
|
{
|
||||||
case value_t::object:
|
|
||||||
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
|
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
|
||||||
|
|
||||||
case value_t::array:
|
|
||||||
return m_it.array_iterator - other.m_it.array_iterator;
|
|
||||||
|
|
||||||
case value_t::null:
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
return m_it.primitive_iterator - other.m_it.primitive_iterator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_object->is_array())
|
||||||
|
{
|
||||||
|
return m_it.array_iterator - other.m_it.array_iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
// primitive (is_primitive(), including discarded)
|
||||||
|
return m_it.primitive_iterator - other.m_it.primitive_iterator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|||||||
@@ -109,11 +109,9 @@ template<typename IteratorType> class iteration_proxy_value
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(anchor.m_object != nullptr);
|
JSON_ASSERT(anchor.m_object != nullptr);
|
||||||
|
|
||||||
switch (anchor.m_object->type())
|
if (anchor.m_object->is_array())
|
||||||
{
|
{
|
||||||
// use integer array index as key
|
// use integer array index as key
|
||||||
case value_t::array:
|
|
||||||
{
|
|
||||||
if (array_index != array_index_last)
|
if (array_index != array_index_last)
|
||||||
{
|
{
|
||||||
int_to_string( array_index_str, array_index );
|
int_to_string( array_index_str, array_index );
|
||||||
@@ -122,23 +120,15 @@ template<typename IteratorType> class iteration_proxy_value
|
|||||||
return array_index_str;
|
return array_index_str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (anchor.m_object->is_object())
|
||||||
|
{
|
||||||
// use key from the object
|
// use key from the object
|
||||||
case value_t::object:
|
|
||||||
return anchor.key();
|
return anchor.key();
|
||||||
|
}
|
||||||
|
|
||||||
// use an empty key for all primitive types
|
// use an empty key for all primitive types
|
||||||
case value_t::null:
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
return empty_str;
|
return empty_str;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// return value of the iterator
|
/// return value of the iterator
|
||||||
typename IteratorType::reference value() const
|
typename IteratorType::reference value() const
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -187,6 +187,22 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// the byte order of the target where the compiler tells it (std::endian is
|
||||||
|
// C++20 and cannot select code in #if): JSON_LITTLE_ENDIAN_TARGET or
|
||||||
|
// JSON_BIG_ENDIAN_TARGET is 1, or both are 0 where it is unknown (code then
|
||||||
|
// handles the bytes one at a time); MSVC and x86 targets are little-endian
|
||||||
|
#if defined(_MSC_VER) || defined(__x86_64__) || defined(__i386__) \
|
||||||
|
|| (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
||||||
|
#define JSON_LITTLE_ENDIAN_TARGET 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#define JSON_BIG_ENDIAN_TARGET 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||||
|
#define JSON_LITTLE_ENDIAN_TARGET 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#define JSON_BIG_ENDIAN_TARGET 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#else
|
||||||
|
#define JSON_LITTLE_ENDIAN_TARGET 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#define JSON_BIG_ENDIAN_TARGET 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
#ifdef JSON_HAS_CPP_17
|
||||||
#define JSON_INLINE_VARIABLE inline
|
#define JSON_INLINE_VARIABLE inline
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -24,6 +24,15 @@
|
|||||||
#undef JSON_UNLOCKED_FILE_IO
|
#undef JSON_UNLOCKED_FILE_IO
|
||||||
#undef JSON_DTOA_SSE2
|
#undef JSON_DTOA_SSE2
|
||||||
#undef JSON_DTOA_NEON
|
#undef JSON_DTOA_NEON
|
||||||
|
#undef JSON_LITTLE_ENDIAN_TARGET
|
||||||
|
#undef JSON_BIG_ENDIAN_TARGET
|
||||||
|
#undef JSON_SCAN_NEON
|
||||||
|
#undef JSON_SCAN_SSE2
|
||||||
|
#undef JSON_SCAN_SSSE3
|
||||||
|
#undef JSON_SCAN_SSSE3_DISPATCH
|
||||||
|
#undef JSON_SCAN_SSSE3_TARGET
|
||||||
|
#undef JSON_SCAN_VECTOR
|
||||||
|
#undef JSON_SCAN_VECTOR_UTF8
|
||||||
|
|
||||||
#ifndef JSON_TEST_KEEP_MACROS
|
#ifndef JSON_TEST_KEEP_MACROS
|
||||||
#undef JSON_CATCH
|
#undef JSON_CATCH
|
||||||
|
|||||||
@@ -10,17 +10,26 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
|
#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_arithmetic, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
|
||||||
#include <utility> // index_sequence, make_index_sequence, index_sequence_for
|
#include <utility> // index_sequence, make_index_sequence, index_sequence_for
|
||||||
|
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
#include <algorithm> // clamp
|
||||||
|
#endif
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
{
|
{
|
||||||
|
|
||||||
template<typename T>
|
#ifdef __cpp_lib_remove_cvref
|
||||||
using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
|
template<typename T>
|
||||||
|
using uncvref_t = std::remove_cvref_t<T>;
|
||||||
|
#else
|
||||||
|
template<typename T>
|
||||||
|
using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_14
|
#ifdef JSON_HAS_CPP_14
|
||||||
|
|
||||||
@@ -160,5 +169,19 @@ struct static_const
|
|||||||
constexpr T static_const<T>::value;
|
constexpr T static_const<T>::value;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// @a v bounded to [@a lo, @a hi] (requires !(hi < lo)); like std::clamp,
|
||||||
|
/// but by value, so that static constexpr members can be passed without an
|
||||||
|
/// out-of-line definition before C++17
|
||||||
|
template<typename T, enable_if_t<std::is_arithmetic<T>::value, int> = 0>
|
||||||
|
constexpr T clamp(T v, T lo, T hi) noexcept
|
||||||
|
{
|
||||||
|
#ifdef __cpp_lib_clamp
|
||||||
|
return std::clamp(v, lo, hi);
|
||||||
|
#else
|
||||||
|
// NOLINTNEXTLINE(readability-avoid-nested-conditional-operator): a constant expression of C++11
|
||||||
|
return v < lo ? lo : (hi < v ? hi : v);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
@@ -313,6 +313,14 @@ struct char_traits<std::byte> : std::char_traits<char>
|
|||||||
// is_ functions //
|
// is_ functions //
|
||||||
///////////////////
|
///////////////////
|
||||||
|
|
||||||
|
#ifdef __cpp_lib_logical_traits
|
||||||
|
|
||||||
|
using std::conjunction;
|
||||||
|
using std::disjunction;
|
||||||
|
using std::negation;
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
// https://en.cppreference.com/w/cpp/types/conjunction
|
// https://en.cppreference.com/w/cpp/types/conjunction
|
||||||
template<class...> struct conjunction : std::true_type { };
|
template<class...> struct conjunction : std::true_type { };
|
||||||
template<class B> struct conjunction<B> : B { };
|
template<class B> struct conjunction<B> : B { };
|
||||||
@@ -330,6 +338,8 @@ struct disjunction<B, Bn...>
|
|||||||
// https://en.cppreference.com/w/cpp/types/negation
|
// https://en.cppreference.com/w/cpp/types/negation
|
||||||
template<class B> struct negation : std::integral_constant < bool, !B::value > { };
|
template<class B> struct negation : std::integral_constant < bool, !B::value > { };
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
// Reimplementation of is_constructible and is_default_constructible, due to them being broken for
|
// Reimplementation of is_constructible and is_default_constructible, due to them being broken for
|
||||||
// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367).
|
// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367).
|
||||||
// This causes compile errors in e.g., Clang 3.5 or GCC 4.9.
|
// This causes compile errors in e.g., Clang 3.5 or GCC 4.9.
|
||||||
@@ -1060,8 +1070,12 @@ constexpr bool value_in_range_of(T val)
|
|||||||
return value_in_range_of_impl1<OfType, T>::test(val);
|
return value_in_range_of_impl1<OfType, T>::test(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<bool Value>
|
#ifdef __cpp_lib_bool_constant
|
||||||
using bool_constant = std::integral_constant<bool, Value>;
|
using std::bool_constant;
|
||||||
|
#else
|
||||||
|
template<bool Value>
|
||||||
|
using bool_constant = std::integral_constant<bool, Value>;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Stands in for number_unsigned_t in overload sets if it is the same type as
|
// Stands in for number_unsigned_t in overload sets if it is the same type as
|
||||||
// number_integer_t, so that the unsigned overloads do not collide with the
|
// number_integer_t, so that the unsigned overloads do not collide with the
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -55,6 +55,66 @@ using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
|
|||||||
/// output_vector_sink also appends writes of at least this size with insert()
|
/// output_vector_sink also appends writes of at least this size with insert()
|
||||||
constexpr std::size_t max_reserve_size = 256;
|
constexpr std::size_t max_reserve_size = 256;
|
||||||
|
|
||||||
|
/// @brief copy @a length characters, at most 32, without a call of memcpy()
|
||||||
|
///
|
||||||
|
/// Short strings are the typical payload of a binary value, and a call of
|
||||||
|
/// memcpy() costs more than copying them. A copy of 16 to 32 characters is two
|
||||||
|
/// possibly overlapping 16-byte copies, one of 8 to 15 two 8-byte copies, and
|
||||||
|
/// so on down to a single character; each is a fixed-size load and store.
|
||||||
|
/// Nothing outside of [src, src + length) is read or written.
|
||||||
|
template<typename CharType>
|
||||||
|
inline void copy_short(CharType* dst, const CharType* src, const std::size_t length, std::true_type /*is_byte*/) noexcept
|
||||||
|
{
|
||||||
|
JSON_ASSERT(length <= 32);
|
||||||
|
if (length >= 16)
|
||||||
|
{
|
||||||
|
std::array<char, 16> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::array<char, 16> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::memcpy(first.data(), src, 16);
|
||||||
|
std::memcpy(last.data(), src + (length - 16), 16);
|
||||||
|
std::memcpy(dst, first.data(), 16);
|
||||||
|
std::memcpy(dst + (length - 16), last.data(), 16);
|
||||||
|
}
|
||||||
|
else if (length >= 8)
|
||||||
|
{
|
||||||
|
std::array<char, 8> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::array<char, 8> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::memcpy(first.data(), src, 8);
|
||||||
|
std::memcpy(last.data(), src + (length - 8), 8);
|
||||||
|
std::memcpy(dst, first.data(), 8);
|
||||||
|
std::memcpy(dst + (length - 8), last.data(), 8);
|
||||||
|
}
|
||||||
|
else if (length >= 4)
|
||||||
|
{
|
||||||
|
std::array<char, 4> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::array<char, 4> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::memcpy(first.data(), src, 4);
|
||||||
|
std::memcpy(last.data(), src + (length - 4), 4);
|
||||||
|
std::memcpy(dst, first.data(), 4);
|
||||||
|
std::memcpy(dst + (length - 4), last.data(), 4);
|
||||||
|
}
|
||||||
|
else if (length != 0)
|
||||||
|
{
|
||||||
|
// 1 to 3: the first, the middle, and the last character
|
||||||
|
const CharType a = src[0];
|
||||||
|
const CharType b = src[length >> 1];
|
||||||
|
const CharType c = src[length - 1];
|
||||||
|
dst[0] = a;
|
||||||
|
dst[length >> 1] = b;
|
||||||
|
dst[length - 1] = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief copy @a length characters, of a character type wider than a byte
|
||||||
|
template<typename CharType>
|
||||||
|
inline void copy_short(CharType* dst, const CharType* src, const std::size_t length, std::false_type /*is_byte*/) noexcept
|
||||||
|
{
|
||||||
|
std::memcpy(dst, src, length * sizeof(CharType));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the number of characters an empty vector is grown to by output_vector_sink
|
||||||
|
constexpr std::size_t min_vector_sink_capacity = 64;
|
||||||
|
|
||||||
/// @brief non-virtual output sink writing into a std::vector
|
/// @brief non-virtual output sink writing into a std::vector
|
||||||
///
|
///
|
||||||
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
||||||
@@ -78,7 +138,7 @@ class output_vector_sink
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
|
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||||
: v(&vec), position(vec.size())
|
: v(&vec), cursor(vec.data() + vec.size()), limit(cursor)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
output_vector_sink(const output_vector_sink&) = delete;
|
output_vector_sink(const output_vector_sink&) = delete;
|
||||||
@@ -86,7 +146,7 @@ class output_vector_sink
|
|||||||
output_vector_sink& operator=(output_vector_sink&&) = delete;
|
output_vector_sink& operator=(output_vector_sink&&) = delete;
|
||||||
|
|
||||||
output_vector_sink(output_vector_sink&& other) noexcept
|
output_vector_sink(output_vector_sink&& other) noexcept
|
||||||
: v(other.v), position(other.position)
|
: v(other.v), cursor(other.cursor), limit(other.limit)
|
||||||
{
|
{
|
||||||
other.v = nullptr;
|
other.v = nullptr;
|
||||||
}
|
}
|
||||||
@@ -95,7 +155,7 @@ class output_vector_sink
|
|||||||
{
|
{
|
||||||
if (v != nullptr)
|
if (v != nullptr)
|
||||||
{
|
{
|
||||||
v->resize(position);
|
v->resize(position());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,11 +164,11 @@ class output_vector_sink
|
|||||||
JSON_HEDLEY_ALWAYS_INLINE
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
void write_character(CharType c)
|
void write_character(CharType c)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(position == v->size()))
|
if (JSON_HEDLEY_UNLIKELY(cursor == limit))
|
||||||
{
|
{
|
||||||
grow(1);
|
grow(1);
|
||||||
}
|
}
|
||||||
(*v)[position++] = c;
|
*cursor++ = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
||||||
@@ -117,7 +177,7 @@ class output_vector_sink
|
|||||||
JSON_HEDLEY_ALWAYS_INLINE
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
void write_characters(const CharType* s, std::size_t length)
|
void write_characters(const CharType* s, std::size_t length)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
if (JSON_HEDLEY_UNLIKELY(space() < length))
|
||||||
{
|
{
|
||||||
if (length >= max_reserve_size)
|
if (length >= max_reserve_size)
|
||||||
{
|
{
|
||||||
@@ -126,10 +186,15 @@ class output_vector_sink
|
|||||||
}
|
}
|
||||||
grow(length);
|
grow(length);
|
||||||
}
|
}
|
||||||
if (length != 0)
|
if (length <= 32)
|
||||||
{
|
{
|
||||||
std::memcpy(v->data() + position, s, length * sizeof(CharType));
|
copy_short(cursor, s, length, std::integral_constant<bool, sizeof(CharType) == 1>());
|
||||||
position += length;
|
cursor += length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::memcpy(cursor, s, length * sizeof(CharType));
|
||||||
|
cursor += length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,21 +209,37 @@ class output_vector_sink
|
|||||||
CharType* reserve(const std::size_t length)
|
CharType* reserve(const std::size_t length)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(length <= max_reserve_size);
|
JSON_ASSERT(length <= max_reserve_size);
|
||||||
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
if (JSON_HEDLEY_UNLIKELY(space() < length))
|
||||||
{
|
{
|
||||||
grow(length);
|
grow(length);
|
||||||
}
|
}
|
||||||
return v->data() + position;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// keep the first @a length characters written to the space from reserve()
|
/// keep the first @a length characters written to the space from reserve()
|
||||||
JSON_HEDLEY_ALWAYS_INLINE
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
void commit(const std::size_t length) noexcept
|
void commit(const std::size_t length) noexcept
|
||||||
{
|
{
|
||||||
position += length;
|
cursor += length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// nothing to pass on: the characters are in the vector (see output_adapter_sink)
|
||||||
|
void flush() noexcept
|
||||||
|
{}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// number of characters of *v written so far
|
||||||
|
std::size_t position() const noexcept
|
||||||
|
{
|
||||||
|
return static_cast<std::size_t>(cursor - v->data());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// number of characters that fit the vector behind the write position
|
||||||
|
std::size_t space() const noexcept
|
||||||
|
{
|
||||||
|
return static_cast<std::size_t>(limit - cursor);
|
||||||
|
}
|
||||||
|
|
||||||
/// make room for at least @a length more characters after the position;
|
/// make room for at least @a length more characters after the position;
|
||||||
/// kept out of line so that the write functions stay small enough for
|
/// kept out of line so that the write functions stay small enough for
|
||||||
/// GCC to inline them into the writer
|
/// GCC to inline them into the writer
|
||||||
@@ -166,12 +247,15 @@ class output_vector_sink
|
|||||||
void grow(const std::size_t length)
|
void grow(const std::size_t length)
|
||||||
{
|
{
|
||||||
// use the capacity the vector already has, else at least double it;
|
// use the capacity the vector already has, else at least double it;
|
||||||
// never less than 64 characters, so that a small value does not
|
// never less than min_vector_sink_capacity, so that a small value does not
|
||||||
// reallocate for every few bytes
|
// reallocate for every few bytes
|
||||||
const std::size_t needed = position + length;
|
const std::size_t written = position();
|
||||||
v->resize((std::max)(std::size_t{64}, needed <= v->capacity()
|
const std::size_t needed = written + length;
|
||||||
|
v->resize((std::max)(min_vector_sink_capacity, needed <= v->capacity()
|
||||||
? v->capacity()
|
? v->capacity()
|
||||||
: (std::max)(needed, v->capacity() * 2)));
|
: (std::max)(needed, v->capacity() * 2)));
|
||||||
|
cursor = v->data() + written;
|
||||||
|
limit = v->data() + v->size();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// append @a length (at least max_reserve_size) characters that do not fit
|
/// append @a length (at least max_reserve_size) characters that do not fit
|
||||||
@@ -181,20 +265,24 @@ class output_vector_sink
|
|||||||
JSON_HEDLEY_NEVER_INLINE
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
void append(const CharType* s, const std::size_t length)
|
void append(const CharType* s, const std::size_t length)
|
||||||
{
|
{
|
||||||
v->resize(position);
|
const std::size_t written = position();
|
||||||
if (v->capacity() - position < length)
|
v->resize(written);
|
||||||
|
if (v->capacity() - written < length)
|
||||||
{
|
{
|
||||||
// grow geometrically, with room for the writes that follow, so
|
// grow geometrically, with room for the writes that follow, so
|
||||||
// that the next grow() need not reallocate right away
|
// that the next grow() need not reallocate right away
|
||||||
v->reserve((std::max)(v->capacity() * 2, position + length + max_reserve_size));
|
v->reserve((std::max)(v->capacity() * 2, written + length + max_reserve_size));
|
||||||
}
|
}
|
||||||
v->insert(v->end(), s, s + length);
|
v->insert(v->end(), s, s + length);
|
||||||
position = v->size();
|
cursor = v->data() + v->size();
|
||||||
|
limit = cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<CharType, AllocatorType>* v;
|
std::vector<CharType, AllocatorType>* v;
|
||||||
/// number of characters of *v written so far
|
/// the write position: in *v, at the number of characters written so far
|
||||||
std::size_t position;
|
CharType* cursor;
|
||||||
|
/// the end of *v
|
||||||
|
CharType* limit;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// output adapter for byte vectors
|
/// output adapter for byte vectors
|
||||||
@@ -277,9 +365,17 @@ class output_string_adapter : public output_adapter_protocol<CharType>
|
|||||||
///
|
///
|
||||||
/// Wraps the polymorphic output_adapter_t so the same binary_writer template can
|
/// Wraps the polymorphic output_adapter_t so the same binary_writer template can
|
||||||
/// also target arbitrary adapters (output streams, strings, user-provided
|
/// also target arbitrary adapters (output streams, strings, user-provided
|
||||||
/// adapters) via the `output_adapter`-based overloads. Each write still goes
|
/// adapters) via the `output_adapter`-based overloads.
|
||||||
/// through one virtual call, exactly as before; only the concrete sinks above
|
///
|
||||||
/// avoid it.
|
/// A write through the adapter is a virtual call, and for a stream a call with
|
||||||
|
/// a sentry on top, which costs more than encoding the few bytes of most values.
|
||||||
|
/// The sink therefore collects what is written in a buffer and passes it on in
|
||||||
|
/// one piece when it is full, a write is too large for it, or @ref flush is
|
||||||
|
/// called. Everything written reaches the adapter in the same order and with
|
||||||
|
/// the same bytes; the destructor passes on what is left, so that a write that
|
||||||
|
/// throws leaves the adapter with exactly the bytes written before it. Only
|
||||||
|
/// @ref flush can report an error of the adapter (the destructor must not
|
||||||
|
/// throw), so it has to be called once the value is written.
|
||||||
template<typename CharType>
|
template<typename CharType>
|
||||||
class output_adapter_sink
|
class output_adapter_sink
|
||||||
{
|
{
|
||||||
@@ -290,36 +386,121 @@ class output_adapter_sink
|
|||||||
JSON_ASSERT(oa);
|
JSON_ASSERT(oa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output_adapter_sink(const output_adapter_sink&) = delete;
|
||||||
|
output_adapter_sink& operator=(const output_adapter_sink&) = delete;
|
||||||
|
output_adapter_sink& operator=(output_adapter_sink&&) = delete;
|
||||||
|
|
||||||
|
output_adapter_sink(output_adapter_sink&& other) noexcept
|
||||||
|
: oa(std::move(other.oa)), used(other.used)
|
||||||
|
{
|
||||||
|
std::memcpy(buffer.data(), other.buffer.data(), other.used * sizeof(CharType));
|
||||||
|
other.used = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
~output_adapter_sink()
|
||||||
|
{
|
||||||
|
// a normal return has flushed already, so this is for a write that threw
|
||||||
|
if (used != 0)
|
||||||
|
{
|
||||||
|
JSON_TRY
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
JSON_CATCH(...)
|
||||||
|
{
|
||||||
|
// nothing can be done about an adapter that throws here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
void write_character(CharType c)
|
void write_character(CharType c)
|
||||||
{
|
{
|
||||||
oa->write_character(c);
|
if (JSON_HEDLEY_UNLIKELY(used == buffer_size))
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
buffer[used++] = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
// no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as
|
// no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as
|
||||||
// the type-erased path already did before this sink existed
|
// the type-erased path already did before this sink existed
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
void write_characters(const CharType* s, std::size_t length)
|
void write_characters(const CharType* s, std::size_t length)
|
||||||
{
|
{
|
||||||
oa->write_characters(s, length);
|
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length))
|
||||||
|
{
|
||||||
|
write_large(s, length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (length <= 32)
|
||||||
|
{
|
||||||
|
copy_short(buffer.data() + used, s, length, std::integral_constant<bool, sizeof(CharType) == 1>());
|
||||||
|
used += length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::memcpy(buffer.data() + used, s, length * sizeof(CharType));
|
||||||
|
used += length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// space for writing up to @a length characters (see
|
/// space for writing up to @a length characters (see
|
||||||
/// output_vector_sink::reserve)
|
/// output_vector_sink::reserve)
|
||||||
CharType* reserve(const std::size_t length) noexcept
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
|
CharType* reserve(const std::size_t length)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(length <= max_reserve_size);
|
JSON_ASSERT(length <= max_reserve_size);
|
||||||
static_cast<void>(length);
|
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length))
|
||||||
return reserved.data();
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
return buffer.data() + used;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// write the first @a length characters written to the space from reserve()
|
/// keep the first @a length characters written to the space from reserve()
|
||||||
void commit(const std::size_t length)
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
|
void commit(const std::size_t length) noexcept
|
||||||
{
|
{
|
||||||
oa->write_characters(reserved.data(), length);
|
used += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// pass everything written so far on to the adapter
|
||||||
|
void flush()
|
||||||
|
{
|
||||||
|
if (used != 0)
|
||||||
|
{
|
||||||
|
// reset first: the characters count as written even if the adapter throws
|
||||||
|
const std::size_t length = used;
|
||||||
|
used = 0;
|
||||||
|
oa->write_characters(buffer.data(), length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// a write that does not fit the space left in the buffer
|
||||||
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
|
void write_large(const CharType* s, const std::size_t length)
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
if (length >= buffer_size)
|
||||||
|
{
|
||||||
|
oa->write_characters(s, length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::memcpy(buffer.data(), s, length * sizeof(CharType));
|
||||||
|
used = length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// large enough for reserve() at any time
|
||||||
|
static constexpr std::size_t buffer_size = 4 * max_reserve_size;
|
||||||
|
|
||||||
output_adapter_t<CharType> oa;
|
output_adapter_t<CharType> oa;
|
||||||
std::array<CharType, max_reserve_size> reserved{};
|
/// number of characters of @ref buffer not yet passed on
|
||||||
|
std::size_t used = 0;
|
||||||
|
std::array<CharType, buffer_size> buffer{};
|
||||||
};
|
};
|
||||||
|
|
||||||
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
|
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -48,6 +48,24 @@ inline std::string hex_byte(const std::uint8_t byte)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief write a UTF-16 code unit as four lowercase hexadecimal digits
|
||||||
|
|
||||||
|
The lowercase counterpart of hex_byte(), used for the `\\uXXXX` escapes of the
|
||||||
|
serializers, whose output is lowercase. Writes exactly four characters to
|
||||||
|
@a out and returns the pointer past them.
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE char* hex4_lower(const std::uint16_t codeunit, char* out) noexcept
|
||||||
|
{
|
||||||
|
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
||||||
|
const unsigned int unit = codeunit;
|
||||||
|
out[0] = nibble_to_hex[(unit >> 12u) & 0x0Fu];
|
||||||
|
out[1] = nibble_to_hex[(unit >> 8u) & 0x0Fu];
|
||||||
|
out[2] = nibble_to_hex[(unit >> 4u) & 0x0Fu];
|
||||||
|
out[3] = nibble_to_hex[unit & 0x0Fu];
|
||||||
|
return out + 4;
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////
|
///////////////////
|
||||||
// UTF-8 encoding //
|
// UTF-8 encoding //
|
||||||
///////////////////
|
///////////////////
|
||||||
@@ -103,6 +121,66 @@ void encode_utf8(std::uint32_t cp, const Out& out)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief the shape of a well-formed UTF-8 sequence, by its lead byte
|
||||||
|
|
||||||
|
@a length is 1 for ASCII, 2 to 4 for a valid lead byte, and 0 for a byte that
|
||||||
|
cannot start a well-formed sequence (a continuation byte, 0xC0, 0xC1, or 0xF5
|
||||||
|
to 0xFF). For a multibyte sequence, the second byte must lie in
|
||||||
|
[@a second_lo, @a second_hi], and every later byte in [0x80, 0xBF]
|
||||||
|
(RFC 3629, section 4; Unicode Table 3-7). The narrower second-byte ranges
|
||||||
|
exclude overlong forms (0xE0, 0xF0), surrogates (0xED), and code points
|
||||||
|
beyond U+10FFFF (0xF4).
|
||||||
|
*/
|
||||||
|
struct utf8_sequence_shape
|
||||||
|
{
|
||||||
|
std::uint8_t length;
|
||||||
|
std::uint8_t second_lo;
|
||||||
|
std::uint8_t second_hi;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @return the shape of a UTF-8 sequence starting with @a lead, see utf8_sequence_shape
|
||||||
|
inline utf8_sequence_shape utf8_shape(const std::uint8_t lead) noexcept
|
||||||
|
{
|
||||||
|
if (lead < 0x80u)
|
||||||
|
{
|
||||||
|
return {1, 0, 0};
|
||||||
|
}
|
||||||
|
if (lead < 0xC2u)
|
||||||
|
{
|
||||||
|
return {0, 0, 0};
|
||||||
|
}
|
||||||
|
if (lead <= 0xDFu)
|
||||||
|
{
|
||||||
|
return {2, 0x80u, 0xBFu};
|
||||||
|
}
|
||||||
|
if (lead == 0xE0u)
|
||||||
|
{
|
||||||
|
return {3, 0xA0u, 0xBFu};
|
||||||
|
}
|
||||||
|
if (lead == 0xEDu)
|
||||||
|
{
|
||||||
|
return {3, 0x80u, 0x9Fu};
|
||||||
|
}
|
||||||
|
if (lead <= 0xEFu)
|
||||||
|
{
|
||||||
|
return {3, 0x80u, 0xBFu};
|
||||||
|
}
|
||||||
|
if (lead == 0xF0u)
|
||||||
|
{
|
||||||
|
return {4, 0x90u, 0xBFu};
|
||||||
|
}
|
||||||
|
if (lead <= 0xF3u)
|
||||||
|
{
|
||||||
|
return {4, 0x80u, 0xBFu};
|
||||||
|
}
|
||||||
|
if (lead == 0xF4u)
|
||||||
|
{
|
||||||
|
return {4, 0x80u, 0x8Fu};
|
||||||
|
}
|
||||||
|
return {0, 0, 0};
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////
|
///////////////////
|
||||||
// UTF-8 decoding //
|
// UTF-8 decoding //
|
||||||
///////////////////
|
///////////////////
|
||||||
@@ -118,7 +196,7 @@ This is a single-byte step of a "shift-based" UTF-8 decoder originally
|
|||||||
written by Björn Hoehrmann. See
|
written by Björn Hoehrmann. See
|
||||||
http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
|
http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
|
||||||
|
|
||||||
The library checks UTF-8 well-formedness (RFC 3629, section 4) in three
|
The library checks UTF-8 well-formedness (RFC 3629, section 4) in five
|
||||||
places, which differ in speed, diagnostics, and how they read the input:
|
places, which differ in speed, diagnostics, and how they read the input:
|
||||||
|
|
||||||
- decode() below: the serializer, to escape and, in strict mode, reject
|
- decode() below: the serializer, to escape and, in strict mode, reject
|
||||||
@@ -132,10 +210,15 @@ places, which differ in speed, diagnostics, and how they read the input:
|
|||||||
bulk string scan, the bulk path of the BON8 reader, and the BON8 writer.
|
bulk string scan, the bulk path of the BON8 reader, and the BON8 writer.
|
||||||
They must accept exactly what the lexer's switch accepts.
|
They must accept exactly what the lexer's switch accepts.
|
||||||
- the byte path of binary_reader::get_bon8_string(): BON8 input without bulk
|
- the byte path of binary_reader::get_bon8_string(): BON8 input without bulk
|
||||||
access, and the bytes the bulk path leaves to it.
|
access, and the bytes the bulk path leaves to it. It keeps its own ranges:
|
||||||
|
taking them from utf8_shape() made BON8 decoding 2-3% slower (#5891).
|
||||||
|
- check_utf8() in detail/view/edit.hpp: strings stored in an editable
|
||||||
|
json_document, with the index of the offending byte. It also uses
|
||||||
|
utf8_shape().
|
||||||
|
|
||||||
All four must accept the same set of sequences, so a change to one needs a
|
All five must accept the same set of sequences, which the test "UTF-8
|
||||||
matching change to the others.
|
validators accept the same sequences" in unit-class_lexer.cpp checks
|
||||||
|
exhaustively against decode() and utf8_shape().
|
||||||
|
|
||||||
@param[in,out] state the current decoder state
|
@param[in,out] state the current decoder state
|
||||||
@param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
|
@param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
|
||||||
|
|||||||
@@ -177,6 +177,36 @@ inline bool owns_memory(const value_t t) noexcept
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief whether a value of the given type is neither an object nor an array
|
||||||
|
|
||||||
|
Unlike basic_json::is_primitive(), this includes value_t::discarded: it names
|
||||||
|
the types that iterators walk with a primitive_iterator_t (a single value, or no
|
||||||
|
value for null and discarded) and that JSON Pointer resolution and the
|
||||||
|
serializer treat as leaves. Every enumerator is listed so that -Wswitch-enum
|
||||||
|
flags this function when a new type is added.
|
||||||
|
*/
|
||||||
|
inline bool is_primitive(const value_t t) noexcept
|
||||||
|
{
|
||||||
|
switch (t)
|
||||||
|
{
|
||||||
|
case value_t::object:
|
||||||
|
case value_t::array:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case value_t::null:
|
||||||
|
case value_t::string:
|
||||||
|
case value_t::boolean:
|
||||||
|
case value_t::number_integer:
|
||||||
|
case value_t::number_unsigned:
|
||||||
|
case value_t::number_float:
|
||||||
|
case value_t::binary:
|
||||||
|
case value_t::discarded:
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief compare an integer with a floating point number without precision loss
|
@brief compare an integer with a floating point number without precision loss
|
||||||
|
|||||||
@@ -275,11 +275,34 @@ 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 by `write_back` on every return.
|
||||||
|
const unsigned char* const end = m_end;
|
||||||
const std::size_t start = arena_used();
|
const std::size_t start = arena_used();
|
||||||
arena_run(s, static_cast<std::size_t>(p - s));
|
char* w = m_arena_write;
|
||||||
|
const char* w_end = m_arena_end;
|
||||||
|
const arena_write_back write_back(*this, w);
|
||||||
|
{
|
||||||
|
const auto n = static_cast<std::size_t>(p - s);
|
||||||
|
if (n <= 16 && end - s >= 16)
|
||||||
|
{
|
||||||
|
arena_ensure(w, w_end, 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)
|
||||||
|
arena_ensure(w, w_end, n);
|
||||||
|
std::memcpy(w, s, n);
|
||||||
|
}
|
||||||
|
w += n;
|
||||||
|
}
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
if (p == m_end)
|
if (p == end)
|
||||||
{
|
{
|
||||||
return failed(error_code::string_missing_quote, p);
|
return failed(error_code::string_missing_quote, p);
|
||||||
}
|
}
|
||||||
@@ -287,7 +310,9 @@ class builder
|
|||||||
if (c == '"')
|
if (c == '"')
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
return decoded{p, start, arena_used() - start};
|
// (from w: `write_back` stores it only after the return value
|
||||||
|
// is made; the arena is allocated, since an escape was written)
|
||||||
|
return decoded{p, start, static_cast<std::size_t>(w - m_doc.arena.data()) - start};
|
||||||
}
|
}
|
||||||
if (c != '\\')
|
if (c != '\\')
|
||||||
{
|
{
|
||||||
@@ -297,7 +322,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 == m_end)
|
if (p == end)
|
||||||
{
|
{
|
||||||
return failed(error_code::string_missing_quote, p);
|
return failed(error_code::string_missing_quote, p);
|
||||||
}
|
}
|
||||||
@@ -315,7 +340,7 @@ class builder
|
|||||||
{
|
{
|
||||||
return failed(error_code::string_surrogate_low, p);
|
return failed(error_code::string_surrogate_low, p);
|
||||||
}
|
}
|
||||||
if (m_end - p < 2 || p[0] != '\\' || p[1] != 'u')
|
if (end - p < 2 || p[0] != '\\' || p[1] != 'u')
|
||||||
{
|
{
|
||||||
return failed(error_code::string_surrogate_high, p);
|
return failed(error_code::string_surrogate_high, p);
|
||||||
}
|
}
|
||||||
@@ -331,26 +356,106 @@ class builder
|
|||||||
}
|
}
|
||||||
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
||||||
}
|
}
|
||||||
arena_ensure(4);
|
arena_ensure(w, w_end, 4);
|
||||||
m_arena_write = put_utf8(m_arena_write, cp);
|
w = put_utf8(w, cp);
|
||||||
}
|
}
|
||||||
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
|
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
|
||||||
{
|
{
|
||||||
arena_ensure(1);
|
arena_ensure(w, w_end, 1);
|
||||||
*m_arena_write++ = simple_escape[d];
|
*w++ = simple_escape[d];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
--p;
|
--p;
|
||||||
return failed(error_code::string_escape, p);
|
return failed(error_code::string_escape, p);
|
||||||
}
|
}
|
||||||
if (p != m_end && *p == '\\')
|
#if NLOHMANN_VIEW_NEON
|
||||||
|
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:
|
||||||
|
arena_ensure(w, w_end, 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;
|
const unsigned char* const r = p;
|
||||||
p = scan_string_run(p, m_end);
|
p = scan_string_run(p, end);
|
||||||
arena_run(r, static_cast<std::size_t>(p - r));
|
const auto n = static_cast<std::size_t>(p - r);
|
||||||
|
arena_ensure(w, w_end, 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);
|
||||||
|
arena_ensure(w, w_end, n);
|
||||||
|
std::memcpy(w, r, n);
|
||||||
|
w += n;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
arena_ensure(w, w_end, 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);
|
||||||
|
arena_ensure(w, w_end, n);
|
||||||
|
std::memcpy(w, r, n);
|
||||||
|
w += n;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
const unsigned char* const r = p;
|
||||||
|
p = scan_string_run(p, end);
|
||||||
|
const auto n = static_cast<std::size_t>(p - r);
|
||||||
|
arena_ensure(w, w_end, n);
|
||||||
|
std::memcpy(w, r, n);
|
||||||
|
w += n;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,19 +497,49 @@ class builder
|
|||||||
char* m_arena_write = nullptr;
|
char* m_arena_write = nullptr;
|
||||||
char* m_arena_end = nullptr;
|
char* m_arena_end = nullptr;
|
||||||
|
|
||||||
|
/// room for n more bytes at w, the write position kept in a local, with
|
||||||
|
/// w_end the end of the arena. (A lambda instead is not always inlined,
|
||||||
|
/// and then w would be kept in memory, as m_arena_write is.)
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE void arena_ensure(char*& w, const char*& w_end, std::size_t 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// stores a write position kept in a local back into m_arena_write when
|
||||||
|
/// the function that keeps it returns, on every path
|
||||||
|
class arena_write_back
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
arena_write_back(builder& b, char*& w) noexcept
|
||||||
|
: m_builder(b)
|
||||||
|
, m_w(w)
|
||||||
|
{}
|
||||||
|
~arena_write_back()
|
||||||
|
{
|
||||||
|
m_builder.m_arena_write = m_w;
|
||||||
|
}
|
||||||
|
|
||||||
|
arena_write_back(const arena_write_back&) = delete;
|
||||||
|
arena_write_back& operator=(const arena_write_back&) = delete;
|
||||||
|
arena_write_back(arena_write_back&&) = delete;
|
||||||
|
arena_write_back& operator=(arena_write_back&&) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
builder& m_builder;
|
||||||
|
char*& m_w;
|
||||||
|
};
|
||||||
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE std::size_t arena_used() const noexcept
|
NLOHMANN_VIEW_ALWAYS_INLINE std::size_t arena_used() const noexcept
|
||||||
{
|
{
|
||||||
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();
|
||||||
@@ -413,25 +548,6 @@ class builder
|
|||||||
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.)
|
||||||
@@ -954,11 +1070,11 @@ indent_done:
|
|||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
const unsigned char* const int_start = p;
|
const unsigned char* const int_start = p;
|
||||||
if (p != e && *p == '0')
|
if (cur() == '0')
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
else if (NLOHMANN_VIEW_LIKELY(p != e && *p >= '1' && *p <= '9'))
|
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
|
||||||
{
|
{
|
||||||
p = skip_digits(p + 1, e);
|
p = skip_digits(p + 1, e);
|
||||||
}
|
}
|
||||||
@@ -969,7 +1085,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 (p != e && *p == '.')
|
if (cur() == '.')
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
const unsigned char* const f0 = p;
|
const unsigned char* const f0 = p;
|
||||||
@@ -982,20 +1098,20 @@ indent_done:
|
|||||||
is_float = true;
|
is_float = true;
|
||||||
}
|
}
|
||||||
std::int64_t exponent = 0;
|
std::int64_t exponent = 0;
|
||||||
if (p != e && (*p | 0x20) == 'e')
|
if ((cur() | 0x20) == 'e')
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
bool exp_negative = false;
|
bool exp_negative = false;
|
||||||
if (p != e && (*p == '+' || *p == '-'))
|
if (cur() == '+' || cur() == '-')
|
||||||
{
|
{
|
||||||
exp_negative = *p == '-';
|
exp_negative = *p == '-';
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(p == e || !is_digit(*p)))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_digit(cur())))
|
||||||
{
|
{
|
||||||
return fail(error_code::number_after_exponent);
|
return fail(error_code::number_after_exponent);
|
||||||
}
|
}
|
||||||
while (p != e && is_digit(*p))
|
while (is_digit(cur()))
|
||||||
{
|
{
|
||||||
if (exponent < 100000)
|
if (exponent < 100000)
|
||||||
{
|
{
|
||||||
@@ -1057,7 +1173,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(p != e && *p == '"'))
|
if (NLOHMANN_VIEW_LIKELY(cur() == '"'))
|
||||||
{
|
{
|
||||||
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;
|
||||||
|
|||||||
@@ -51,11 +51,6 @@ struct first_true<false, Conditions...> : std::integral_constant < int, 1 + firs
|
|||||||
inline void check_utf8(const char* s, std::size_t n)
|
inline void check_utf8(const char* s, std::size_t n)
|
||||||
{
|
{
|
||||||
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
const auto hex = [](unsigned char c)
|
|
||||||
{
|
|
||||||
constexpr const char* digits = "0123456789ABCDEF";
|
|
||||||
return std::string{digits[c >> 4u], digits[c & 0xFu]};
|
|
||||||
};
|
|
||||||
for (std::size_t i = 0; i < n;)
|
for (std::size_t i = 0; i < n;)
|
||||||
{
|
{
|
||||||
const unsigned char c = p[i];
|
const unsigned char c = p[i];
|
||||||
@@ -64,39 +59,22 @@ inline void check_utf8(const char* s, std::size_t n)
|
|||||||
++i;
|
++i;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
std::size_t len = 0;
|
const utf8_sequence_shape shape = utf8_shape(c);
|
||||||
unsigned char lo = 0x80;
|
const std::size_t len = shape.length;
|
||||||
unsigned char hi = 0xBF;
|
if (len == 0)
|
||||||
if (c >= 0xC2 && c <= 0xDF)
|
|
||||||
{
|
{
|
||||||
len = 2;
|
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex_byte(c)));
|
||||||
}
|
|
||||||
else if (c >= 0xE0 && c <= 0xEF)
|
|
||||||
{
|
|
||||||
len = 3;
|
|
||||||
lo = c == 0xE0 ? 0xA0 : 0x80;
|
|
||||||
hi = c == 0xED ? 0x9F : 0xBF;
|
|
||||||
}
|
|
||||||
else if (c >= 0xF0 && c <= 0xF4)
|
|
||||||
{
|
|
||||||
len = 4;
|
|
||||||
lo = c == 0xF0 ? 0x90 : 0x80;
|
|
||||||
hi = c == 0xF4 ? 0x8F : 0xBF;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex(c)));
|
|
||||||
}
|
}
|
||||||
for (std::size_t k = 1; k < len; ++k)
|
for (std::size_t k = 1; k < len; ++k)
|
||||||
{
|
{
|
||||||
if (i + k == n)
|
if (i + k == n)
|
||||||
{
|
{
|
||||||
throw_type_error(316, concat("incomplete UTF-8 string; last byte: 0x", hex(p[n - 1])));
|
throw_type_error(exception_id::invalid_utf8, concat("incomplete UTF-8 string; last byte: 0x", hex_byte(p[n - 1])));
|
||||||
}
|
}
|
||||||
const unsigned char b = p[i + k];
|
const unsigned char b = p[i + k];
|
||||||
if (b < (k == 1 ? lo : 0x80) || b > (k == 1 ? hi : 0xBF))
|
if (b < (k == 1 ? shape.second_lo : 0x80) || b > (k == 1 ? shape.second_hi : 0xBF))
|
||||||
{
|
{
|
||||||
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex(b)));
|
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex_byte(b)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i += len;
|
i += len;
|
||||||
@@ -144,7 +122,7 @@ class editor
|
|||||||
node* const o = own(object);
|
node* const o = own(object);
|
||||||
if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null))
|
if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null))
|
||||||
{
|
{
|
||||||
throw_type_error(305, "cannot use operator[] with a string argument with ", object.type_name());
|
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name());
|
||||||
}
|
}
|
||||||
check_utf8(key.data(), key.size());
|
check_utf8(key.data(), key.size());
|
||||||
const encoded e = encode(std::forward<V>(value));
|
const encoded e = encode(std::forward<V>(value));
|
||||||
@@ -195,7 +173,7 @@ class editor
|
|||||||
node* const a = own(array);
|
node* const a = own(array);
|
||||||
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
||||||
{
|
{
|
||||||
throw_type_error(305, "cannot use operator[] with a numeric argument with ", array.type_name());
|
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name());
|
||||||
}
|
}
|
||||||
check_index(idx, a->len);
|
check_index(idx, a->len);
|
||||||
const encoded e = encode(std::forward<V>(value));
|
const encoded e = encode(std::forward<V>(value));
|
||||||
@@ -212,7 +190,7 @@ class editor
|
|||||||
node* const a = own(array);
|
node* const a = own(array);
|
||||||
if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null))
|
if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null))
|
||||||
{
|
{
|
||||||
throw_type_error(308, "cannot use push_back() with ", array.type_name());
|
throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name());
|
||||||
}
|
}
|
||||||
const encoded e = encode(std::forward<V>(value));
|
const encoded e = encode(std::forward<V>(value));
|
||||||
if (a->kind == static_cast<std::uint8_t>(value_t::null))
|
if (a->kind == static_cast<std::uint8_t>(value_t::null))
|
||||||
@@ -236,7 +214,7 @@ class editor
|
|||||||
node* const a = own(array);
|
node* const a = own(array);
|
||||||
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
||||||
{
|
{
|
||||||
throw_type_error(309, "cannot use insert() with ", array.type_name());
|
throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name());
|
||||||
}
|
}
|
||||||
check_index(idx, a->len + 1);
|
check_index(idx, a->len + 1);
|
||||||
const encoded e = encode(std::forward<V>(value));
|
const encoded e = encode(std::forward<V>(value));
|
||||||
@@ -256,7 +234,7 @@ class editor
|
|||||||
node* const o = own(object);
|
node* const o = own(object);
|
||||||
if (o->kind != static_cast<std::uint8_t>(value_t::object))
|
if (o->kind != static_cast<std::uint8_t>(value_t::object))
|
||||||
{
|
{
|
||||||
throw_type_error(307, "cannot use erase() with ", object.type_name());
|
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name());
|
||||||
}
|
}
|
||||||
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
||||||
{
|
{
|
||||||
@@ -274,7 +252,7 @@ class editor
|
|||||||
node* const a = own(array);
|
node* const a = own(array);
|
||||||
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
||||||
{
|
{
|
||||||
throw_type_error(307, "cannot use erase() with ", array.type_name());
|
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name());
|
||||||
}
|
}
|
||||||
check_index(idx, a->len);
|
check_index(idx, a->len);
|
||||||
node* const h = block_of(m_doc, a, 0);
|
node* const h = block_of(m_doc, a, 0);
|
||||||
@@ -297,7 +275,7 @@ class editor
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr))
|
if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr))
|
||||||
{
|
{
|
||||||
throw_invalid_iterator(202, "view does not belong to this document");
|
throw_invalid_iterator(exception_id::iterator_from_other_value, "view does not belong to this document");
|
||||||
}
|
}
|
||||||
edit_state_of(m_doc);
|
edit_state_of(m_doc);
|
||||||
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
||||||
@@ -307,7 +285,7 @@ class editor
|
|||||||
{
|
{
|
||||||
if (idx >= limit)
|
if (idx >= limit)
|
||||||
{
|
{
|
||||||
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
|
throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,7 +426,7 @@ class editor
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr))
|
if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr))
|
||||||
{
|
{
|
||||||
throw_type_error(302, "type must be a value, but is ", "discarded");
|
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded");
|
||||||
}
|
}
|
||||||
encoded r;
|
encoded r;
|
||||||
if (!is_container(*v.m_node))
|
if (!is_container(*v.m_node))
|
||||||
@@ -598,7 +576,7 @@ class editor
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu))
|
if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu))
|
||||||
{
|
{
|
||||||
throw_out_of_range(416, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE
|
throw_out_of_range(exception_id::document_too_large, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
node n = plain_node(value_t::string);
|
node n = plain_node(value_t::string);
|
||||||
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
|
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
|
||||||
@@ -673,12 +651,12 @@ class editor
|
|||||||
return string_node(s.data(), s.size());
|
return string_node(s.data(), s.size());
|
||||||
}
|
}
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
throw_type_error(319, "cannot store a binary value in a json_document", "");
|
throw_type_error(exception_id::document_binary_value, "cannot store a binary value in a json_document", "");
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
default:
|
default:
|
||||||
throw_type_error(302, "type must be a value, but is ", "discarded");
|
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ inline std::size_t text_capacity(std::size_t cap, std::size_t used, std::size_t
|
|||||||
constexpr std::size_t limit = 0xFFFFFFFFu;
|
constexpr std::size_t limit = 0xFFFFFFFFu;
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(used > limit || n > limit - used))
|
if (NLOHMANN_VIEW_UNLIKELY(used > limit || n > limit - used))
|
||||||
{
|
{
|
||||||
throw_out_of_range(416, "edits of 4 GiB or more are not supported by json_document");
|
throw_out_of_range(exception_id::document_too_large, "edits of 4 GiB or more are not supported by json_document");
|
||||||
}
|
}
|
||||||
const std::size_t needed = used + n;
|
const std::size_t needed = used + n;
|
||||||
const std::size_t wanted = needed + (std::min)(limit - needed, std::size_t{256});
|
const std::size_t wanted = needed + (std::min)(limit - needed, std::size_t{256});
|
||||||
@@ -135,7 +135,7 @@ inline void reserve_moved(document_data& d)
|
|||||||
document_data::edit_state& e = edit_state_of(d);
|
document_data::edit_state& e = edit_state_of(d);
|
||||||
if (e.moved.size() >= 0xFFFFFFFFu)
|
if (e.moved.size() >= 0xFFFFFFFFu)
|
||||||
{
|
{
|
||||||
throw_out_of_range(416, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE
|
throw_out_of_range(exception_id::document_too_large, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity())
|
if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,32 +25,52 @@ namespace view
|
|||||||
// Exceptions are thrown out of line, so that the accessors that may throw stay
|
// Exceptions are thrown out of line, so that the accessors that may throw stay
|
||||||
// small enough to be inlined.
|
// small enough to be inlined.
|
||||||
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const char* prefix, const char* type)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type)
|
||||||
{
|
{
|
||||||
NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr));
|
NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const std::string& msg)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const std::string& msg)
|
||||||
{
|
{
|
||||||
NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr));
|
NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(int id, const std::string& msg)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(exception_id id, const std::string& msg)
|
||||||
{
|
{
|
||||||
NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr));
|
NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(int id, const char* msg)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(exception_id id, const char* msg)
|
||||||
{
|
{
|
||||||
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
|
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// a parse error without a position (as those of json_pointer)
|
/// a parse error without a position (as those of json_pointer)
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(int id, const std::string& msg)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(exception_id id, const std::string& msg)
|
||||||
{
|
{
|
||||||
NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr));
|
NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// out_of_range.401: the array index @a idx is not below the size
|
||||||
|
template<typename Index>
|
||||||
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const Index idx)
|
||||||
|
{
|
||||||
|
throw_out_of_range(exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// out_of_range.402: the JSON Pointer token '-' of an array of @a size elements
|
||||||
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_array_index_past_end(std::size_t size)
|
||||||
|
{
|
||||||
|
throw_out_of_range(exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// out_of_range.403: the object has no member @a key
|
||||||
|
template<typename Key>
|
||||||
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const Key& key)
|
||||||
|
{
|
||||||
|
throw_out_of_range(exception_id::key_not_found, concat("key '", key, "' not found"));
|
||||||
|
}
|
||||||
|
|
||||||
/// SAX handler of the error replay: accepts every event without storing it,
|
/// SAX handler of the error replay: accepts every event without storing it,
|
||||||
/// and throws the exception the parser reports
|
/// and throws the exception the parser reports
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
@@ -149,7 +169,7 @@ template<typename BasicJsonType>
|
|||||||
if (f.code == error_code::input_too_large)
|
if (f.code == error_code::input_too_large)
|
||||||
{
|
{
|
||||||
// (the limit is detail::view::max_input_size(): 4 GiB minus 16 bytes)
|
// (the limit is detail::view::max_input_size(): 4 GiB minus 16 bytes)
|
||||||
NLOHMANN_VIEW_THROW(out_of_range::create(416, "input of 4294967280 bytes or more is not supported by json_document", nullptr));
|
NLOHMANN_VIEW_THROW(out_of_range::create(exception_id::document_too_large, "input of 4294967280 bytes or more is not supported by json_document", nullptr));
|
||||||
}
|
}
|
||||||
throwing_sax<BasicJsonType> sax;
|
throwing_sax<BasicJsonType> sax;
|
||||||
const bool accepted = BasicJsonType::sax_parse(src, src + size, &sax, input_format_t::json, true, ignore_comments, ignore_trailing_commas);
|
const bool accepted = BasicJsonType::sax_parse(src, src + size, &sax, input_format_t::json, true, ignore_comments, ignore_trailing_commas);
|
||||||
@@ -169,7 +189,7 @@ template<typename BasicJsonType>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pos.chars_read_current_line = off + 1 - line_start;
|
pos.chars_read_current_line = off + 1 - line_start;
|
||||||
NLOHMANN_VIEW_THROW(parse_error::create(101, pos, "syntax error while parsing value", nullptr));
|
NLOHMANN_VIEW_THROW(parse_error::create(exception_id::syntax_error, pos, "syntax error while parsing value", nullptr));
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
|||||||
inline std::vector<std::uint8_t> save_image(const document_data& d)
|
inline std::vector<std::uint8_t> save_image(const document_data& d)
|
||||||
{
|
{
|
||||||
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||||
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||||
#endif
|
#endif
|
||||||
const std::size_t arena_size = d.arena_size;
|
const std::size_t arena_size = d.arena_size;
|
||||||
std::vector<node> compacted;
|
std::vector<node> compacted;
|
||||||
@@ -203,7 +203,7 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
|
|||||||
if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit))
|
if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit))
|
||||||
{
|
{
|
||||||
// LCOV_EXCL_START (4 GiB)
|
// LCOV_EXCL_START (4 GiB)
|
||||||
throw_out_of_range(416, "images of 4 GiB or more are not supported by json_document");
|
throw_out_of_range(exception_id::document_too_large, "images of 4 GiB or more are not supported by json_document");
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
image_header h{};
|
image_header h{};
|
||||||
@@ -660,7 +660,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
|||||||
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what)
|
||||||
{
|
{
|
||||||
throw_parse_error(116, concat("invalid json_document image: ", what));
|
throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read an image into d. The text and the decoded strings stay in the image;
|
/// Read an image into d. The text and the decoded strings stay in the image;
|
||||||
@@ -668,7 +668,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
|||||||
inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check)
|
inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check)
|
||||||
{
|
{
|
||||||
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||||
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||||
#endif
|
#endif
|
||||||
if (image == nullptr || size < sizeof(image_header))
|
if (image == nullptr || size < sizeof(image_header))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
|
|
||||||
#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
|
||||||
@@ -154,6 +157,37 @@ 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
|
||||||
@@ -110,7 +110,7 @@ class view_iterator
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0))
|
if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0))
|
||||||
{
|
{
|
||||||
throw_invalid_iterator(207, "cannot use key() for non-object iterators");
|
throw_invalid_iterator(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators");
|
||||||
}
|
}
|
||||||
return string_view_t(m_doc->str(*m_pos), m_pos->len);
|
return string_view_t(m_doc->str(*m_pos), m_pos->len);
|
||||||
}
|
}
|
||||||
@@ -147,20 +147,21 @@ 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
|
/// the member key, or the element index for arrays (as text made on the
|
||||||
|
/// 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());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +184,8 @@ class view_item
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
iterator m_it;
|
iterator m_it;
|
||||||
std::string m_index{}; // NOLINT(readability-redundant-member-init)
|
std::size_t m_position; ///< the index of an array element
|
||||||
|
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()
|
||||||
|
|||||||
@@ -166,6 +166,10 @@ 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);
|
||||||
@@ -181,6 +185,10 @@ 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;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm> // max
|
#include <array> // array
|
||||||
#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
|
||||||
@@ -40,12 +40,13 @@ template<typename BasicJsonType, bool Editable>
|
|||||||
BasicJsonType materialize(const document_data& d, const node* n)
|
BasicJsonType materialize(const document_data& d, const node* n)
|
||||||
{
|
{
|
||||||
using string_t = typename BasicJsonType::string_t;
|
using string_t = typename BasicJsonType::string_t;
|
||||||
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>>;
|
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>, true>;
|
||||||
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; sized once from the
|
// their end, and whether they are objects; for the first 16 levels on the
|
||||||
// document's deepest nesting (for a subtree an upper bound)
|
// stack, else sized once from the document's deepest nesting (for a
|
||||||
|
// subtree an upper bound)
|
||||||
struct frame
|
struct frame
|
||||||
{
|
{
|
||||||
const node* pos;
|
const node* pos;
|
||||||
@@ -53,10 +54,43 @@ 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{};
|
||||||
std::vector<frame> open((std::max)(d.max_depth, std::size_t{1}));
|
// (shallow subtrees on the stack: a call for a small value then allocates only for the value)
|
||||||
|
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 (;;)
|
||||||
{
|
{
|
||||||
@@ -76,9 +110,13 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
{
|
{
|
||||||
sax.start_array(n->len);
|
sax.start_array(n->len);
|
||||||
}
|
}
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(depth == open.size()))
|
if (NLOHMANN_VIEW_UNLIKELY(depth == capacity))
|
||||||
{
|
{
|
||||||
open.resize(depth * 2); // (deeper than the recorded nesting, e.g. after edits)
|
// (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;
|
||||||
|
|||||||
@@ -47,27 +47,96 @@ 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
|
/// hash of a key: its bytes, eight at a time (in a fixed byte order), where the
|
||||||
|
/// 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)
|
std::uint64_t w = 0;
|
||||||
|
if (n >= 8)
|
||||||
|
{
|
||||||
|
const unsigned char* const last = p + n - 8;
|
||||||
|
while (p < last)
|
||||||
{
|
{
|
||||||
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
|
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
|
||||||
h ^= h >> 29u;
|
h ^= h >> 29u;
|
||||||
p += 8;
|
p += 8;
|
||||||
n -= 8;
|
|
||||||
}
|
}
|
||||||
std::uint64_t w = 0;
|
w = read_eight_bytes(last);
|
||||||
for (std::size_t i = 0; i < n; ++i)
|
}
|
||||||
|
else if (n >= 4)
|
||||||
{
|
{
|
||||||
w |= static_cast<std::uint64_t>(p[i]) << (8u * i);
|
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);
|
||||||
}
|
}
|
||||||
h = (h ^ w) * 0x94D049BB133111EBu;
|
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)
|
||||||
{
|
{
|
||||||
@@ -132,6 +201,27 @@ 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];
|
||||||
|
|||||||
@@ -32,51 +32,15 @@ enum class pointer_mode
|
|||||||
contains, ///< as basic_json::contains(): no exceptions at all
|
contains, ///< as basic_json::contains(): no exceptions at all
|
||||||
};
|
};
|
||||||
|
|
||||||
/// the outcome of reading an array index from a reference token
|
/// the outcome of reading an array index: detail::parse_array_index(), shared with json_pointer
|
||||||
enum class index_status
|
using index_status = detail::array_index_status;
|
||||||
{
|
|
||||||
ok,
|
|
||||||
leading_zero, ///< parse_error.106
|
|
||||||
not_number, ///< parse_error.109
|
|
||||||
unresolved, ///< out_of_range.404
|
|
||||||
too_large, ///< out_of_range.410
|
|
||||||
};
|
|
||||||
|
|
||||||
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
|
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
|
||||||
/// but reports errors instead of throwing them
|
/// but reports errors instead of throwing them
|
||||||
template<typename StringType>
|
template<typename StringType>
|
||||||
index_status array_index(const StringType& s, std::size_t& idx) noexcept
|
index_status array_index(const StringType& s, std::size_t& idx) noexcept
|
||||||
{
|
{
|
||||||
// as basic_json: the empty token and "-" (past the last element) are
|
return detail::parse_array_index(s, idx);
|
||||||
// unresolved rather than not a number
|
|
||||||
if (s.empty() || (s.size() == 1 && s[0] == '-'))
|
|
||||||
{
|
|
||||||
return index_status::unresolved;
|
|
||||||
}
|
|
||||||
if (s[0] < '0' || s[0] > '9')
|
|
||||||
{
|
|
||||||
return index_status::not_number;
|
|
||||||
}
|
|
||||||
if (s.size() > 1 && s[0] == '0')
|
|
||||||
{
|
|
||||||
return index_status::leading_zero;
|
|
||||||
}
|
|
||||||
std::uint64_t v = 0;
|
|
||||||
for (const auto c : s)
|
|
||||||
{
|
|
||||||
const auto d = static_cast<unsigned>(static_cast<unsigned char>(c)) - '0';
|
|
||||||
if (d > 9 || v > ((std::numeric_limits<std::uint64_t>::max)() - d) / 10)
|
|
||||||
{
|
|
||||||
return index_status::unresolved; // not a number, or beyond unsigned long long
|
|
||||||
}
|
|
||||||
v = (v * 10) + d;
|
|
||||||
}
|
|
||||||
if (v >= (std::numeric_limits<std::size_t>::max)()) // (std::size_t converts to std::uint64_t implicitly)
|
|
||||||
{
|
|
||||||
return index_status::too_large;
|
|
||||||
}
|
|
||||||
idx = static_cast<std::size_t>(v);
|
|
||||||
return index_status::ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// throws the exception json_pointer::array_index throws for this status
|
/// throws the exception json_pointer::array_index throws for this status
|
||||||
@@ -86,15 +50,15 @@ template<typename StringType>
|
|||||||
switch (status)
|
switch (status)
|
||||||
{
|
{
|
||||||
case index_status::leading_zero:
|
case index_status::leading_zero:
|
||||||
throw_parse_error(106, concat("array index '", s, "' must not begin with '0'"));
|
throw_parse_error(exception_id::pointer_index_leading_zero, concat("array index '", s, "' must not begin with '0'"));
|
||||||
case index_status::not_number:
|
case index_status::not_a_number:
|
||||||
throw_parse_error(109, concat("array index '", s, "' is not a number"));
|
throw_parse_error(exception_id::pointer_index_not_a_number, concat("array index '", s, "' is not a number"));
|
||||||
case index_status::too_large:
|
case index_status::exceeds_size_type:
|
||||||
throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
|
throw_out_of_range(exception_id::value_out_of_range, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
|
||||||
case index_status::unresolved:
|
case index_status::unresolved:
|
||||||
case index_status::ok:
|
case index_status::ok:
|
||||||
default:
|
default:
|
||||||
throw_out_of_range(404, concat("unresolved reference token '", s, "'"));
|
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", s, "'"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +83,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
|||||||
{
|
{
|
||||||
if (mode == pointer_mode::checked)
|
if (mode == pointer_mode::checked)
|
||||||
{
|
{
|
||||||
throw_out_of_range(403, concat("key '", token, "' not found"));
|
throw_key_not_found(token);
|
||||||
}
|
}
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
@@ -131,7 +95,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
|||||||
{
|
{
|
||||||
if (throwing)
|
if (throwing)
|
||||||
{
|
{
|
||||||
throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range"));
|
throw_array_index_past_end(cur.size());
|
||||||
}
|
}
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
@@ -139,7 +103,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
|||||||
const index_status status = array_index(token, idx);
|
const index_status status = array_index(token, idx);
|
||||||
if (status != index_status::ok)
|
if (status != index_status::ok)
|
||||||
{
|
{
|
||||||
const bool parse_error = status == index_status::leading_zero || status == index_status::not_number;
|
const bool parse_error = status == index_status::leading_zero || status == index_status::not_a_number;
|
||||||
if (throwing || (mode == pointer_mode::value && parse_error))
|
if (throwing || (mode == pointer_mode::value && parse_error))
|
||||||
{
|
{
|
||||||
throw_array_index_error(status, token);
|
throw_array_index_error(status, token);
|
||||||
@@ -150,7 +114,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
|||||||
{
|
{
|
||||||
if (mode == pointer_mode::checked)
|
if (mode == pointer_mode::checked)
|
||||||
{
|
{
|
||||||
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
|
throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
@@ -160,7 +124,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
|||||||
{
|
{
|
||||||
if (throwing)
|
if (throwing)
|
||||||
{
|
{
|
||||||
throw_out_of_range(404, concat("unresolved reference token '", token, "'"));
|
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'"));
|
||||||
}
|
}
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||||
#include <nlohmann/detail/view/node.hpp>
|
#include <nlohmann/detail/view/node.hpp>
|
||||||
#include <nlohmann/detail/view/number.hpp>
|
#include <nlohmann/detail/view/number.hpp>
|
||||||
|
#include <nlohmann/detail/view/simd.hpp>
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -80,13 +81,6 @@ class output_buffer
|
|||||||
put(s, N - 1);
|
put(s, N - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void put_repeated(char c, std::size_t n)
|
|
||||||
{
|
|
||||||
reserve(n);
|
|
||||||
std::memset(m_pos, c, n);
|
|
||||||
m_pos += n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the write position and the end of the writable space, for a writer
|
/// the write position and the end of the writable space, for a writer
|
||||||
/// that keeps the position in a local variable (set_cursor() hands it back)
|
/// that keeps the position in a local variable (set_cursor() hands it back)
|
||||||
char* cursor() const noexcept
|
char* cursor() const noexcept
|
||||||
@@ -105,17 +99,13 @@ class output_buffer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// the size of a growth step (a function: std::min() takes a reference,
|
/// the size of a growth step
|
||||||
/// which a static constexpr member does not have before C++17)
|
static constexpr std::size_t step = 0x10000;
|
||||||
static constexpr std::size_t step() noexcept
|
|
||||||
{
|
|
||||||
return 0x10000;
|
|
||||||
}
|
|
||||||
|
|
||||||
static StringType& sized(StringType& out, std::size_t estimate)
|
static StringType& sized(StringType& out, std::size_t estimate)
|
||||||
{
|
{
|
||||||
out.reserve(estimate);
|
out.reserve(estimate);
|
||||||
out.resize((std::min)((std::max)(estimate, static_cast<std::size_t>(64)), step()));
|
out.resize(detail::clamp(estimate, std::size_t{64}, step));
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,8 +113,9 @@ class output_buffer
|
|||||||
{
|
{
|
||||||
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
||||||
// (a step does not go beyond the reserved estimate, so that a good
|
// (a step does not go beyond the reserved estimate, so that a good
|
||||||
// estimate is never copied to a larger allocation)
|
// estimate is never copied to a larger allocation; not a clamp: the
|
||||||
const std::size_t size = (std::max)((std::min)(m_out.size() + step(), m_out.capacity()), used + n + 256);
|
// lower bound used + n + 256 can exceed capacity(), and then it must win)
|
||||||
|
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256);
|
||||||
if (size > m_out.capacity())
|
if (size > m_out.capacity())
|
||||||
{
|
{
|
||||||
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
||||||
@@ -146,9 +137,27 @@ class output_buffer
|
|||||||
/// image_check::bounds can have others, which are then written unchanged).
|
/// image_check::bounds can have others, which are then written unchanged).
|
||||||
inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexcept
|
inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexcept
|
||||||
{
|
{
|
||||||
|
std::size_t i = 0;
|
||||||
|
#if NLOHMANN_VIEW_SSE2
|
||||||
|
// (16 bytes per step; with NEON, the mask of a step costs more than it saves on the
|
||||||
|
// short runs that are typical here)
|
||||||
|
// NOLINTBEGIN(portability-simd-intrinsics)
|
||||||
|
for (; i + 16 <= n; i += 16)
|
||||||
|
{
|
||||||
|
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(s + i))); // NOLINT(bugprone-casting-through-void)
|
||||||
|
// (a byte is below 0x20 if it is its own minimum with 0x1F)
|
||||||
|
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||||
|
_mm_cmpeq_epi8(_mm_min_epu8(in, _mm_set1_epi8(0x1F)), in));
|
||||||
|
const auto mask = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
||||||
|
if (mask != 0)
|
||||||
|
{
|
||||||
|
return i + static_cast<std::size_t>(count_trailing_zeros(mask));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// NOLINTEND(portability-simd-intrinsics)
|
||||||
|
#endif
|
||||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||||
constexpr std::uint64_t high = 0x8080808080808080ull;
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||||
std::size_t i = 0;
|
|
||||||
for (; i + 8 <= n; i += 8)
|
for (; i + 8 <= n; i += 8)
|
||||||
{
|
{
|
||||||
const std::uint64_t v = read_eight_bytes(s + i);
|
const std::uint64_t v = read_eight_bytes(s + i);
|
||||||
@@ -171,6 +180,98 @@ inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexc
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether dump() with ensure_ascii writes the string at s unchanged, for a
|
||||||
|
/// string of the source without escape sequences (which has no quote,
|
||||||
|
/// backslash, or control character): no byte is DEL (0x7F) or above. Adding 1
|
||||||
|
/// to each byte sets its high bit exactly for 0x7F, and a carry into the next
|
||||||
|
/// byte only comes from 0xFF, whose own high bit is set anyway.
|
||||||
|
inline bool is_ascii_without_delete(const char* s, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||||
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||||
|
std::size_t i = 0;
|
||||||
|
for (; i + 8 <= n; i += 8)
|
||||||
|
{
|
||||||
|
const std::uint64_t v = read_eight_bytes(s + i);
|
||||||
|
if (((v | (v + ones)) & high) != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; i < n; ++i)
|
||||||
|
{
|
||||||
|
if (static_cast<unsigned char>(s[i]) >= 0x7F)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if NLOHMANN_VIEW_VECTOR
|
||||||
|
/// the bytes of the 16 at p that ensure_ascii cannot copy (a quote, a
|
||||||
|
/// backslash, a control character, DEL, or not ASCII; a signed compare with
|
||||||
|
/// 0x20 finds control characters and non-ASCII bytes at once), one bit per
|
||||||
|
/// byte (x86) or one nibble per byte (NEON)
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t ascii_stop_mask(const unsigned char* p) noexcept
|
||||||
|
{
|
||||||
|
#if NLOHMANN_VIEW_NEON
|
||||||
|
const uint8x16_t in = vld1q_u8(p);
|
||||||
|
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
||||||
|
vorrq_u8(vceqq_u8(in, vdupq_n_u8(0x7F)), vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20))));
|
||||||
|
return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
||||||
|
#else
|
||||||
|
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // 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_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F)), _mm_cmplt_epi8(in, _mm_set1_epi8(0x20))));
|
||||||
|
return static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the width in bits of a byte in the mask of ascii_stop_mask()
|
||||||
|
constexpr unsigned ascii_stop_bits() noexcept
|
||||||
|
{
|
||||||
|
return NLOHMANN_VIEW_NEON ? 4u : 1u;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// As find_ascii_copyable_run(), 16 bytes per step. end is the end of the
|
||||||
|
/// buffer that holds the n bytes at p (a short run is read as 16 bytes if
|
||||||
|
/// they are in the buffer).
|
||||||
|
inline std::size_t ascii_copyable_run(const unsigned char* p, std::size_t n, const unsigned char* end) noexcept
|
||||||
|
{
|
||||||
|
#if NLOHMANN_VIEW_VECTOR
|
||||||
|
if (n >= 16)
|
||||||
|
{
|
||||||
|
std::size_t i = 0;
|
||||||
|
for (; i + 16 <= n; i += 16)
|
||||||
|
{
|
||||||
|
const std::uint64_t mask = ascii_stop_mask(p + i);
|
||||||
|
if (mask != 0)
|
||||||
|
{
|
||||||
|
return i + (static_cast<std::size_t>(count_trailing_zeros(mask)) / ascii_stop_bits());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i == n)
|
||||||
|
{
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
// the last 16 bytes: those before i are known to be copyable
|
||||||
|
const std::uint64_t mask = ascii_stop_mask(p + n - 16);
|
||||||
|
return mask != 0 ? n - 16 + (static_cast<std::size_t>(count_trailing_zeros(mask)) / ascii_stop_bits()) : n;
|
||||||
|
}
|
||||||
|
if (end - p >= 16)
|
||||||
|
{
|
||||||
|
// (the bytes after the run are masked out)
|
||||||
|
const std::uint64_t mask = ascii_stop_mask(p) & ((std::uint64_t{1} << (n * ascii_stop_bits())) - 1u);
|
||||||
|
return mask != 0 ? static_cast<std::size_t>(count_trailing_zeros(mask)) / ascii_stop_bits() : n;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static_cast<void>(end);
|
||||||
|
#endif
|
||||||
|
return find_ascii_copyable_run(p, n);
|
||||||
|
}
|
||||||
|
|
||||||
/// A stack that starts in a buffer of the caller (a local array) and moves to
|
/// A stack that starts in a buffer of the caller (a local array) and moves to
|
||||||
/// the heap (a vector of the caller) only when that is full, so that dumps of
|
/// the heap (a vector of the caller) only when that is full, so that dumps of
|
||||||
/// shallow documents need no allocation. The top is a pointer, as in
|
/// shallow documents need no allocation. The top is a pointer, as in
|
||||||
@@ -250,6 +351,39 @@ NLOHMANN_VIEW_ALWAYS_INLINE char* put_literal(char* w, const char (&s)[N]) noexc
|
|||||||
return w + N - 1;
|
return w + N - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// the depth of the innermost open container, counted only for indented output
|
||||||
|
template<bool Counted>
|
||||||
|
struct depth_counter
|
||||||
|
{
|
||||||
|
std::size_t value = 0;
|
||||||
|
void enter() noexcept
|
||||||
|
{
|
||||||
|
++value;
|
||||||
|
}
|
||||||
|
std::size_t leave() noexcept
|
||||||
|
{
|
||||||
|
return --value;
|
||||||
|
}
|
||||||
|
std::size_t get() const noexcept
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct depth_counter<false>
|
||||||
|
{
|
||||||
|
void enter() noexcept {}
|
||||||
|
std::size_t leave() noexcept // NOLINT(readability-convert-member-functions-to-static): same interface as depth_counter<true>
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
std::size_t get() const noexcept // NOLINT(readability-convert-member-functions-to-static): same interface as depth_counter<true>
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/// how the view's dump() writes a value
|
/// how the view's dump() writes a value
|
||||||
struct dump_style
|
struct dump_style
|
||||||
{
|
{
|
||||||
@@ -286,121 +420,258 @@ class view_serializer
|
|||||||
|
|
||||||
void dump(const node* root)
|
void dump(const node* root)
|
||||||
{
|
{
|
||||||
if (!m_style.pretty && !m_style.ensure_ascii)
|
if (m_style.ensure_ascii)
|
||||||
{
|
{
|
||||||
if (m_style.source_numbers)
|
dump_ascii(root);
|
||||||
|
}
|
||||||
|
else if (m_style.pretty)
|
||||||
{
|
{
|
||||||
dump_compact<true>(root);
|
dump_pretty(root);
|
||||||
|
}
|
||||||
|
else if (m_style.source_numbers)
|
||||||
|
{
|
||||||
|
dump_text<true, false, false>(root);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dump_compact<false>(root);
|
dump_text<false, false, false>(root);
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
struct frame
|
|
||||||
{
|
|
||||||
const node* pos; ///< next element, or key of the next member
|
|
||||||
const node* end;
|
|
||||||
bool object;
|
|
||||||
bool first; ///< nothing written yet
|
|
||||||
};
|
|
||||||
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
|
||||||
std::vector<frame> heap;
|
|
||||||
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
|
|
||||||
const node* n = root;
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
// write the value at n
|
|
||||||
if (is_container(*n))
|
|
||||||
{
|
|
||||||
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
|
||||||
if (n->len == 0)
|
|
||||||
{
|
|
||||||
m_out.put(object ? "{}" : "[]", 2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_out.put(object ? '{' : '[');
|
|
||||||
stack.push_back(frame{nav::first(m_doc, n), nav::end(m_doc, n), object, true});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_scalar(*n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// go to the next value: close finished containers, then separate
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
if (stack.empty())
|
|
||||||
{
|
|
||||||
m_out.finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (stack.back().pos == stack.back().end)
|
|
||||||
{
|
|
||||||
const bool object = stack.back().object;
|
|
||||||
stack.pop_back();
|
|
||||||
newline(stack.size());
|
|
||||||
m_out.put(object ? '}' : ']');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
frame& f = stack.back();
|
|
||||||
if (!f.first)
|
|
||||||
{
|
|
||||||
m_out.put(',');
|
|
||||||
}
|
|
||||||
f.first = false;
|
|
||||||
newline(stack.size());
|
|
||||||
if (f.object)
|
|
||||||
{
|
|
||||||
write_string(*f.pos);
|
|
||||||
if (m_style.pretty)
|
|
||||||
{
|
|
||||||
m_out.put(": ");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_out.put(':');
|
|
||||||
}
|
|
||||||
n = nav::value(f.pos + 1);
|
|
||||||
f.pos = document_data::after(f.pos + 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
n = nav::value(f.pos);
|
|
||||||
f.pos = document_data::after(f.pos);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// dump_text() with ensure_ascii, out of line: the walk without it is
|
||||||
|
/// faster when it is not merged with this one into dump()
|
||||||
|
NLOHMANN_VIEW_NOINLINE void dump_ascii(const node* root)
|
||||||
|
{
|
||||||
|
if (m_style.pretty)
|
||||||
|
{
|
||||||
|
if (m_style.source_numbers)
|
||||||
|
{
|
||||||
|
dump_text<true, true, true>(root);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dump_text<false, true, true>(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (m_style.source_numbers)
|
||||||
|
{
|
||||||
|
dump_text<true, false, true>(root);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dump_text<false, false, true>(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// dump_text() with indentation, out of line: the compact walk is faster
|
||||||
|
/// when it is not merged with the indented ones into dump()
|
||||||
|
NLOHMANN_VIEW_NOINLINE void dump_pretty(const node* root)
|
||||||
|
{
|
||||||
|
if (m_style.source_numbers)
|
||||||
|
{
|
||||||
|
dump_text<true, true, false>(root);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dump_text<false, true, false>(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// a string that is not a plain string of the source (decoded, or written
|
/// a string that is not a plain string of the source (decoded, or written
|
||||||
/// by an edit), without ensure_ascii: runs without characters to escape
|
/// by an edit), without ensure_ascii: runs without characters to escape
|
||||||
/// are copied
|
/// are copied. If validate is set, the string must be valid UTF-8: the
|
||||||
NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n)
|
/// runs are found by the lexer's scan, which validates the sequences, and
|
||||||
|
/// invalid UTF-8 throws what basic_json::dump() throws. (The compact output
|
||||||
|
/// writes the bytes of a damaged image unchanged, and scans without
|
||||||
|
/// validating them.)
|
||||||
|
NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n, bool validate)
|
||||||
{
|
{
|
||||||
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
m_out.put('"');
|
m_out.put('"');
|
||||||
for (std::size_t i = 0; i < n.len;)
|
for (std::size_t i = 0; i < n.len;)
|
||||||
{
|
{
|
||||||
const std::size_t run = plain_output_run(s + i, n.len - i);
|
const std::size_t run = validate ? string_bulk_run(s + i, n.len - i) : plain_output_run(s + i, n.len - i);
|
||||||
if (run != 0)
|
if (run != 0)
|
||||||
{
|
{
|
||||||
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
i += run;
|
i += run;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (validate && s[i] >= 0x80)
|
||||||
|
{
|
||||||
|
invalid_utf8(s, n.len); // (the scan stopped at ill-formed UTF-8)
|
||||||
|
}
|
||||||
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
|
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
m_out.put('"');
|
m_out.put('"');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the copies of dump_compact() that are not fixed-size moves (long
|
/// whether dump_text() copies a string or key from the source: one of
|
||||||
|
/// the source, and with ensure_ascii one without bytes >= 0x7F (an overload
|
||||||
|
/// each, so that the walk without ensure_ascii is compiled exactly as
|
||||||
|
/// before: with GCC, a condition that only folds away later changed its
|
||||||
|
/// block layout and made it 4-6% slower)
|
||||||
|
static bool copied(const node& n, const char* /*src*/, std::false_type /*ensure_ascii*/) noexcept
|
||||||
|
{
|
||||||
|
return (n.flags & node_flags::storage) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool copied(const node& n, const char* src, std::true_type /*ensure_ascii*/) noexcept
|
||||||
|
{
|
||||||
|
return (n.flags & node_flags::storage) == 0 && is_ascii_without_delete(src + n.off, n.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief a string with ensure_ascii: the characters that are not printable ASCII as \\uXXXX
|
||||||
|
(a character beyond U+FFFF as a surrogate pair); strings of a document
|
||||||
|
are valid UTF-8, except in a damaged image loaded with image_check::bounds,
|
||||||
|
for which this throws what basic_json::dump() throws for the string
|
||||||
|
*/
|
||||||
|
NLOHMANN_VIEW_NOINLINE void write_ascii(const node& n)
|
||||||
|
{
|
||||||
|
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
const std::size_t size = n.len;
|
||||||
|
char* w = m_out.cursor();
|
||||||
|
char* lim = m_out.limit();
|
||||||
|
// room for k bytes
|
||||||
|
const auto room = [&](std::size_t k)
|
||||||
|
{
|
||||||
|
const auto left = static_cast<std::size_t>(lim - w);
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY(left < k))
|
||||||
|
{
|
||||||
|
m_out.set_cursor(w);
|
||||||
|
m_out.reserve(k);
|
||||||
|
w = m_out.cursor();
|
||||||
|
lim = m_out.limit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
static constexpr const char* hex = "0123456789abcdef";
|
||||||
|
// \\uXXXX
|
||||||
|
const auto u_escape = [&](std::uint32_t u)
|
||||||
|
{
|
||||||
|
w[0] = '\\';
|
||||||
|
w[1] = 'u';
|
||||||
|
w[2] = hex[(u >> 12u) & 0xFu];
|
||||||
|
w[3] = hex[(u >> 8u) & 0xFu];
|
||||||
|
w[4] = hex[(u >> 4u) & 0xFu];
|
||||||
|
w[5] = hex[u & 0xFu];
|
||||||
|
w += 6;
|
||||||
|
};
|
||||||
|
room(1);
|
||||||
|
*w++ = '"';
|
||||||
|
std::size_t i = 0;
|
||||||
|
// (a string of the source starts with a run that has nothing to escape, 16 bytes per step)
|
||||||
|
const std::size_t prefix = (n.flags & node_flags::storage) == 0 ? ascii_copyable_run(s, size, reinterpret_cast<const unsigned char*>(m_doc.src + m_doc.size)) : 0; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
if (prefix != 0)
|
||||||
|
{
|
||||||
|
room(prefix);
|
||||||
|
std::memcpy(w, s, prefix);
|
||||||
|
w += prefix;
|
||||||
|
i = prefix;
|
||||||
|
}
|
||||||
|
while (i < size)
|
||||||
|
{
|
||||||
|
// (a character that needs an escape sequence is not worth a call)
|
||||||
|
const std::size_t run = is_ascii_copyable(s[i]) ? find_ascii_copyable_run(s + i, size - i) : 0;
|
||||||
|
if (run != 0)
|
||||||
|
{
|
||||||
|
room(run);
|
||||||
|
std::memcpy(w, s + i, run);
|
||||||
|
w += run;
|
||||||
|
i += run;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
room(12);
|
||||||
|
if (s[i] >= 0x80)
|
||||||
|
{
|
||||||
|
const unsigned char lead = s[i];
|
||||||
|
// (the two-byte sequences of Latin text and the three-byte sequences of most other
|
||||||
|
// text, with continuation bytes of any value: the ranges that are restricted (E0,
|
||||||
|
// ED, and the sequences of four bytes) go through validate_one_utf8())
|
||||||
|
if (lead >= 0xE1 && lead <= 0xEF && lead != 0xED && size - i >= 3 && (s[i + 1] & 0xC0u) == 0x80 && (s[i + 2] & 0xC0u) == 0x80)
|
||||||
|
{
|
||||||
|
u_escape(((lead & 0x0Fu) << 12u) | ((s[i + 1] & 0x3Fu) << 6u) | (s[i + 2] & 0x3Fu));
|
||||||
|
i += 3;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (lead >= 0xC2 && lead <= 0xDF && size - i >= 2 && (s[i + 1] & 0xC0u) == 0x80)
|
||||||
|
{
|
||||||
|
u_escape(((lead & 0x1Fu) << 6u) | (s[i + 1] & 0x3Fu));
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::size_t len = validate_one_utf8(s + i, size - i);
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY(len == 0))
|
||||||
|
{
|
||||||
|
m_out.set_cursor(w);
|
||||||
|
invalid_utf8(s, size);
|
||||||
|
return; // LCOV_EXCL_LINE (invalid_utf8 throws)
|
||||||
|
}
|
||||||
|
std::uint32_t codepoint = s[i] & (0xFFu >> (len + 1));
|
||||||
|
for (std::size_t k = 1; k < len; ++k)
|
||||||
|
{
|
||||||
|
codepoint = (codepoint << 6u) | (s[i + k] & 0x3Fu);
|
||||||
|
}
|
||||||
|
i += len;
|
||||||
|
if (codepoint <= 0xFFFF)
|
||||||
|
{
|
||||||
|
u_escape(codepoint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
u_escape(0xD7C0u + (codepoint >> 10u));
|
||||||
|
u_escape(0xDC00u + (codepoint & 0x3FFu));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// a quote, a backslash, or a control character (or DEL)
|
||||||
|
const unsigned char c = s[i++];
|
||||||
|
char escape = 0;
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case 0x08:
|
||||||
|
escape = 'b';
|
||||||
|
break;
|
||||||
|
case 0x09:
|
||||||
|
escape = 't';
|
||||||
|
break;
|
||||||
|
case 0x0A:
|
||||||
|
escape = 'n';
|
||||||
|
break;
|
||||||
|
case 0x0C:
|
||||||
|
escape = 'f';
|
||||||
|
break;
|
||||||
|
case 0x0D:
|
||||||
|
escape = 'r';
|
||||||
|
break;
|
||||||
|
case 0x22:
|
||||||
|
escape = '"';
|
||||||
|
break;
|
||||||
|
case 0x5C:
|
||||||
|
escape = '\\';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (escape != 0)
|
||||||
|
{
|
||||||
|
w[0] = '\\';
|
||||||
|
w[1] = escape;
|
||||||
|
w += 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
u_escape(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
room(1);
|
||||||
|
*w++ = '"';
|
||||||
|
m_out.set_cursor(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the copies of dump_text() that are not fixed-size moves (long
|
||||||
/// strings, or near the end of the source); out of line, so that the
|
/// strings, or near the end of the source); out of line, so that the
|
||||||
/// compiler does not merge the fixed-size moves into this call
|
/// compiler does not merge the fixed-size moves into this call
|
||||||
NLOHMANN_VIEW_NOINLINE static void copy_long(char* to, const char* from, std::size_t n) noexcept
|
NLOHMANN_VIEW_NOINLINE static void copy_long(char* to, const char* from, std::size_t n) noexcept
|
||||||
@@ -417,17 +688,19 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief the compact output without ensure_ascii (the default dump())
|
@brief the output, compact (the default dump()) or indented, with or without ensure_ascii
|
||||||
|
|
||||||
The same walk as dump(), with the write position in a local variable
|
The same walk as dump(), with the write position in a local variable
|
||||||
(stores through char pointers would otherwise force a reload of the
|
(stores through char pointers would otherwise force a reload of the
|
||||||
buffer's members after each one), and with strings and number tokens of
|
buffer's members after each one), and with strings and number tokens of
|
||||||
the source copied by fixed-size moves of 32 bytes where the source has
|
the source copied by fixed-size moves of 32 bytes where the source has
|
||||||
that many bytes left, instead of a library call per token. The buffer
|
that many bytes left, instead of a library call per token. The buffer
|
||||||
keeps 64 bytes of slack for the overshoot.
|
keeps 64 bytes of slack for the overshoot. With ensure_ascii (Ascii), a
|
||||||
|
string of the source is copied only if it has nothing to escape (see
|
||||||
|
copied()); any other string is written by write_ascii().
|
||||||
*/
|
*/
|
||||||
template<bool SourceNumbers>
|
template<bool SourceNumbers, bool Pretty, bool Ascii>
|
||||||
void dump_compact(const node* root)
|
void dump_text(const node* root)
|
||||||
{
|
{
|
||||||
struct frame
|
struct frame
|
||||||
{
|
{
|
||||||
@@ -442,6 +715,16 @@ class view_serializer
|
|||||||
const char* const src_end = src + m_doc.size;
|
const char* const src_end = src + m_doc.size;
|
||||||
char* w = m_out.cursor();
|
char* w = m_out.cursor();
|
||||||
char* lim = m_out.limit();
|
char* lim = m_out.limit();
|
||||||
|
// (indented output) a line break and 63 indentation characters, from
|
||||||
|
// which the line break of a level of up to 63 characters is a copy
|
||||||
|
std::array<char, 64> ruler; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled below if used
|
||||||
|
if (Pretty)
|
||||||
|
{
|
||||||
|
ruler.fill(m_style.indent_char);
|
||||||
|
ruler[0] = '\n';
|
||||||
|
}
|
||||||
|
const std::size_t indent = m_style.indent;
|
||||||
|
depth_counter<Pretty> level; // depth of the innermost open container (indented output)
|
||||||
// room for n bytes and the slack
|
// room for n bytes and the slack
|
||||||
const auto room = [&](std::size_t n)
|
const auto room = [&](std::size_t n)
|
||||||
{
|
{
|
||||||
@@ -473,12 +756,35 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
w += n;
|
w += n;
|
||||||
};
|
};
|
||||||
|
// (indented output) the line break and the indentation of a level
|
||||||
|
const auto newline = [&](std::size_t depth)
|
||||||
|
{
|
||||||
|
const std::size_t count = 1 + (depth * indent);
|
||||||
|
room(count);
|
||||||
|
if (count <= ruler.size())
|
||||||
|
{
|
||||||
|
std::memcpy(w, ruler.data(), ruler.size());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*w = '\n';
|
||||||
|
std::memset(w + 1, m_style.indent_char, count - 1);
|
||||||
|
}
|
||||||
|
w += count;
|
||||||
|
};
|
||||||
// a string that is not a plain string of the source (out of line, so
|
// a string that is not a plain string of the source (out of line, so
|
||||||
// that the cursor stays in a register here)
|
// that the cursor stays in a register here)
|
||||||
const auto escaped = [&](const node & n)
|
const auto escaped = [&](const node & n)
|
||||||
{
|
{
|
||||||
m_out.set_cursor(w);
|
m_out.set_cursor(w);
|
||||||
write_decoded(n);
|
if (enabled(Ascii))
|
||||||
|
{
|
||||||
|
write_ascii(n);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
write_decoded(n, Pretty);
|
||||||
|
}
|
||||||
w = m_out.cursor();
|
w = m_out.cursor();
|
||||||
lim = m_out.limit();
|
lim = m_out.limit();
|
||||||
};
|
};
|
||||||
@@ -501,7 +807,7 @@ class view_serializer
|
|||||||
switch (static_cast<value_t>(n->kind))
|
switch (static_cast<value_t>(n->kind))
|
||||||
{
|
{
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
if ((n->flags & node_flags::storage) == 0)
|
if (copied(*n, src, std::integral_constant<bool, Ascii>()))
|
||||||
{
|
{
|
||||||
room(n->len + 2);
|
room(n->len + 2);
|
||||||
*w++ = '"';
|
*w++ = '"';
|
||||||
@@ -578,6 +884,7 @@ class view_serializer
|
|||||||
{
|
{
|
||||||
*w++ = object ? '{' : '[';
|
*w++ = object ? '{' : '[';
|
||||||
stack.push_back(cur);
|
stack.push_back(cur);
|
||||||
|
level.enter();
|
||||||
if (Editable)
|
if (Editable)
|
||||||
{
|
{
|
||||||
cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object};
|
cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object};
|
||||||
@@ -620,6 +927,10 @@ class view_serializer
|
|||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (Pretty)
|
||||||
|
{
|
||||||
|
newline(level.leave());
|
||||||
|
}
|
||||||
room(1);
|
room(1);
|
||||||
*w++ = cur.object ? '}' : ']';
|
*w++ = cur.object ? '}' : ']';
|
||||||
cur = stack.back();
|
cur = stack.back();
|
||||||
@@ -628,13 +939,17 @@ class view_serializer
|
|||||||
room(1);
|
room(1);
|
||||||
*w++ = ',';
|
*w++ = ',';
|
||||||
}
|
}
|
||||||
|
if (Pretty)
|
||||||
|
{
|
||||||
|
newline(level.get());
|
||||||
|
}
|
||||||
const node* const at = Editable ? cur.pos : n;
|
const node* const at = Editable ? cur.pos : n;
|
||||||
if (cur.object)
|
if (cur.object)
|
||||||
{
|
{
|
||||||
const node& key = *at;
|
const node& key = *at;
|
||||||
if ((key.flags & node_flags::storage) == 0)
|
if (copied(key, src, std::integral_constant<bool, Ascii>()))
|
||||||
{
|
{
|
||||||
room(key.len + 3);
|
room(key.len + (Pretty ? 4 : 3));
|
||||||
*w++ = '"';
|
*w++ = '"';
|
||||||
copy(src + key.off, key.len);
|
copy(src + key.off, key.len);
|
||||||
w[0] = '"';
|
w[0] = '"';
|
||||||
@@ -644,9 +959,13 @@ class view_serializer
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
escaped(key);
|
escaped(key);
|
||||||
room(1);
|
room(Pretty ? 2 : 1);
|
||||||
*w++ = ':';
|
*w++ = ':';
|
||||||
}
|
}
|
||||||
|
if (Pretty)
|
||||||
|
{
|
||||||
|
*w++ = ' ';
|
||||||
|
}
|
||||||
if (Editable)
|
if (Editable)
|
||||||
{
|
{
|
||||||
n = nav::value(at + 1);
|
n = nav::value(at + 1);
|
||||||
@@ -665,69 +984,6 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void newline(std::size_t level)
|
|
||||||
{
|
|
||||||
if (m_style.pretty)
|
|
||||||
{
|
|
||||||
m_out.put('\n');
|
|
||||||
m_out.put_repeated(m_style.indent_char, level * m_style.indent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void write_scalar(const node& n)
|
|
||||||
{
|
|
||||||
switch (static_cast<value_t>(n.kind))
|
|
||||||
{
|
|
||||||
case value_t::null:
|
|
||||||
m_out.put("null");
|
|
||||||
break;
|
|
||||||
case value_t::boolean:
|
|
||||||
if ((n.flags & node_flags::is_true) != 0)
|
|
||||||
{
|
|
||||||
m_out.put("true");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_out.put("false");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case value_t::string:
|
|
||||||
write_string(n);
|
|
||||||
break;
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
{
|
|
||||||
const char* const token = m_doc.str(n);
|
|
||||||
const std::uint32_t len = number_length(n);
|
|
||||||
if (!m_style.source_numbers && len == 2 && token[0] == '-' && token[1] == '0')
|
|
||||||
{
|
|
||||||
m_out.put('0'); // parse() reads -0 as the integer 0
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_out.put(token, len);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case value_t::number_float:
|
|
||||||
if (m_style.source_numbers && (n.flags & node_flags::storage) != node_flags::edited)
|
|
||||||
{
|
|
||||||
m_out.put(m_doc.str(n), n.len); // (a float set by an edit is written as with shortest)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_float_node(n);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case value_t::object: // LCOV_EXCL_LINE (containers are written by dump())
|
|
||||||
case value_t::array: // LCOV_EXCL_LINE
|
|
||||||
case value_t::binary: // LCOV_EXCL_LINE (not in a document)
|
|
||||||
case value_t::discarded: // LCOV_EXCL_LINE
|
|
||||||
default: // LCOV_EXCL_LINE
|
|
||||||
break; // LCOV_EXCL_LINE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// a float node as dump() writes it
|
/// a float node as dump() writes it
|
||||||
void write_float_node(const node& n)
|
void write_float_node(const node& n)
|
||||||
{
|
{
|
||||||
@@ -794,8 +1050,7 @@ class view_serializer
|
|||||||
static char* write_double_value_at(char* w, double x)
|
static char* write_double_value_at(char* w, double x)
|
||||||
{
|
{
|
||||||
// (from the bits: without the checks of to_chars())
|
// (from the bits: without the checks of to_chars())
|
||||||
std::uint64_t bits = 0;
|
auto bits = bit_cast<std::uint64_t>(x);
|
||||||
std::memcpy(&bits, &x, sizeof(bits));
|
|
||||||
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
|
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
|
||||||
{
|
{
|
||||||
return write_text_at(w, "null", 4);
|
return write_text_at(w, "null", 4);
|
||||||
@@ -818,92 +1073,23 @@ class view_serializer
|
|||||||
m_out.put("null");
|
m_out.put("null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
static constexpr bool is_ieee_single_or_double
|
write_float(x, std::integral_constant<bool, ::nlohmann::detail::has_native_float_format<number_float_t>::value>());
|
||||||
= std::numeric_limits<number_float_t>::is_iec559
|
|
||||||
&& ((std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128)
|
|
||||||
|| (std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024));
|
|
||||||
write_float(x, std::integral_constant<bool, is_ieee_single_or_double>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
|
void write_float(number_float_t x, std::true_type /*has_native_float_format*/)
|
||||||
{
|
{
|
||||||
std::array<char, 64> buf{};
|
std::array<char, 64> buf{};
|
||||||
const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x);
|
const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x);
|
||||||
m_out.put(buf.data(), static_cast<std::size_t>(end - buf.data()));
|
m_out.put(buf.data(), static_cast<std::size_t>(end - buf.data()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
|
void write_float(number_float_t x, std::false_type /*has_native_float_format*/)
|
||||||
{
|
{
|
||||||
// other types (e.g. long double) are rare: the library writes them
|
// other types (e.g. long double) are rare: the library writes them
|
||||||
const string_t s = BasicJsonType(x).dump();
|
const string_t s = BasicJsonType(x).dump();
|
||||||
m_out.put(s.data(), s.size());
|
m_out.put(s.data(), s.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_string(const node& n)
|
|
||||||
{
|
|
||||||
const char* const s = m_doc.str(n);
|
|
||||||
m_out.put('"');
|
|
||||||
if ((n.flags & node_flags::storage) == 0 && !m_style.ensure_ascii)
|
|
||||||
{
|
|
||||||
// a string of the source without escape sequences has nothing to escape
|
|
||||||
m_out.put(s, n.len);
|
|
||||||
}
|
|
||||||
else if (m_style.ensure_ascii)
|
|
||||||
{
|
|
||||||
write_escaped<true>(reinterpret_cast<const unsigned char*>(s), n.len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_escaped<false>(reinterpret_cast<const unsigned char*>(s), n.len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
}
|
|
||||||
m_out.put('"');
|
|
||||||
}
|
|
||||||
|
|
||||||
/// as serializer::dump_escaped(); strings of a document are valid UTF-8,
|
|
||||||
/// except in a damaged image loaded with image_check::bounds, for which
|
|
||||||
/// this throws what basic_json::dump() throws for the string
|
|
||||||
template<bool EnsureAscii>
|
|
||||||
void write_escaped(const unsigned char* s, std::size_t n)
|
|
||||||
{
|
|
||||||
std::size_t i = 0;
|
|
||||||
while (i < n)
|
|
||||||
{
|
|
||||||
std::size_t run = 0;
|
|
||||||
if (!EnsureAscii)
|
|
||||||
{
|
|
||||||
run = string_bulk_run(s + i, n - i);
|
|
||||||
}
|
|
||||||
else if (is_ascii_copyable(s[i]))
|
|
||||||
{
|
|
||||||
run = find_ascii_copyable_run(s + i, n - i);
|
|
||||||
}
|
|
||||||
if (run != 0)
|
|
||||||
{
|
|
||||||
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
i += run;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
std::uint32_t codepoint = s[i];
|
|
||||||
std::size_t len = 1;
|
|
||||||
if (codepoint >= 0x80)
|
|
||||||
{
|
|
||||||
len = validate_one_utf8(s + i, n - i);
|
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(len == 0))
|
|
||||||
{
|
|
||||||
invalid_utf8(s, n);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
codepoint &= 0xFFu >> (len + 1);
|
|
||||||
for (std::size_t k = 1; k < len; ++k)
|
|
||||||
{
|
|
||||||
codepoint = (codepoint << 6u) | (s[i + k] & 0x3Fu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
write_codepoint<EnsureAscii>(codepoint, s + i, len);
|
|
||||||
i += len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// throw what basic_json::dump() throws for a string that is not valid UTF-8
|
/// throw what basic_json::dump() throws for a string that is not valid UTF-8
|
||||||
NLOHMANN_VIEW_NOINLINE static void invalid_utf8(const unsigned char* s, std::size_t n)
|
NLOHMANN_VIEW_NOINLINE static void invalid_utf8(const unsigned char* s, std::size_t n)
|
||||||
{
|
{
|
||||||
@@ -958,8 +1144,8 @@ class view_serializer
|
|||||||
|
|
||||||
void write_u_escape(std::uint32_t u)
|
void write_u_escape(std::uint32_t u)
|
||||||
{
|
{
|
||||||
static constexpr const char* hex = "0123456789abcdef";
|
std::array<char, 6> e = {{'\\', 'u', 0, 0, 0, 0}};
|
||||||
const std::array<char, 6> e = {{'\\', 'u', hex[(u >> 12u) & 0xFu], hex[(u >> 8u) & 0xFu], hex[(u >> 4u) & 0xFu], hex[u & 0xFu]}};
|
hex4_lower(static_cast<std::uint16_t>(u), &e[2]);
|
||||||
m_out.put(e.data(), e.size());
|
m_out.put(e.data(), e.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,12 @@
|
|||||||
#endif
|
#endif
|
||||||
#if NLOHMANN_VIEW_SSE2 && !NLOHMANN_VIEW_SSSE3 && !defined(NLOHMANN_VIEW_NO_TARGET_ATTRIBUTE) && ((defined(__clang__) && __clang_major__ >= 4) || (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9))))
|
#if NLOHMANN_VIEW_SSE2 && !NLOHMANN_VIEW_SSSE3 && !defined(NLOHMANN_VIEW_NO_TARGET_ATTRIBUTE) && ((defined(__clang__) && __clang_major__ >= 4) || (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9))))
|
||||||
// (GCC before 4.9 has no SSSE3 intrinsics without -mssse3)
|
// (GCC before 4.9 has no SSSE3 intrinsics without -mssse3)
|
||||||
|
// <cpuid.h> of GCC before 11 has no include guard, and json.hpp and
|
||||||
|
// json_view.hpp both include it
|
||||||
|
#ifndef NLOHMANN_JSON_CPUID_INCLUDED
|
||||||
|
#define NLOHMANN_JSON_CPUID_INCLUDED
|
||||||
#include <cpuid.h>
|
#include <cpuid.h>
|
||||||
|
#endif
|
||||||
#include <tmmintrin.h>
|
#include <tmmintrin.h>
|
||||||
#define NLOHMANN_VIEW_SSSE3_DISPATCH 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
#define NLOHMANN_VIEW_SSSE3_DISPATCH 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
#define NLOHMANN_VIEW_SSSE3_TARGET __attribute__((target("ssse3")))
|
#define NLOHMANN_VIEW_SSSE3_TARGET __attribute__((target("ssse3")))
|
||||||
@@ -111,6 +116,34 @@ 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
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
|
|||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
throw_type_error(302, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
|
throw_type_error(exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ std::vector<U, A> vector_value(const View& v)
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
|
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
|
||||||
{
|
{
|
||||||
throw_type_error(302, "type must be array, but is ", v.type_name());
|
throw_type_error(exception_id::type_mismatch, "type must be array, but is ", v.type_name());
|
||||||
}
|
}
|
||||||
std::vector<U, A> r;
|
std::vector<U, A> r;
|
||||||
r.reserve(v.size());
|
r.reserve(v.size());
|
||||||
@@ -88,7 +88,7 @@ Map map_value(const View& v)
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
|
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
|
||||||
{
|
{
|
||||||
throw_type_error(302, "type must be object, but is ", v.type_name());
|
throw_type_error(exception_id::type_mismatch, "type must be object, but is ", v.type_name());
|
||||||
}
|
}
|
||||||
Map r;
|
Map r;
|
||||||
for (auto it = v.begin(); it != v.end(); ++it)
|
for (auto it = v.begin(); it != v.end(); ++it)
|
||||||
|
|||||||
+664
-343
File diff suppressed because it is too large.
Load diff
+102
-28
@@ -30,6 +30,7 @@
|
|||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <cstddef> // nullptr_t, size_t // IWYU pragma: keep
|
#include <cstddef> // nullptr_t, size_t // IWYU pragma: keep
|
||||||
#include <cstdint> // uint8_t, uint32_t
|
#include <cstdint> // uint8_t, uint32_t
|
||||||
|
#include <cstdio> // FILE
|
||||||
#include <cstring> // memcpy, strlen
|
#include <cstring> // memcpy, strlen
|
||||||
#include <istream> // istream // IWYU pragma: keep
|
#include <istream> // istream // IWYU pragma: keep
|
||||||
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
||||||
@@ -267,7 +268,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name());
|
||||||
}
|
}
|
||||||
return lookup(key);
|
return lookup(key);
|
||||||
}
|
}
|
||||||
@@ -293,9 +294,9 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name());
|
||||||
}
|
}
|
||||||
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();
|
return idx < m_node->len ? element(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
|
||||||
@@ -326,12 +327,12 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(304, "cannot use at() with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
||||||
}
|
}
|
||||||
const basic_json_view r = lookup(key);
|
const basic_json_view r = lookup(key);
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
|
if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
|
||||||
{
|
{
|
||||||
detail::view::throw_out_of_range(403, detail::concat("key '", std::string(key.data(), key.size()), "' not found"));
|
detail::view::throw_key_not_found(std::string(key.data(), key.size()));
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
@@ -352,13 +353,13 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(304, "cannot use at() with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
||||||
}
|
}
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len))
|
if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len))
|
||||||
{
|
{
|
||||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
detail::view::throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
|
return element(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// any other integer type, see operator[]; negative values are out of
|
/// any other integer type, see operator[]; negative values are out of
|
||||||
@@ -384,7 +385,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||||
}
|
}
|
||||||
const basic_json_view r = lookup(key);
|
const basic_json_view r = lookup(key);
|
||||||
return r.is_discarded() ? default_value : r.template get<T>();
|
return r.is_discarded() ? default_value : r.template get<T>();
|
||||||
@@ -392,7 +393,13 @@ 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
|
||||||
{
|
{
|
||||||
return value(key, string_t(default_value));
|
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||||
|
{
|
||||||
|
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
|
||||||
@@ -403,7 +410,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "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);
|
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() ? default_value : r.template get<T>();
|
return r.is_discarded() ? default_value : r.template get<T>();
|
||||||
@@ -411,7 +418,12 @@ 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
|
||||||
{
|
{
|
||||||
return value(ptr, string_t(default_value));
|
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||||
|
{
|
||||||
|
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
|
||||||
@@ -421,7 +433,7 @@ class basic_json_view
|
|||||||
const iterator it = begin();
|
const iterator it = begin();
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(it == end()))
|
if (NLOHMANN_VIEW_UNLIKELY(it == end()))
|
||||||
{
|
{
|
||||||
detail::view::throw_invalid_iterator(214, "cannot get value");
|
detail::view::throw_invalid_iterator(detail::exception_id::iterator_value_unavailable, "cannot get value");
|
||||||
}
|
}
|
||||||
return *it;
|
return *it;
|
||||||
}
|
}
|
||||||
@@ -433,7 +445,8 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (is_structured() && m_node->len != 0)
|
if (is_structured() && m_node->len != 0)
|
||||||
{
|
{
|
||||||
return basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
|
return unedited() ? basic_json_view(m_doc, detail::view::last_child<false>(*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();
|
||||||
}
|
}
|
||||||
@@ -450,7 +463,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
return end();
|
return end();
|
||||||
}
|
}
|
||||||
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
|
const node* const k = find_key(key);
|
||||||
return k != nullptr ? iterator(m_doc, k, true) : end();
|
return k != nullptr ? iterator(m_doc, k, true) : end();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,7 +480,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() && detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr;
|
return is_object() && find_key(key) != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool contains(const char* key) const
|
bool contains(const char* key) const
|
||||||
@@ -573,7 +586,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(302, "type must be string, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be string, but is ", type_name());
|
||||||
}
|
}
|
||||||
return {m_doc->str(*m_node), m_node->len};
|
return {m_doc->str(*m_node), m_node->len};
|
||||||
}
|
}
|
||||||
@@ -585,7 +598,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
||||||
}
|
}
|
||||||
return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
|
return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
|
||||||
}
|
}
|
||||||
@@ -627,7 +640,14 @@ 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, false>(*m_doc, out, estimate, style).dump(m_node);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
|
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
|
||||||
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -707,7 +727,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
return BasicJsonType(value_t::discarded);
|
return BasicJsonType(value_t::discarded);
|
||||||
}
|
}
|
||||||
return detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
|
return unedited() ? detail::view::materialize<BasicJsonType, false>(*m_doc, m_node) : 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
|
||||||
@@ -785,17 +805,57 @@ 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 member of this object with @a key (the last one if the key occurs more than once), or nullptr
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key) const
|
||||||
|
{
|
||||||
|
return find_key(key, std::integral_constant<bool, Editable> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::true_type /*editable*/) const
|
||||||
|
{
|
||||||
|
// (an editable document without edits is looked up as one that cannot be edited)
|
||||||
|
return unedited() ? detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size())
|
||||||
|
: detail::view::find_member<true>(*m_doc, m_node, key.data(), key.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::false_type /*editable*/) const
|
||||||
|
{
|
||||||
|
return detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(302, "type must be boolean, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name());
|
||||||
}
|
}
|
||||||
return (m_node->flags & detail::view::node_flags::is_true) != 0;
|
return (m_node->flags & detail::view::node_flags::is_true) != 0;
|
||||||
}
|
}
|
||||||
@@ -805,7 +865,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
|
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
||||||
}
|
}
|
||||||
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
|
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
|
||||||
}
|
}
|
||||||
@@ -814,7 +874,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(302, "type must be null, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be null, but is ", type_name());
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -1123,7 +1183,7 @@ class basic_json_document
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(320, "cannot save a discarded json_document");
|
detail::view::throw_type_error(detail::exception_id::document_image_unsupported, "cannot save a discarded json_document");
|
||||||
}
|
}
|
||||||
return detail::view::save_image(*m_data);
|
return detail::view::save_image(*m_data);
|
||||||
}
|
}
|
||||||
@@ -1224,7 +1284,7 @@ class basic_json_document
|
|||||||
const std::size_t idx = pointer_index(token);
|
const std::size_t idx = pointer_index(token);
|
||||||
if (idx >= 0xFFFFFFFFu)
|
if (idx >= 0xFFFFFFFFu)
|
||||||
{
|
{
|
||||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
detail::view::throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < idx; ++i)
|
for (std::size_t i = 0; i < idx; ++i)
|
||||||
{
|
{
|
||||||
@@ -1280,7 +1340,7 @@ class basic_json_document
|
|||||||
{
|
{
|
||||||
if (ptr.empty())
|
if (ptr.empty())
|
||||||
{
|
{
|
||||||
detail::view::throw_out_of_range(405, "JSON pointer has no parent");
|
detail::view::throw_out_of_range(detail::exception_id::patch_on_root, "JSON pointer has no parent");
|
||||||
}
|
}
|
||||||
const view_type parent = root().at(ptr.parent_pointer());
|
const view_type parent = root().at(ptr.parent_pointer());
|
||||||
const auto& token = ptr.back();
|
const auto& token = ptr.back();
|
||||||
@@ -1300,7 +1360,7 @@ class basic_json_document
|
|||||||
static_assert(Editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, true> (json_editable_document)");
|
static_assert(Editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, true> (json_editable_document)");
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
||||||
{
|
{
|
||||||
detail::view::throw_invalid_iterator(202, "view does not belong to this document");
|
detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document");
|
||||||
}
|
}
|
||||||
return detail::view::editor<BasicJsonType, view_type>(*m_data);
|
return detail::view::editor<BasicJsonType, view_type>(*m_data);
|
||||||
}
|
}
|
||||||
@@ -1317,7 +1377,7 @@ class basic_json_document
|
|||||||
{
|
{
|
||||||
if (idx < 0)
|
if (idx < 0)
|
||||||
{
|
{
|
||||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
detail::view::throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
return static_cast<std::size_t>(idx);
|
return static_cast<std::size_t>(idx);
|
||||||
}
|
}
|
||||||
@@ -1528,7 +1588,8 @@ 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 : 3 > {});
|
: detail::view::is_adapter_input<D>::value ? 2
|
||||||
|
: detail::view::is_c_stream<D>::value ? 4 : 3 > {});
|
||||||
// NOLINTEND(readability-avoid-nested-conditional-operator)
|
// NOLINTEND(readability-avoid-nested-conditional-operator)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1550,6 +1611,19 @@ 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*/)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -125,6 +125,19 @@ private:
|
|||||||
Container::pop_back();
|
Container::pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief remove the entry with the given key; returns the number of removed entries
|
||||||
|
template<class K>
|
||||||
|
size_type erase_impl(const K& key)
|
||||||
|
{
|
||||||
|
const auto it = find_impl(*this, key);
|
||||||
|
if (it != this->end())
|
||||||
|
{
|
||||||
|
erase_at(it);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
template<class V, detail::enable_if_t<
|
template<class V, detail::enable_if_t<
|
||||||
detail::is_constructible<T, V>::value, int> = 0>
|
detail::is_constructible<T, V>::value, int> = 0>
|
||||||
@@ -214,26 +227,14 @@ public:
|
|||||||
|
|
||||||
size_type erase(const key_type& key)
|
size_type erase(const key_type& key)
|
||||||
{
|
{
|
||||||
const auto it = find_impl(*this, key);
|
return erase_impl(key);
|
||||||
if (it != this->end())
|
|
||||||
{
|
|
||||||
erase_at(it);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class KeyType, detail::enable_if_t<
|
template<class KeyType, detail::enable_if_t<
|
||||||
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
|
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
|
||||||
size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)
|
size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)
|
||||||
{
|
{
|
||||||
const auto it = find_impl(*this, key);
|
return erase_impl(key);
|
||||||
if (it != this->end())
|
|
||||||
{
|
|
||||||
erase_at(it);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
iterator erase(iterator pos)
|
iterator erase(iterator pos)
|
||||||
|
|||||||
Generated
+1924
-594516
File diff suppressed because it is too large.
Load diff
Generated
+7053
-2231
File diff suppressed because it is too large.
Load diff
Generated
+10
-3
@@ -139,6 +139,12 @@
|
|||||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO
|
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_NO_SIMD
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_NO_SIMD _ns
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_NO_SIMD
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef JSON_VIEW_NO_SIMD
|
#ifdef JSON_VIEW_NO_SIMD
|
||||||
#define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD _vns
|
#define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD _vns
|
||||||
#else
|
#else
|
||||||
@@ -156,9 +162,9 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Construct the namespace ABI tags component
|
// Construct the namespace ABI tags component
|
||||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k ## l
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l, m) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k ## l ## m
|
||||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j, k, l) \
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j, k, l, m) \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l)
|
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l, m)
|
||||||
|
|
||||||
#define NLOHMANN_JSON_ABI_TAGS \
|
#define NLOHMANN_JSON_ABI_TAGS \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||||
@@ -172,6 +178,7 @@
|
|||||||
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS, \
|
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS, \
|
||||||
NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN, \
|
NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN, \
|
||||||
NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO, \
|
NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO, \
|
||||||
|
NLOHMANN_JSON_ABI_TAG_NO_SIMD, \
|
||||||
NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD, \
|
NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD, \
|
||||||
NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3)
|
NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3)
|
||||||
|
|
||||||
|
|||||||
Generated
+1001
-457
File diff suppressed because it is too large.
Load diff
@@ -60,6 +60,7 @@ inline namespace json_literals
|
|||||||
namespace detail
|
namespace detail
|
||||||
{
|
{
|
||||||
using NLOHMANN_JSON_NAMESPACE::detail::json_sax_dom_callback_parser;
|
using NLOHMANN_JSON_NAMESPACE::detail::json_sax_dom_callback_parser;
|
||||||
|
using NLOHMANN_JSON_NAMESPACE::detail::json_sax_dom_parser;
|
||||||
using NLOHMANN_JSON_NAMESPACE::detail::unknown_size;
|
using NLOHMANN_JSON_NAMESPACE::detail::unknown_size;
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
|
|||||||
@@ -373,6 +373,25 @@ json_test_add_test_for(src/unit-diagnostic-positions.cpp
|
|||||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# the lexer again with the portable string scanning instead of SSE2/NEON, and on
|
||||||
|
# x86-64 with SSSE3 compiled in (no check of the CPU)
|
||||||
|
json_test_set_test_options(test-class_lexer_no_simd
|
||||||
|
COMPILE_DEFINITIONS JSON_NO_SIMD
|
||||||
|
)
|
||||||
|
json_test_add_test_for(src/unit-class_lexer.cpp
|
||||||
|
NAME test-class_lexer_no_simd
|
||||||
|
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||||
|
)
|
||||||
|
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$" AND NOT MSVC)
|
||||||
|
json_test_set_test_options(test-class_lexer_ssse3
|
||||||
|
COMPILE_OPTIONS -mssse3
|
||||||
|
)
|
||||||
|
json_test_add_test_for(src/unit-class_lexer.cpp
|
||||||
|
NAME test-class_lexer_ssse3
|
||||||
|
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# test reading from a std::istream again without scanning the get area in bulk
|
# test reading from a std::istream again without scanning the get area in bulk
|
||||||
json_test_set_test_options(test-stream-bulk-scan_disabled
|
json_test_set_test_options(test-stream-bulk-scan_disabled
|
||||||
COMPILE_DEFINITIONS JSON_DISABLE_STREAM_BULK_SCAN=1
|
COMPILE_DEFINITIONS JSON_DISABLE_STREAM_BULK_SCAN=1
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ foreach(test default noversion)
|
|||||||
JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1
|
JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1
|
||||||
JSON_DISABLE_STREAM_BULK_SCAN=1
|
JSON_DISABLE_STREAM_BULK_SCAN=1
|
||||||
JSON_DISABLE_UNLOCKED_FILE_IO=1
|
JSON_DISABLE_UNLOCKED_FILE_IO=1
|
||||||
|
JSON_NO_SIMD
|
||||||
JSON_VIEW_NO_SIMD
|
JSON_VIEW_NO_SIMD
|
||||||
JSON_VIEW_USE_SSSE3)
|
JSON_VIEW_USE_SSSE3)
|
||||||
target_link_libraries(abi_config_${test}_all_tags PRIVATE abi_compat_main)
|
target_link_libraries(abi_config_${test}_all_tags PRIVATE abi_compat_main)
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ TEST_CASE("default namespace")
|
|||||||
expected += "_dufio";
|
expected += "_dufio";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_NO_SIMD
|
||||||
|
expected += "_ns";
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef JSON_VIEW_NO_SIMD
|
#ifdef JSON_VIEW_NO_SIMD
|
||||||
expected += "_vns";
|
expected += "_vns";
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ TEST_CASE("default namespace without version component")
|
|||||||
expected += "_dufio";
|
expected += "_dufio";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_NO_SIMD
|
||||||
|
expected += "_ns";
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef JSON_VIEW_NO_SIMD
|
#ifdef JSON_VIEW_NO_SIMD
|
||||||
expected += "_vns";
|
expected += "_vns";
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -22,6 +22,15 @@ Micro-benchmarks for parsing, serialization and the binary formats, written with
|
|||||||
| `ViewAccept` | validate with `json_document::accept`; compare with `Accept` |
|
| `ViewAccept` | validate with `json_document::accept`; compare with `Accept` |
|
||||||
| `ViewMaterialize` | convert a parsed `json_document` into a `json` value |
|
| `ViewMaterialize` | convert a parsed `json_document` into a `json` value |
|
||||||
| `ViewDump` | serialize a parsed `json_document`; compare with `Dump` |
|
| `ViewDump` | serialize a parsed `json_document`; compare with `Dump` |
|
||||||
|
| `SaxParse` | parse with a SAX handler that only counts events, so no values are built |
|
||||||
|
| `Copy`, `CopyAssign`, `CopyRing` | copy a parsed document and destroy a copy, both timed: a temporary copy, an assignment over an earlier copy, and replacing the oldest of eight live copies |
|
||||||
|
| `CopyOnly`, `DestroyOnly` | only the copy or only the destruction, the other half untimed; a fast destructor can leave work to the next allocations, so these tell the halves apart but do not compare versions |
|
||||||
|
| `Equal`, `Iterate` | compare a parsed document with a copy, or visit every value with range-based `for` loops |
|
||||||
|
| `Flatten`, `FlattenOrdered`, `Unflatten` | `flatten()` for `json` and `ordered_json`, and `unflatten()` |
|
||||||
|
| `Diff`, `Patch` | `diff()` against the document with every eighth scalar changed, and applying that patch |
|
||||||
|
| `JsonPointerParse`, `JsonPointerToString`, `JsonPointerAt`, `JsonPointerContains` | the JSON pointers of every scalar in a document: parse them from strings, turn them back into strings, look them up with `at()` and `contains()` |
|
||||||
|
| `GetVectorInt`, `GetVectorDouble`, `GetMap` | `get<std::vector<std::int64_t>>()`, `get<std::vector<double>>()` and `get<std::map<std::string, json>>()` on large arrays and a large flat object |
|
||||||
|
| `ToJsonStrings` | create a `json` array from a `std::vector` of 100,000 strings, copied or moved, and destroy it again |
|
||||||
|
|
||||||
The input files are those of [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark) (`canada`,
|
The input files are those of [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark) (`canada`,
|
||||||
`citm_catalog`, `twitter`), a large `jeopardy` file, and number-heavy files (`floats`, `signed_ints`, ...).
|
`citm_catalog`, `twitter`), a large `jeopardy` file, and number-heavy files (`floats`, `signed_ints`, ...).
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
build/
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# json_view compared with other libraries
|
||||||
|
|
||||||
|
The in-tree benchmarks in [`tests/benchmarks`](../README.md) measure `json_document` against `json::parse` only. The
|
||||||
|
programs here compare it with [yyjson](https://github.com/ibireme/yyjson),
|
||||||
|
[simdjson](https://github.com/simdjson/simdjson), and [Boost.JSON](https://github.com/boostorg/json): the question
|
||||||
|
users ask when they pick a library. They are not built by CMake or run by CI.
|
||||||
|
|
||||||
|
## Reproducing the numbers
|
||||||
|
|
||||||
|
`compare.py` builds the programs against `include/` of this checkout, runs them, and writes the results together with
|
||||||
|
everything needed to reproduce them to `results/<date>-<host>.md` (and `.csv`): the date, the commit, the CPU, the
|
||||||
|
OS, the compiler, the flags, and the versions of all libraries.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 tests/benchmarks/json_view/compare.py --data <json_test_data directory> [--native] [--rounds 30]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `--data` is the downloaded [test data](https://github.com/nlohmann/json_test_data), e.g. the `test_files` directory
|
||||||
|
of a CMake build directory. It needs `nativejson-benchmark/{twitter,citm_catalog,canada}.json` and
|
||||||
|
`jeopardy/jeopardy.json`.
|
||||||
|
- The other libraries come from the system: pkg-config, or Homebrew (`brew install yyjson simdjson boost`). With
|
||||||
|
`--download`, pinned releases are downloaded instead and checked against their SHA-256. Without Boost headers (or
|
||||||
|
with `--no-boost`), the Boost.JSON columns are skipped, and the results say so.
|
||||||
|
- `--corpus file...` adds files to the corpus benchmark, e.g. those of
|
||||||
|
[simdjson-data](https://github.com/simdjson/simdjson-data) or the
|
||||||
|
[yyjson benchmark](https://github.com/ibireme/yyjson_benchmark).
|
||||||
|
- Only the Python 3 standard library is used; a C++17 compiler is needed (`CXX` and `CC` are honored).
|
||||||
|
|
||||||
|
For numbers worth publishing, use a quiet machine (see [Getting stable numbers](../README.md#getting-stable-numbers)),
|
||||||
|
the default 30 rounds or more, and `--native` only if the other libraries were built for the same CPU.
|
||||||
|
|
||||||
|
### On GitHub-hosted runners
|
||||||
|
|
||||||
|
The workflow [json_view benchmarks](../../../.github/workflows/json_view_benchmarks.yml) runs `compare.py --download`
|
||||||
|
on demand: by hand (Actions → "json_view benchmarks" → "Run workflow"), on an x86-64 or AArch64 Ubuntu runner with GCC
|
||||||
|
or Clang, or when a pull request gets the label `benchmark`, on both architectures with GCC. The results appear as the
|
||||||
|
job summary and as an artifact. Shared runners are noisy, so these numbers show
|
||||||
|
where `json_view` stands on another architecture; they are not meant for publication.
|
||||||
|
|
||||||
|
## What is measured
|
||||||
|
|
||||||
|
`bench_view.cpp` runs four workloads on twitter, citm_catalog, canada, jeopardy, a single tweet (`status`), and a
|
||||||
|
JSON-RPC request (`rpc`):
|
||||||
|
|
||||||
|
| workload | what it does |
|
||||||
|
|---|---|
|
||||||
|
| parse | build and free a document |
|
||||||
|
| traverse | parse, then visit every value, convert every number, touch every string and key |
|
||||||
|
| select | parse, then read a few fields per record (e.g. id, user name, and retweet count of each tweet) |
|
||||||
|
| dump | serialize a parsed document (compact) |
|
||||||
|
|
||||||
|
`bench_corpus.cpp` runs parse, traverse, and dump on any list of files, so that no library is tuned to a handful of
|
||||||
|
documents. Its dump also writes the numbers as they are in the input: `json_view` with `number_format::source`, and
|
||||||
|
yyjson with numbers read as raw text (`YYJSON_READ_NUMBER_AS_RAW`), without converting them.
|
||||||
|
|
||||||
|
`bench_edit.cpp` measures read-modify-write: parse, apply the same logical edits with each library's own API, and
|
||||||
|
serialize (compact). Workloads: `patch` (a handful of edits at fixed places) and `update` (edits in every record).
|
||||||
|
An editable `json_document` edits in place; yyjson copies its immutable document into a mutable one first
|
||||||
|
(`yyjson_doc_mut_copy`); Boost.JSON and `json::parse` build mutable DOMs; simdjson cannot edit a document. All
|
||||||
|
outputs are checked to describe the same value.
|
||||||
|
|
||||||
|
Before anything is timed, all engines must accept each document and agree on the traversal: the number of values, the
|
||||||
|
bytes of all strings and keys, and the sum of all numbers. All engines run interleaved in every round, and the best
|
||||||
|
round is reported, as time and as a factor of the `json_view` time (below 1 means faster than `json_view`). Each timed
|
||||||
|
call follows an untimed call of the same engine: otherwise the engine after `json::parse` pays for the allocator
|
||||||
|
cleaning up the tens of thousands of nodes `json::parse` just freed (with glibc, this made `json_view` look 1.7 times
|
||||||
|
slower on citm_catalog traverse).
|
||||||
|
|
||||||
|
The engines do not all offer the same features, which the numbers should be read with:
|
||||||
|
|
||||||
|
| engine | document | random access | editable | notes |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `json_view` | immutable index into the text | yes | no | a fresh document per parse; "reused" parses into the same document |
|
||||||
|
| yyjson | immutable (`yyjson_read`) | yes | via a mutable copy | |
|
||||||
|
| simdjson DOM | immutable, parser reused | yes | no | "fresh" uses a new parser per parse |
|
||||||
|
| simdjson On-Demand | none: forward-only, lazy | no | no | only traverse and select |
|
||||||
|
| Boost.JSON | owning, mutable DOM | yes | yes | monotonic resource |
|
||||||
|
| `json::parse` | owning, mutable DOM | yes | yes | |
|
||||||
|
|
||||||
|
Reusing memory matters as much as the parser. simdjson DOM reuses its parser, so it writes into memory it already
|
||||||
|
touched; a fresh `json_view` document or yyjson document gets new memory for every parse. On Linux, glibc returns large
|
||||||
|
blocks to the system when they are freed, so every fresh parse of a large document pays a page fault per 4 KiB page:
|
||||||
|
on x86-64 Linux, a fresh `json_view` parse of jeopardy took about twice as long as a reused one. On macOS on Apple
|
||||||
|
silicon, with 16 KiB pages, the difference is much smaller. Compare "json_view (reused)" with "simdjson DOM", and the
|
||||||
|
fresh `json_view` with "simdjson DOM (fresh)" and yyjson.
|
||||||
|
|
||||||
|
## Published results
|
||||||
|
|
||||||
|
Results are only published with the file `compare.py` wrote, which names the machine and the versions; see
|
||||||
|
`results/`. Numbers from one machine and compiler do not carry over to another: rerun the script.
|
||||||
@@ -0,0 +1,348 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// Corpus benchmark: the read-only workloads of bench_view.cpp on any list of
|
||||||
|
// JSON files (for example the benchmark sets of simdjson and yyjson).
|
||||||
|
//
|
||||||
|
// ./bench_corpus [--rounds N] file...
|
||||||
|
//
|
||||||
|
// For every file, all engines must accept it and agree on a traversal (value
|
||||||
|
// count, string bytes, sum of numbers) before anything is timed. Workloads:
|
||||||
|
// parse (build and free a document), traverse (visit every value, convert
|
||||||
|
// every number), dump (compact), and for json_view also dump with the source
|
||||||
|
// number text, compared with yyjson writing numbers read as raw text
|
||||||
|
// (YYJSON_READ_NUMBER_AS_RAW). Results go to bench_corpus.csv.
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
#include <boost/json.hpp>
|
||||||
|
#include <boost/json/src.hpp>
|
||||||
|
#endif
|
||||||
|
#include <simdjson.h>
|
||||||
|
#include <yyjson.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <functional>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using nlohmann::json;
|
||||||
|
using nlohmann::json_document;
|
||||||
|
using nlohmann::json_view;
|
||||||
|
|
||||||
|
static volatile double g_sink;
|
||||||
|
|
||||||
|
struct stats
|
||||||
|
{
|
||||||
|
double num = 0;
|
||||||
|
std::size_t str = 0, nodes = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void walk(json_view v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.type())
|
||||||
|
{
|
||||||
|
case json::value_t::object:
|
||||||
|
for (auto it = v.begin(); it != v.end(); ++it)
|
||||||
|
{
|
||||||
|
st.str += it.key().size();
|
||||||
|
walk(*it, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::array:
|
||||||
|
for (const json_view e : v)
|
||||||
|
{
|
||||||
|
walk(e, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::string:
|
||||||
|
st.str += v.get_string().size();
|
||||||
|
break;
|
||||||
|
case json::value_t::number_integer:
|
||||||
|
st.num += static_cast<double>(v.get<std::int64_t>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_unsigned:
|
||||||
|
st.num += static_cast<double>(v.get<std::uint64_t>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_float:
|
||||||
|
st.num += v.get<double>();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk(yyjson_val* v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (yyjson_get_type(v))
|
||||||
|
{
|
||||||
|
case YYJSON_TYPE_OBJ:
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* k, * val;
|
||||||
|
yyjson_obj_foreach(v, idx, max, k, val)
|
||||||
|
{
|
||||||
|
st.str += yyjson_get_len(k);
|
||||||
|
walk(val, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case YYJSON_TYPE_ARR:
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* val;
|
||||||
|
yyjson_arr_foreach(v, idx, max, val)
|
||||||
|
{
|
||||||
|
walk(val, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case YYJSON_TYPE_STR:
|
||||||
|
st.str += yyjson_get_len(v);
|
||||||
|
break;
|
||||||
|
case YYJSON_TYPE_NUM:
|
||||||
|
st.num += yyjson_is_sint(v) ? static_cast<double>(yyjson_get_sint(v)) : yyjson_is_uint(v) ? static_cast<double>(yyjson_get_uint(v)) : yyjson_get_real(v);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk(simdjson::dom::element e, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (e.type())
|
||||||
|
{
|
||||||
|
case simdjson::dom::element_type::OBJECT:
|
||||||
|
for (auto f : simdjson::dom::object(e))
|
||||||
|
{
|
||||||
|
st.str += f.key.size();
|
||||||
|
walk(f.value, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::ARRAY:
|
||||||
|
for (auto c : simdjson::dom::array(e))
|
||||||
|
{
|
||||||
|
walk(c, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::STRING:
|
||||||
|
st.str += std::string_view(e).size();
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::INT64:
|
||||||
|
st.num += static_cast<double>(int64_t(e));
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::UINT64:
|
||||||
|
st.num += static_cast<double>(uint64_t(e));
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::DOUBLE:
|
||||||
|
st.num += double(e);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
static void walk(const boost::json::value& v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.kind())
|
||||||
|
{
|
||||||
|
case boost::json::kind::object:
|
||||||
|
for (const auto& kv : v.get_object())
|
||||||
|
{
|
||||||
|
st.str += kv.key().size();
|
||||||
|
walk(kv.value(), st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case boost::json::kind::array:
|
||||||
|
for (const auto& c : v.get_array())
|
||||||
|
{
|
||||||
|
walk(c, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case boost::json::kind::string:
|
||||||
|
st.str += v.get_string().size();
|
||||||
|
break;
|
||||||
|
case boost::json::kind::int64:
|
||||||
|
st.num += static_cast<double>(v.get_int64());
|
||||||
|
break;
|
||||||
|
case boost::json::kind::uint64:
|
||||||
|
st.num += static_cast<double>(v.get_uint64());
|
||||||
|
break;
|
||||||
|
case boost::json::kind::double_:
|
||||||
|
st.num += v.get_double();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static std::string slurp(const std::string& p)
|
||||||
|
{
|
||||||
|
std::ifstream f(p, std::ios::binary);
|
||||||
|
if (!f)
|
||||||
|
{
|
||||||
|
std::fprintf(stderr, "cannot open %s\n", p.c_str());
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << f.rdbuf();
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool same(const stats& a, const stats& b)
|
||||||
|
{
|
||||||
|
return a.nodes == b.nodes && a.str == b.str && (a.num == b.num || std::fabs(a.num - b.num) <= 1e-9 * std::fabs(a.num));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
int rounds = 0; // 0: by size
|
||||||
|
std::vector<std::string> files;
|
||||||
|
for (int i = 1; i < argc; ++i)
|
||||||
|
{
|
||||||
|
if (std::strcmp(argv[i], "--rounds") == 0 && i + 1 < argc)
|
||||||
|
{
|
||||||
|
// (clamped: 0 rounds by size, at most a million)
|
||||||
|
rounds = static_cast<int>((std::min)((std::max)(std::strtol(argv[++i], nullptr, 10), 0L), 1000000L));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
files.push_back(argv[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::FILE* csv = std::fopen("bench_corpus.csv", "w");
|
||||||
|
if (csv == nullptr)
|
||||||
|
{
|
||||||
|
std::perror("bench_corpus.csv");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::fprintf(csv, "file,bytes,workload,engine,ns\n");
|
||||||
|
json_document reused;
|
||||||
|
simdjson::dom::parser sj;
|
||||||
|
for (const auto& path : files)
|
||||||
|
{
|
||||||
|
const std::string s = slurp(path);
|
||||||
|
const std::string name = path.substr(path.rfind('/') + 1);
|
||||||
|
const simdjson::padded_string ps(s);
|
||||||
|
|
||||||
|
// all engines must agree before timing
|
||||||
|
stats a, b, c, d;
|
||||||
|
const json_document doc = json_document::parse(s);
|
||||||
|
walk(doc.root(), a);
|
||||||
|
yyjson_doc* y = yyjson_read(s.data(), s.size(), 0);
|
||||||
|
auto sjr = sj.parse(ps);
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
boost::json::parse_options opt;
|
||||||
|
opt.numbers = boost::json::number_precision::precise;
|
||||||
|
boost::json::monotonic_resource mr0;
|
||||||
|
const boost::json::value bv = boost::json::parse(s, &mr0, opt);
|
||||||
|
#endif
|
||||||
|
if (y == nullptr || sjr.error())
|
||||||
|
{
|
||||||
|
std::printf("%-34s skipped (an engine rejects it)\n", name.c_str());
|
||||||
|
yyjson_doc_free(y);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
walk(yyjson_doc_get_root(y), b);
|
||||||
|
walk(sjr.value_unsafe(), c);
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
walk(bv, d);
|
||||||
|
#else
|
||||||
|
d = a;
|
||||||
|
#endif
|
||||||
|
yyjson_doc_free(y);
|
||||||
|
const bool ok = same(a, b) && same(a, c) && same(a, d);
|
||||||
|
|
||||||
|
const int r = rounds > 0 ? rounds : static_cast<int>(std::max<std::size_t>(3, std::min<std::size_t>(60, 400000000 / (s.size() + 1))));
|
||||||
|
struct engine
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::function<void()> fn;
|
||||||
|
};
|
||||||
|
json_document vd = json_document::parse(s);
|
||||||
|
yyjson_doc* yd = yyjson_read(s.data(), s.size(), 0);
|
||||||
|
yyjson_doc* yd_raw = yyjson_read(s.data(), s.size(), YYJSON_READ_NUMBER_AS_RAW);
|
||||||
|
simdjson::dom::parser sjd;
|
||||||
|
const simdjson::dom::element se = sjd.parse(ps).value_unsafe();
|
||||||
|
const std::vector<std::pair<std::string, std::vector<engine>>> workloads =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"parse", {
|
||||||
|
{"json_view", [&] { auto x = json_document::parse(s); g_sink = static_cast<double>(x.node_count()); }},
|
||||||
|
{"json_view (reused)", [&] { reused.read(s); g_sink = static_cast<double>(reused.node_count()); }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* x = yyjson_read(s.data(), s.size(), 0); g_sink = static_cast<double>(yyjson_doc_get_val_count(x)); yyjson_doc_free(x); }},
|
||||||
|
{"simdjson DOM", [&] { auto e = sj.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||||
|
{"simdjson DOM (fresh)", [&] { simdjson::dom::parser p; auto e = p.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); g_sink = v.is_object(); }},
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"traverse", {
|
||||||
|
{"json_view", [&] { auto x = json_document::parse(s); stats st; walk(x.root(), st); g_sink = st.num; }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* x = yyjson_read(s.data(), s.size(), 0); stats st; walk(yyjson_doc_get_root(x), st); g_sink = st.num; yyjson_doc_free(x); }},
|
||||||
|
{"simdjson DOM", [&] { stats st; walk(sj.parse(ps).value_unsafe(), st); g_sink = st.num; }},
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); stats st; walk(v, st); g_sink = st.num; }},
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dump", {
|
||||||
|
{"json_view", [&] { std::string o = vd.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"yyjson", [&] { std::size_t n = 0; char* o = yyjson_write(yd, 0, &n); g_sink = static_cast<double>(n); std::free(o); }},
|
||||||
|
{"simdjson DOM", [&] { std::string o = simdjson::to_string(se); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"json_view (source numbers)", [&] { std::string o = vd.root().dump(-1, ' ', false, json_view::number_format::source); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"yyjson (raw numbers)", [&] { std::size_t n = 0; char* o = yyjson_write(yd_raw, 0, &n); g_sink = static_cast<double>(n); std::free(o); }},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
std::printf("%-34s %9zu B%s\n", name.c_str(), s.size(), ok ? "" : " [ENGINES DISAGREE]");
|
||||||
|
for (const auto& wl : workloads)
|
||||||
|
{
|
||||||
|
std::vector<double> best(wl.second.size(), 1e300);
|
||||||
|
for (int i = 0; i < r; ++i)
|
||||||
|
{
|
||||||
|
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
||||||
|
{
|
||||||
|
// an untimed call first: whatever the previous engine left to the allocator
|
||||||
|
// (e.g. thousands of freed json nodes) is cleaned up here, not in the timing
|
||||||
|
wl.second[k].fn();
|
||||||
|
const auto t0 = std::chrono::steady_clock::now();
|
||||||
|
wl.second[k].fn();
|
||||||
|
best[k] = std::min(best[k], std::chrono::duration<double, std::nano>(std::chrono::steady_clock::now() - t0).count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::printf(" %-9s", wl.first.c_str());
|
||||||
|
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
||||||
|
{
|
||||||
|
std::printf(" %s %.2f GB/s (%.2fx)", wl.second[k].name.c_str(), static_cast<double>(s.size()) / best[k], best[k] / best[0]);
|
||||||
|
std::fprintf(csv, "%s,%zu,%s,%s,%.1f\n", name.c_str(), s.size(), wl.first.c_str(), wl.second[k].name.c_str(), best[k]);
|
||||||
|
}
|
||||||
|
std::printf("\n");
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
yyjson_doc_free(yd);
|
||||||
|
yyjson_doc_free(yd_raw);
|
||||||
|
}
|
||||||
|
std::fclose(csv);
|
||||||
|
}
|
||||||
@@ -0,0 +1,589 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// Read-modify-write benchmark: parse a document, apply the same logical edits
|
||||||
|
// with each library's own API, and serialize it (compact).
|
||||||
|
//
|
||||||
|
// json_view json_editable_document: edits in place, unchanged values stay in the index
|
||||||
|
// yyjson yyjson_read + yyjson_doc_mut_copy (the way to edit a parsed document)
|
||||||
|
// Boost.JSON parse into a mutable DOM (monotonic resource, precise numbers), serialize
|
||||||
|
// json::parse nlohmann::json today
|
||||||
|
// simdjson has no mutable document and is not part of this comparison.
|
||||||
|
//
|
||||||
|
// Workloads:
|
||||||
|
// patch a handful of edits at fixed places (scalars, a new member, a new array element)
|
||||||
|
// update edits in every record (twitter: 100 statuses, citm: 243 performances,
|
||||||
|
// canada: 480 rings, jeopardy: 216,930 questions): set scalars, erase a
|
||||||
|
// member, add a member (canada: replace the first point of every ring)
|
||||||
|
//
|
||||||
|
// Build: see README.md (same flags as bench_view.cpp).
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
#include <boost/json.hpp>
|
||||||
|
#include <boost/json/src.hpp>
|
||||||
|
#endif
|
||||||
|
#include <yyjson.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <fstream>
|
||||||
|
#include <functional>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
using nlohmann::json;
|
||||||
|
using nlohmann::json_editable_document;
|
||||||
|
using nlohmann::json_editable_view;
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
namespace bj = boost::json;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static volatile std::size_t g_sink;
|
||||||
|
|
||||||
|
// ---------------- json_view ----------------
|
||||||
|
|
||||||
|
static std::string edit_view(const std::string& name, const std::string& s, bool update)
|
||||||
|
{
|
||||||
|
json_editable_document d = json_editable_document::parse(s);
|
||||||
|
const json_editable_view r = d.root();
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
std::int64_t i = 0;
|
||||||
|
for (const json_editable_view st : r["statuses"])
|
||||||
|
{
|
||||||
|
d.set(st, "retweet_count", i++);
|
||||||
|
d.set(st, "favorited", true);
|
||||||
|
d.set(st, "text", "redacted");
|
||||||
|
d.erase(st, "entities");
|
||||||
|
d.set(st, "edited", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d.set(r["search_metadata"], "count", 200);
|
||||||
|
d.set(r["statuses"][0], "text", "patched");
|
||||||
|
d.set(r["statuses"][0]["user"], "followers_count", 1);
|
||||||
|
d.set(r["statuses"][99], "favorited", true);
|
||||||
|
d.set(r, "patched", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (const json_editable_view p : r["performances"])
|
||||||
|
{
|
||||||
|
d.set(p, "name", "performance");
|
||||||
|
d.set(p, "start", p["start"].get<std::int64_t>() + 1);
|
||||||
|
d.erase(p, "seatMapImage");
|
||||||
|
d.set(p, "edited", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d.set(r["events"]["138586341"], "name", "patched");
|
||||||
|
d.set(r["performances"][0], "start", 0);
|
||||||
|
d.set(r["venueNames"], "PLEYEL_PLEYEL", "Salle");
|
||||||
|
d.set(r, "patched", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
const json_editable_view coords = r["features"][0]["geometry"]["coordinates"];
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (const json_editable_view ring : coords)
|
||||||
|
{
|
||||||
|
d.set(ring, 0, json::array({0.5, 0.5}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d.set(r["features"][0]["properties"], "name", "patched");
|
||||||
|
d.set(r, "type", "FeatureCollection2");
|
||||||
|
d.set(coords[0], 0, json::array({0.0, 0.0}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (const json_editable_view q : r)
|
||||||
|
{
|
||||||
|
d.set(q, "value", "$1");
|
||||||
|
d.erase(q, "air_date");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d.set(r[0], "value", "$0");
|
||||||
|
d.set(r[100000], "answer", "patched");
|
||||||
|
d.set(r[216929], "round", "x");
|
||||||
|
d.push_back(r, json::object({{"category", "NEW"}, {"value", "$5"}}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "status")
|
||||||
|
{
|
||||||
|
d.set(r, "retweet_count", 1);
|
||||||
|
d.set(r["user"], "name", "x");
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
d.set(r, "id", 4);
|
||||||
|
d.set(r["params"], "subtrahend", 24);
|
||||||
|
}
|
||||||
|
return r.dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- nlohmann::json ----------------
|
||||||
|
|
||||||
|
static std::string edit_json(const std::string& name, const std::string& s, bool update)
|
||||||
|
{
|
||||||
|
json r = json::parse(s);
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
std::int64_t i = 0;
|
||||||
|
for (auto& st : r["statuses"])
|
||||||
|
{
|
||||||
|
st["retweet_count"] = i++;
|
||||||
|
st["favorited"] = true;
|
||||||
|
st["text"] = "redacted";
|
||||||
|
st.erase("entities");
|
||||||
|
st["edited"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
r["search_metadata"]["count"] = 200;
|
||||||
|
r["statuses"][0]["text"] = "patched";
|
||||||
|
r["statuses"][0]["user"]["followers_count"] = 1;
|
||||||
|
r["statuses"][99]["favorited"] = true;
|
||||||
|
r["patched"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (auto& p : r["performances"])
|
||||||
|
{
|
||||||
|
p["name"] = "performance";
|
||||||
|
p["start"] = p["start"].get<std::int64_t>() + 1;
|
||||||
|
p.erase("seatMapImage");
|
||||||
|
p["edited"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
r["events"]["138586341"]["name"] = "patched";
|
||||||
|
r["performances"][0]["start"] = 0;
|
||||||
|
r["venueNames"]["PLEYEL_PLEYEL"] = "Salle";
|
||||||
|
r["patched"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
json& coords = r["features"][0]["geometry"]["coordinates"];
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (auto& ring : coords)
|
||||||
|
{
|
||||||
|
ring[0] = json::array({0.5, 0.5});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
r["features"][0]["properties"]["name"] = "patched";
|
||||||
|
r["type"] = "FeatureCollection2";
|
||||||
|
coords[0][0] = json::array({0.0, 0.0});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (auto& q : r)
|
||||||
|
{
|
||||||
|
q["value"] = "$1";
|
||||||
|
q.erase("air_date");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
r[0]["value"] = "$0";
|
||||||
|
r[100000]["answer"] = "patched";
|
||||||
|
r[216929]["round"] = "x";
|
||||||
|
r.push_back(json::object({{"category", "NEW"}, {"value", "$5"}}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "status")
|
||||||
|
{
|
||||||
|
r["retweet_count"] = 1;
|
||||||
|
r["user"]["name"] = "x";
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
r["id"] = 4;
|
||||||
|
r["params"]["subtrahend"] = 24;
|
||||||
|
}
|
||||||
|
return r.dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- yyjson ----------------
|
||||||
|
|
||||||
|
static std::string edit_yyjson(const std::string& name, const std::string& s, bool update)
|
||||||
|
{
|
||||||
|
yyjson_doc* idoc = yyjson_read(s.data(), s.size(), 0);
|
||||||
|
yyjson_mut_doc* d = yyjson_doc_mut_copy(idoc, nullptr);
|
||||||
|
yyjson_doc_free(idoc);
|
||||||
|
yyjson_mut_val* r = yyjson_mut_doc_get_root(d);
|
||||||
|
auto get = [](yyjson_mut_val * o, const char* k)
|
||||||
|
{
|
||||||
|
return yyjson_mut_obj_get(o, k);
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
yyjson_mut_val* sts = get(r, "statuses");
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_mut_val* st;
|
||||||
|
std::int64_t i = 0;
|
||||||
|
yyjson_mut_arr_foreach(sts, idx, max, st)
|
||||||
|
{
|
||||||
|
yyjson_mut_set_sint(get(st, "retweet_count"), i++);
|
||||||
|
yyjson_mut_set_bool(get(st, "favorited"), true);
|
||||||
|
yyjson_mut_set_str(get(st, "text"), "redacted");
|
||||||
|
yyjson_mut_obj_remove_key(st, "entities");
|
||||||
|
yyjson_mut_obj_add_bool(d, st, "edited", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yyjson_mut_set_sint(get(get(r, "search_metadata"), "count"), 200);
|
||||||
|
yyjson_mut_val* s0 = yyjson_mut_arr_get(sts, 0);
|
||||||
|
yyjson_mut_set_str(get(s0, "text"), "patched");
|
||||||
|
yyjson_mut_set_sint(get(get(s0, "user"), "followers_count"), 1);
|
||||||
|
yyjson_mut_set_bool(get(yyjson_mut_arr_get(sts, 99), "favorited"), true);
|
||||||
|
yyjson_mut_obj_add_bool(d, r, "patched", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_mut_val* p;
|
||||||
|
yyjson_mut_arr_foreach(get(r, "performances"), idx, max, p)
|
||||||
|
{
|
||||||
|
yyjson_mut_set_str(get(p, "name"), "performance");
|
||||||
|
yyjson_mut_val* start = get(p, "start");
|
||||||
|
yyjson_mut_set_sint(start, yyjson_mut_get_sint(start) + 1);
|
||||||
|
yyjson_mut_obj_remove_key(p, "seatMapImage");
|
||||||
|
yyjson_mut_obj_add_bool(d, p, "edited", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yyjson_mut_set_str(get(get(get(r, "events"), "138586341"), "name"), "patched");
|
||||||
|
yyjson_mut_set_sint(get(yyjson_mut_arr_get(get(r, "performances"), 0), "start"), 0);
|
||||||
|
yyjson_mut_set_str(get(get(r, "venueNames"), "PLEYEL_PLEYEL"), "Salle");
|
||||||
|
yyjson_mut_obj_add_bool(d, r, "patched", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
yyjson_mut_val* f0 = yyjson_mut_arr_get(get(r, "features"), 0);
|
||||||
|
yyjson_mut_val* coords = get(get(f0, "geometry"), "coordinates");
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
static const double half[2] = {0.5, 0.5};
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_mut_val* ring;
|
||||||
|
yyjson_mut_arr_foreach(coords, idx, max, ring)
|
||||||
|
{
|
||||||
|
yyjson_mut_arr_replace(ring, 0, yyjson_mut_arr_with_real(d, half, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
static const double zero[2] = {0.0, 0.0};
|
||||||
|
yyjson_mut_set_str(get(get(f0, "properties"), "name"), "patched");
|
||||||
|
yyjson_mut_set_str(get(r, "type"), "FeatureCollection2");
|
||||||
|
yyjson_mut_arr_replace(yyjson_mut_arr_get(coords, 0), 0, yyjson_mut_arr_with_real(d, zero, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_mut_val* q;
|
||||||
|
yyjson_mut_arr_foreach(r, idx, max, q)
|
||||||
|
{
|
||||||
|
yyjson_mut_set_str(get(q, "value"), "$1");
|
||||||
|
yyjson_mut_obj_remove_key(q, "air_date");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yyjson_mut_set_str(get(yyjson_mut_arr_get(r, 0), "value"), "$0");
|
||||||
|
yyjson_mut_set_str(get(yyjson_mut_arr_get(r, 100000), "answer"), "patched");
|
||||||
|
yyjson_mut_set_str(get(yyjson_mut_arr_get(r, 216929), "round"), "x");
|
||||||
|
yyjson_mut_val* o = yyjson_mut_obj(d);
|
||||||
|
yyjson_mut_obj_add_str(d, o, "category", "NEW");
|
||||||
|
yyjson_mut_obj_add_str(d, o, "value", "$5");
|
||||||
|
yyjson_mut_arr_append(r, o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "status")
|
||||||
|
{
|
||||||
|
yyjson_mut_set_sint(get(r, "retweet_count"), 1);
|
||||||
|
yyjson_mut_set_str(get(get(r, "user"), "name"), "x");
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
yyjson_mut_set_sint(get(r, "id"), 4);
|
||||||
|
yyjson_mut_set_sint(get(get(r, "params"), "subtrahend"), 24);
|
||||||
|
}
|
||||||
|
std::size_t n = 0;
|
||||||
|
char* out = yyjson_mut_write(d, 0, &n);
|
||||||
|
std::string result(out, n);
|
||||||
|
std::free(out);
|
||||||
|
yyjson_mut_doc_free(d);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
// ---------------- Boost.JSON ----------------
|
||||||
|
|
||||||
|
static std::string edit_boost(const std::string& name, const std::string& s, bool update)
|
||||||
|
{
|
||||||
|
bj::monotonic_resource mr;
|
||||||
|
bj::parse_options opt;
|
||||||
|
opt.numbers = bj::number_precision::precise; // correctly rounded, like the others
|
||||||
|
bj::value v = bj::parse(s, &mr, opt);
|
||||||
|
bj::object* const obj = v.if_object(); // nullptr for jeopardy (an array)
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
bj::array& sts = (*obj)["statuses"].as_array();
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
std::int64_t i = 0;
|
||||||
|
for (auto& e : sts)
|
||||||
|
{
|
||||||
|
bj::object& st = e.as_object();
|
||||||
|
st["retweet_count"] = i++;
|
||||||
|
st["favorited"] = true;
|
||||||
|
st["text"] = "redacted";
|
||||||
|
st.erase("entities");
|
||||||
|
st["edited"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(*obj)["search_metadata"].as_object()["count"] = 200;
|
||||||
|
bj::object& s0 = sts[0].as_object();
|
||||||
|
s0["text"] = "patched";
|
||||||
|
s0["user"].as_object()["followers_count"] = 1;
|
||||||
|
sts[99].as_object()["favorited"] = true;
|
||||||
|
(*obj)["patched"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (auto& e : (*obj)["performances"].as_array())
|
||||||
|
{
|
||||||
|
bj::object& p = e.as_object();
|
||||||
|
p["name"] = "performance";
|
||||||
|
p["start"] = p["start"].as_int64() + 1;
|
||||||
|
p.erase("seatMapImage");
|
||||||
|
p["edited"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(*obj)["events"].as_object()["138586341"].as_object()["name"] = "patched";
|
||||||
|
(*obj)["performances"].as_array()[0].as_object()["start"] = 0;
|
||||||
|
(*obj)["venueNames"].as_object()["PLEYEL_PLEYEL"] = "Salle";
|
||||||
|
(*obj)["patched"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
bj::object& f0 = (*obj)["features"].as_array()[0].as_object();
|
||||||
|
bj::array& coords = f0["geometry"].as_object()["coordinates"].as_array();
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (auto& ring : coords)
|
||||||
|
{
|
||||||
|
ring.as_array()[0] = bj::array({0.5, 0.5});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
f0["properties"].as_object()["name"] = "patched";
|
||||||
|
(*obj)["type"] = "FeatureCollection2";
|
||||||
|
coords[0].as_array()[0] = bj::array({0.0, 0.0});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
bj::array& a = v.as_array();
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (auto& e : a)
|
||||||
|
{
|
||||||
|
bj::object& q = e.as_object();
|
||||||
|
q["value"] = "$1";
|
||||||
|
q.erase("air_date");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
a[0].as_object()["value"] = "$0";
|
||||||
|
a[100000].as_object()["answer"] = "patched";
|
||||||
|
a[216929].as_object()["round"] = "x";
|
||||||
|
a.push_back(bj::object({{"category", "NEW"}, {"value", "$5"}}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "status")
|
||||||
|
{
|
||||||
|
(*obj)["retweet_count"] = 1;
|
||||||
|
(*obj)["user"].as_object()["name"] = "x";
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
(*obj)["id"] = 4;
|
||||||
|
(*obj)["params"].as_object()["subtrahend"] = 24;
|
||||||
|
}
|
||||||
|
return bj::serialize(v);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ---------------- harness ----------------
|
||||||
|
|
||||||
|
static std::string slurp(const std::string& p)
|
||||||
|
{
|
||||||
|
std::ifstream f(p, std::ios::binary);
|
||||||
|
if (!f)
|
||||||
|
{
|
||||||
|
std::fprintf(stderr, "cannot open %s\n", p.c_str());
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << f.rdbuf();
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
if (argc < 2)
|
||||||
|
{
|
||||||
|
std::fprintf(stderr, "usage: %s <json_test_data directory> [rounds] [document]\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const std::string T = std::string(argv[1]) + "/";
|
||||||
|
// (clamped to 1 to a million)
|
||||||
|
const int rounds = argc > 2 ? static_cast<int>((std::min)((std::max)(std::strtol(argv[2], nullptr, 10), 1L), 1000000L)) : 20;
|
||||||
|
const std::string only = argc > 3 ? argv[3] : "";
|
||||||
|
struct doc
|
||||||
|
{
|
||||||
|
std::string name, text;
|
||||||
|
int batch;
|
||||||
|
};
|
||||||
|
std::vector<doc> docs;
|
||||||
|
for (const char* f :
|
||||||
|
{"nativejson-benchmark/twitter.json", "nativejson-benchmark/citm_catalog.json", "nativejson-benchmark/canada.json", "jeopardy/jeopardy.json"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
std::string n = std::string(f).substr(std::string(f).find('/') + 1);
|
||||||
|
docs.push_back({n.substr(0, n.size() - 5), slurp(T + f), 1});
|
||||||
|
}
|
||||||
|
docs.push_back({"status", json::parse(docs[0].text)["statuses"][0].dump(), 200});
|
||||||
|
docs.push_back({"rpc", R"({"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 3})", 5000});
|
||||||
|
|
||||||
|
using fn = std::string (*)(const std::string&, const std::string&, bool);
|
||||||
|
const std::vector<std::pair<std::string, fn>> engines =
|
||||||
|
{
|
||||||
|
{"json_view", edit_view}, {"yyjson", edit_yyjson},
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
{"Boost.JSON", edit_boost},
|
||||||
|
#endif
|
||||||
|
{"json::parse", edit_json}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::FILE* csv = std::fopen("bench_edit.csv", "w");
|
||||||
|
if (csv == nullptr)
|
||||||
|
{
|
||||||
|
std::perror("bench_edit.csv");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::fprintf(csv, "doc,bytes,workload,engine,ns\n");
|
||||||
|
for (const auto& dc : docs)
|
||||||
|
{
|
||||||
|
if (!only.empty() && dc.name != only)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const bool update :
|
||||||
|
{
|
||||||
|
false, true
|
||||||
|
})
|
||||||
|
{
|
||||||
|
if (update && (dc.name == "status" || dc.name == "rpc"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// all engines must produce the same value
|
||||||
|
const json expected = json::parse(edit_json(dc.name, dc.text, update));
|
||||||
|
bool ok = true;
|
||||||
|
for (const auto& e : engines)
|
||||||
|
{
|
||||||
|
ok = ok && json::parse(e.second(dc.name, dc.text, update)) == expected;
|
||||||
|
}
|
||||||
|
std::vector<double> best(engines.size(), 1e300);
|
||||||
|
const int r = dc.text.size() > 10000000 ? std::max(3, rounds / 4) : rounds;
|
||||||
|
for (int i = 0; i < r; ++i)
|
||||||
|
{
|
||||||
|
for (std::size_t k = 0; k < engines.size(); ++k)
|
||||||
|
{
|
||||||
|
// an untimed call first: whatever the previous engine left to the allocator
|
||||||
|
// (e.g. thousands of freed json nodes) is cleaned up here, not in the timing
|
||||||
|
g_sink = engines[k].second(dc.name, dc.text, update).size();
|
||||||
|
const auto t0 = std::chrono::steady_clock::now();
|
||||||
|
for (int b = 0; b < dc.batch; ++b)
|
||||||
|
{
|
||||||
|
g_sink = engines[k].second(dc.name, dc.text, update).size();
|
||||||
|
}
|
||||||
|
const double ns = std::chrono::duration<double, std::nano>(std::chrono::steady_clock::now() - t0).count() / dc.batch;
|
||||||
|
best[k] = std::min(best[k], ns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const char* wl = update ? "update" : "patch";
|
||||||
|
std::printf("%-13s %-7s %s", dc.name.c_str(), wl, ok ? "" : "[OUTPUT MISMATCH] ");
|
||||||
|
for (std::size_t k = 0; k < engines.size(); ++k)
|
||||||
|
{
|
||||||
|
const double us = best[k] / 1e3;
|
||||||
|
std::printf(" %s %.*fus (%.2fx)", engines[k].first.c_str(), us < 10 ? 3 : (us < 1000 ? 1 : 0), us, best[k] / best[0]);
|
||||||
|
std::fprintf(csv, "%s,%zu,%s,%s,%.1f\n", dc.name.c_str(), dc.text.size(), wl, engines[k].first.c_str(), best[k]);
|
||||||
|
}
|
||||||
|
std::printf("\n");
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::fclose(csv);
|
||||||
|
}
|
||||||
@@ -0,0 +1,755 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// Same-feature-set benchmark: read-only JSON documents with random access.
|
||||||
|
//
|
||||||
|
// json_view nlohmann/json_view.hpp (fresh document per parse / reused)
|
||||||
|
// yyjson yyjson_read(): immutable document, random access
|
||||||
|
// simdjson DOM dom::parser (reused, as recommended; "fresh": a new parser
|
||||||
|
// per parse): immutable, random access
|
||||||
|
// references (different feature sets):
|
||||||
|
// simdjson OD On-Demand: forward-only, lazy
|
||||||
|
// Boost.JSON owning, mutable DOM (monotonic resource)
|
||||||
|
// json::parse owning, mutable DOM (nlohmann today)
|
||||||
|
//
|
||||||
|
// Workloads: parse (build + free), traverse (visit everything, convert every
|
||||||
|
// number, touch every string and key), select (a few fields per document),
|
||||||
|
// dump (compact serialization of the parsed document).
|
||||||
|
// All engines run interleaved in every round, each timed call after an untimed
|
||||||
|
// one of the same engine; the best round is reported.
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
#include <boost/json.hpp>
|
||||||
|
#include <boost/json/src.hpp>
|
||||||
|
#endif
|
||||||
|
#include <simdjson.h>
|
||||||
|
#include <yyjson.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <fstream>
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
using nlohmann::json;
|
||||||
|
using nlohmann::json_document;
|
||||||
|
using nlohmann::json_view;
|
||||||
|
|
||||||
|
static volatile double g_sink;
|
||||||
|
|
||||||
|
struct stats
|
||||||
|
{
|
||||||
|
double num = 0;
|
||||||
|
std::size_t str = 0, nodes = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------- traversal ----------------
|
||||||
|
|
||||||
|
static void walk(json_view v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.type())
|
||||||
|
{
|
||||||
|
case json::value_t::object:
|
||||||
|
for (auto it = v.begin(); it != v.end(); ++it)
|
||||||
|
{
|
||||||
|
st.str += it.key().size();
|
||||||
|
walk(*it, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::array:
|
||||||
|
for (const json_view e : v)
|
||||||
|
{
|
||||||
|
walk(e, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::string:
|
||||||
|
st.str += v.get_string().size();
|
||||||
|
break;
|
||||||
|
case json::value_t::number_integer:
|
||||||
|
st.num += static_cast<double>(v.get<std::int64_t>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_unsigned:
|
||||||
|
st.num += static_cast<double>(v.get<std::uint64_t>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_float:
|
||||||
|
st.num += v.get<double>();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk(const json& j, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (j.type())
|
||||||
|
{
|
||||||
|
case json::value_t::object:
|
||||||
|
for (const auto& kv : j.get_ref<const json::object_t&>())
|
||||||
|
{
|
||||||
|
st.str += kv.first.size();
|
||||||
|
walk(kv.second, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::array:
|
||||||
|
for (const auto& e : j.get_ref<const json::array_t&>())
|
||||||
|
{
|
||||||
|
walk(e, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::string:
|
||||||
|
st.str += j.get_ref<const std::string&>().size();
|
||||||
|
break;
|
||||||
|
case json::value_t::number_integer:
|
||||||
|
st.num += static_cast<double>(*j.get_ptr<const json::number_integer_t*>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_unsigned:
|
||||||
|
st.num += static_cast<double>(*j.get_ptr<const json::number_unsigned_t*>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_float:
|
||||||
|
st.num += *j.get_ptr<const json::number_float_t*>();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk(yyjson_val* v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (yyjson_get_type(v))
|
||||||
|
{
|
||||||
|
case YYJSON_TYPE_OBJ:
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* k, * val;
|
||||||
|
yyjson_obj_foreach(v, idx, max, k, val)
|
||||||
|
{
|
||||||
|
st.str += yyjson_get_len(k);
|
||||||
|
walk(val, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case YYJSON_TYPE_ARR:
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* val;
|
||||||
|
yyjson_arr_foreach(v, idx, max, val)
|
||||||
|
{
|
||||||
|
walk(val, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case YYJSON_TYPE_STR:
|
||||||
|
st.str += yyjson_get_len(v);
|
||||||
|
break;
|
||||||
|
case YYJSON_TYPE_NUM:
|
||||||
|
if (yyjson_is_sint(v))
|
||||||
|
{
|
||||||
|
st.num += static_cast<double>(yyjson_get_sint(v));
|
||||||
|
}
|
||||||
|
else if (yyjson_is_uint(v))
|
||||||
|
{
|
||||||
|
st.num += static_cast<double>(yyjson_get_uint(v));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
st.num += yyjson_get_real(v);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk(simdjson::dom::element e, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (e.type())
|
||||||
|
{
|
||||||
|
case simdjson::dom::element_type::OBJECT:
|
||||||
|
for (auto f : simdjson::dom::object(e))
|
||||||
|
{
|
||||||
|
st.str += f.key.size();
|
||||||
|
walk(f.value, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::ARRAY:
|
||||||
|
for (auto c : simdjson::dom::array(e))
|
||||||
|
{
|
||||||
|
walk(c, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::STRING:
|
||||||
|
st.str += std::string_view(e).size();
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::INT64:
|
||||||
|
st.num += static_cast<double>(int64_t(e));
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::UINT64:
|
||||||
|
st.num += static_cast<double>(uint64_t(e));
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::DOUBLE:
|
||||||
|
st.num += double(e);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk_od(simdjson::ondemand::value v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.type())
|
||||||
|
{
|
||||||
|
case simdjson::ondemand::json_type::object:
|
||||||
|
for (auto f : v.get_object())
|
||||||
|
{
|
||||||
|
st.str += std::string_view(f.unescaped_key()).size();
|
||||||
|
walk_od(f.value(), st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::ondemand::json_type::array:
|
||||||
|
for (auto c : v.get_array())
|
||||||
|
{
|
||||||
|
walk_od(c.value(), st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::ondemand::json_type::string:
|
||||||
|
st.str += std::string_view(v.get_string()).size();
|
||||||
|
break;
|
||||||
|
case simdjson::ondemand::json_type::number:
|
||||||
|
{
|
||||||
|
simdjson::ondemand::number n = v.get_number();
|
||||||
|
switch (n.get_number_type())
|
||||||
|
{
|
||||||
|
case simdjson::ondemand::number_type::signed_integer:
|
||||||
|
st.num += static_cast<double>(n.get_int64());
|
||||||
|
break;
|
||||||
|
case simdjson::ondemand::number_type::unsigned_integer:
|
||||||
|
st.num += static_cast<double>(n.get_uint64());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
st.num += n.get_double();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case simdjson::ondemand::json_type::boolean:
|
||||||
|
(void)bool(v.get_bool());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
(void)v.is_null();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
static void walk(const boost::json::value& v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.kind())
|
||||||
|
{
|
||||||
|
case boost::json::kind::object:
|
||||||
|
for (const auto& kv : v.get_object())
|
||||||
|
{
|
||||||
|
st.str += kv.key().size();
|
||||||
|
walk(kv.value(), st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case boost::json::kind::array:
|
||||||
|
for (const auto& c : v.get_array())
|
||||||
|
{
|
||||||
|
walk(c, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case boost::json::kind::string:
|
||||||
|
st.str += v.get_string().size();
|
||||||
|
break;
|
||||||
|
case boost::json::kind::int64:
|
||||||
|
st.num += static_cast<double>(v.get_int64());
|
||||||
|
break;
|
||||||
|
case boost::json::kind::uint64:
|
||||||
|
st.num += static_cast<double>(v.get_uint64());
|
||||||
|
break;
|
||||||
|
case boost::json::kind::double_:
|
||||||
|
st.num += v.get_double();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ---------------- selective access ----------------
|
||||||
|
// twitter: per status id, user.screen_name, retweet_count
|
||||||
|
// citm: per performance id, eventId, #seatCategories; #events
|
||||||
|
// canada: type, features[0].geometry.type, #coordinates
|
||||||
|
// jeopardy: per question: round == "Final Jeopardy!", len(category)
|
||||||
|
// status (one tweet): id, user.screen_name, retweet_count
|
||||||
|
// rpc: method, params.minuend, id
|
||||||
|
|
||||||
|
static double pick(const std::string& name, json_view r)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](json_view s)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(s["id"].get<std::uint64_t>());
|
||||||
|
acc += static_cast<double>(s["user"]["screen_name"].get_string().size());
|
||||||
|
acc += static_cast<double>(s["retweet_count"].get<std::int64_t>());
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
for (const json_view s : r["statuses"])
|
||||||
|
{
|
||||||
|
one(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
one(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
for (const json_view p : r["performances"])
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(p["id"].get<std::uint64_t>() + p["eventId"].get<std::uint64_t>() + p["seatCategories"].size());
|
||||||
|
}
|
||||||
|
acc += static_cast<double>(r["events"].size());
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
const json_view g = r["features"][0]["geometry"];
|
||||||
|
acc += static_cast<double>(r["type"].get_string().size() + g["type"].get_string().size() + g["coordinates"].size());
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
for (const json_view q : r)
|
||||||
|
{
|
||||||
|
acc += q["round"].get_string() == "Final Jeopardy!" ? 1 : 0;
|
||||||
|
acc += static_cast<double>(q["category"].get_string().size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(r["method"].get_string().size());
|
||||||
|
acc += static_cast<double>(r["params"]["minuend"].get<std::int64_t>() + r["id"].get<std::int64_t>());
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double pick(const std::string& name, const json& r)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](const json & s)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(s["id"].get<std::uint64_t>());
|
||||||
|
acc += static_cast<double>(s["user"]["screen_name"].get_ref<const std::string&>().size());
|
||||||
|
acc += static_cast<double>(s["retweet_count"].get<std::int64_t>());
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
for (const auto& s : r["statuses"])
|
||||||
|
{
|
||||||
|
one(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
one(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
for (const auto& p : r["performances"])
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(p["id"].get<std::uint64_t>() + p["eventId"].get<std::uint64_t>() + p["seatCategories"].size());
|
||||||
|
}
|
||||||
|
acc += static_cast<double>(r["events"].size());
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
const json& g = r["features"][0]["geometry"];
|
||||||
|
acc += static_cast<double>(r["type"].get_ref<const std::string&>().size() + g["type"].get_ref<const std::string&>().size() + g["coordinates"].size());
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
for (const auto& q : r)
|
||||||
|
{
|
||||||
|
acc += q["round"].get_ref<const std::string&>() == "Final Jeopardy!" ? 1 : 0;
|
||||||
|
acc += static_cast<double>(q["category"].get_ref<const std::string&>().size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(r["method"].get_ref<const std::string&>().size());
|
||||||
|
acc += static_cast<double>(r["params"]["minuend"].get<std::int64_t>() + r["id"].get<std::int64_t>());
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double pick(const std::string& name, yyjson_val* r)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
auto get = [](yyjson_val * o, const char* k)
|
||||||
|
{
|
||||||
|
return yyjson_obj_get(o, k);
|
||||||
|
};
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](yyjson_val * s)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(yyjson_get_uint(get(s, "id")));
|
||||||
|
acc += static_cast<double>(yyjson_get_len(get(get(s, "user"), "screen_name")));
|
||||||
|
acc += static_cast<double>(yyjson_get_sint(get(s, "retweet_count")));
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* s;
|
||||||
|
yyjson_arr_foreach(get(r, "statuses"), idx, max, s)
|
||||||
|
{
|
||||||
|
one(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
one(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* p;
|
||||||
|
yyjson_arr_foreach(get(r, "performances"), idx, max, p)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(yyjson_get_uint(get(p, "id")) + yyjson_get_uint(get(p, "eventId")) + yyjson_arr_size(get(p, "seatCategories")));
|
||||||
|
}
|
||||||
|
acc += static_cast<double>(yyjson_obj_size(get(r, "events")));
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
yyjson_val* g = get(yyjson_arr_get(get(r, "features"), 0), "geometry");
|
||||||
|
acc += static_cast<double>(yyjson_get_len(get(r, "type")) + yyjson_get_len(get(g, "type")) + yyjson_arr_size(get(g, "coordinates")));
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* q;
|
||||||
|
yyjson_arr_foreach(r, idx, max, q)
|
||||||
|
{
|
||||||
|
acc += yyjson_equals_str(get(q, "round"), "Final Jeopardy!") ? 1 : 0;
|
||||||
|
acc += static_cast<double>(yyjson_get_len(get(q, "category")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(yyjson_get_len(get(r, "method")));
|
||||||
|
acc += static_cast<double>(yyjson_get_sint(get(get(r, "params"), "minuend")) + yyjson_get_sint(get(r, "id")));
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double pick(const std::string& name, simdjson::dom::element r)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](simdjson::dom::element s)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(uint64_t(s["id"]));
|
||||||
|
acc += static_cast<double>(std::string_view(s["user"]["screen_name"]).size());
|
||||||
|
acc += static_cast<double>(int64_t(s["retweet_count"]));
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
for (auto s : simdjson::dom::array(r["statuses"]))
|
||||||
|
{
|
||||||
|
one(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
one(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
for (auto p : simdjson::dom::array(r["performances"]))
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(uint64_t(p["id"]) + uint64_t(p["eventId"]) + simdjson::dom::array(p["seatCategories"]).size());
|
||||||
|
}
|
||||||
|
acc += static_cast<double>(simdjson::dom::object(r["events"]).size());
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
auto g = r["features"].at(0)["geometry"];
|
||||||
|
acc += static_cast<double>(std::string_view(r["type"]).size() + std::string_view(g["type"]).size() + simdjson::dom::array(g["coordinates"]).size());
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
for (auto q : simdjson::dom::array(r))
|
||||||
|
{
|
||||||
|
acc += std::string_view(q["round"]) == "Final Jeopardy!" ? 1 : 0;
|
||||||
|
acc += static_cast<double>(std::string_view(q["category"]).size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(std::string_view(r["method"]).size());
|
||||||
|
acc += static_cast<double>(int64_t(r["params"]["minuend"]) + int64_t(r["id"]));
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double pick_od(const std::string& name, simdjson::ondemand::document& d)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](simdjson::ondemand::object s)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(uint64_t(s["id"]));
|
||||||
|
acc += static_cast<double>(std::string_view(s["user"]["screen_name"]).size());
|
||||||
|
acc += static_cast<double>(int64_t(s["retweet_count"]));
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
for (auto s : d["statuses"])
|
||||||
|
{
|
||||||
|
one(s.get_object());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
one(d.get_object());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
simdjson::ondemand::object ev = d["events"].get_object();
|
||||||
|
acc += static_cast<double>(ev.count_fields());
|
||||||
|
for (auto p : d["performances"])
|
||||||
|
{
|
||||||
|
simdjson::ondemand::object o = p.get_object();
|
||||||
|
const auto a = uint64_t(o["eventId"]) + uint64_t(o["id"]);
|
||||||
|
simdjson::ondemand::array sc = o["seatCategories"].get_array();
|
||||||
|
acc += static_cast<double>(a + sc.count_elements());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(std::string_view(d["type"]).size());
|
||||||
|
auto g = d["features"].at(0)["geometry"];
|
||||||
|
acc += static_cast<double>(std::string_view(g["type"]).size());
|
||||||
|
simdjson::ondemand::array co = g["coordinates"].get_array();
|
||||||
|
acc += static_cast<double>(co.count_elements());
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
for (auto q : d)
|
||||||
|
{
|
||||||
|
simdjson::ondemand::object o = q.get_object();
|
||||||
|
acc += static_cast<double>(std::string_view(o["category"]).size());
|
||||||
|
acc += std::string_view(o["round"]) == "Final Jeopardy!" ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(std::string_view(d["method"]).size());
|
||||||
|
acc += static_cast<double>(int64_t(d["params"]["minuend"]));
|
||||||
|
acc += static_cast<double>(int64_t(d["id"]));
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- harness ----------------
|
||||||
|
|
||||||
|
static std::string slurp(const std::string& p)
|
||||||
|
{
|
||||||
|
std::ifstream f(p, std::ios::binary);
|
||||||
|
if (!f)
|
||||||
|
{
|
||||||
|
std::fprintf(stderr, "cannot open %s\n", p.c_str());
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << f.rdbuf();
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct engine
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::function<void()> fn;
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
if (argc < 2)
|
||||||
|
{
|
||||||
|
std::fprintf(stderr, "usage: %s <json_test_data directory> [rounds] [document]\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const std::string T = std::string(argv[1]) + "/";
|
||||||
|
// (clamped to 1 to a million)
|
||||||
|
const int rounds = argc > 2 ? static_cast<int>((std::min)((std::max)(std::strtol(argv[2], nullptr, 10), 1L), 1000000L)) : 30;
|
||||||
|
const std::string only = argc > 3 ? argv[3] : "";
|
||||||
|
struct doc
|
||||||
|
{
|
||||||
|
std::string name, text;
|
||||||
|
int batch;
|
||||||
|
};
|
||||||
|
std::vector<doc> docs;
|
||||||
|
for (const char* f :
|
||||||
|
{"nativejson-benchmark/twitter.json", "nativejson-benchmark/citm_catalog.json", "nativejson-benchmark/canada.json", "jeopardy/jeopardy.json"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
std::string n = std::string(f).substr(std::string(f).find('/') + 1);
|
||||||
|
docs.push_back({n.substr(0, n.size() - 5), slurp(T + f), 1});
|
||||||
|
}
|
||||||
|
docs.push_back({"status", json::parse(docs[0].text)["statuses"][0].dump(), 200});
|
||||||
|
docs.push_back({"rpc", R"({"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 3})", 5000});
|
||||||
|
|
||||||
|
// correctness cross-check of the workloads
|
||||||
|
for (const auto& dc : docs)
|
||||||
|
{
|
||||||
|
stats a, b, c, dd;
|
||||||
|
walk(json::parse(dc.text), a);
|
||||||
|
auto d = json_document::parse(dc.text);
|
||||||
|
walk(d.root(), b);
|
||||||
|
yyjson_doc* y = yyjson_read(dc.text.data(), dc.text.size(), 0);
|
||||||
|
walk(yyjson_doc_get_root(y), c);
|
||||||
|
simdjson::dom::parser p;
|
||||||
|
walk(p.parse(dc.text).value(), dd);
|
||||||
|
const bool ok = a.nodes == b.nodes && a.nodes == c.nodes && a.nodes == dd.nodes && a.str == b.str && a.str == c.str && a.str == dd.str
|
||||||
|
&& std::fabs(a.num - b.num) <= 1e-9 * std::fabs(a.num) && std::fabs(a.num - c.num) <= 1e-9 * std::fabs(a.num);
|
||||||
|
const double pa = pick(dc.name, json::parse(dc.text)), pb = pick(dc.name, d.root()), pc = pick(dc.name, yyjson_doc_get_root(y)), pd = pick(dc.name, p.parse(dc.text).value());
|
||||||
|
std::printf("check %-13s traverse %s select %s\n", dc.name.c_str(), ok ? "OK" : "MISMATCH", (pa == pb && pa == pc && pa == pd) ? "OK" : "MISMATCH");
|
||||||
|
yyjson_doc_free(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::FILE* csv = std::fopen("bench_view.csv", "w");
|
||||||
|
if (csv == nullptr)
|
||||||
|
{
|
||||||
|
std::perror("bench_view.csv");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::fprintf(csv, "doc,bytes,workload,engine,ns\n");
|
||||||
|
json_document reused;
|
||||||
|
simdjson::dom::parser sj;
|
||||||
|
simdjson::ondemand::parser od;
|
||||||
|
for (const auto& dc : docs)
|
||||||
|
{
|
||||||
|
if (!only.empty() && dc.name != only)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::string& s = dc.text;
|
||||||
|
const simdjson::padded_string ps(s);
|
||||||
|
const std::string name = dc.name;
|
||||||
|
std::vector<std::pair<std::string, std::vector<engine>>> workloads;
|
||||||
|
|
||||||
|
workloads.push_back({"parse", {
|
||||||
|
{"json_view", [&] { auto d = json_document::parse(s); g_sink = static_cast<double>(d.node_count()); }},
|
||||||
|
{"json_view (reused)", [&] { reused.read(s); g_sink = static_cast<double>(reused.node_count()); }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = static_cast<double>(yyjson_doc_get_val_count(d)); yyjson_doc_free(d); }},
|
||||||
|
{"simdjson DOM", [&] { auto e = sj.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||||
|
{"simdjson DOM (fresh)", [&] { simdjson::dom::parser p; auto e = p.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); g_sink = v.is_object(); }},
|
||||||
|
#endif
|
||||||
|
{"json::parse", [&] { json j = json::parse(s); g_sink = static_cast<double>(j.size()); }},
|
||||||
|
}});
|
||||||
|
workloads.push_back({"traverse", {
|
||||||
|
{"json_view", [&] { auto d = json_document::parse(s); stats st; walk(d.root(), st); g_sink = st.num; }},
|
||||||
|
{"json_view (reused)", [&] { reused.read(s); stats st; walk(reused.root(), st); g_sink = st.num; }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); stats st; walk(yyjson_doc_get_root(d), st); g_sink = st.num; yyjson_doc_free(d); }},
|
||||||
|
{"simdjson DOM", [&] { stats st; walk(sj.parse(ps).value_unsafe(), st); g_sink = st.num; }},
|
||||||
|
{"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); stats st; walk_od(d.get_value().value_unsafe(), st); g_sink = st.num; }},
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); stats st; walk(v, st); g_sink = st.num; }},
|
||||||
|
#endif
|
||||||
|
{"json::parse", [&] { json j = json::parse(s); stats st; walk(j, st); g_sink = st.num; }},
|
||||||
|
}});
|
||||||
|
workloads.push_back({"select", {
|
||||||
|
{"json_view", [&] { auto d = json_document::parse(s); g_sink = pick(name, d.root()); }},
|
||||||
|
{"json_view (reused)", [&] { reused.read(s); g_sink = pick(name, reused.root()); }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = pick(name, yyjson_doc_get_root(d)); yyjson_doc_free(d); }},
|
||||||
|
{"simdjson DOM", [&] { g_sink = pick(name, sj.parse(ps).value_unsafe()); }},
|
||||||
|
{"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); g_sink = pick_od(name, d); }},
|
||||||
|
{"json::parse", [&] { json j = json::parse(s); g_sink = pick(name, j); }},
|
||||||
|
}});
|
||||||
|
{
|
||||||
|
// serialization of an already parsed document
|
||||||
|
static json_document vd;
|
||||||
|
vd.read(s);
|
||||||
|
static yyjson_doc* yd = nullptr;
|
||||||
|
if (yd)
|
||||||
|
{
|
||||||
|
yyjson_doc_free(yd);
|
||||||
|
}
|
||||||
|
yd = yyjson_read(s.data(), s.size(), 0);
|
||||||
|
static simdjson::dom::parser sjd;
|
||||||
|
static simdjson::dom::element se;
|
||||||
|
se = sjd.parse(ps).value_unsafe();
|
||||||
|
static json jd;
|
||||||
|
jd = json::parse(s);
|
||||||
|
workloads.push_back({"dump", {
|
||||||
|
{"json_view", [&] { std::string o = vd.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"yyjson", [&] { std::size_t n = 0; char* o = yyjson_write(yd, 0, &n); g_sink = static_cast<double>(n); std::free(o); }},
|
||||||
|
{"simdjson DOM", [&] { std::string o = simdjson::to_string(se); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"json::parse", [&] { std::string o = jd.dump(); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& wl : workloads)
|
||||||
|
{
|
||||||
|
std::vector<double> best(wl.second.size(), 1e300);
|
||||||
|
const int r = s.size() > 10000000 ? std::max(3, rounds / 5) : rounds;
|
||||||
|
for (int i = 0; i < r; ++i)
|
||||||
|
{
|
||||||
|
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
||||||
|
{
|
||||||
|
// an untimed call first: whatever the previous engine left to the allocator
|
||||||
|
// (e.g. thousands of freed json nodes) is cleaned up here, not in the timing
|
||||||
|
wl.second[k].fn();
|
||||||
|
const auto t0 = std::chrono::steady_clock::now();
|
||||||
|
for (int b = 0; b < dc.batch; ++b)
|
||||||
|
{
|
||||||
|
wl.second[k].fn();
|
||||||
|
}
|
||||||
|
const double ns = std::chrono::duration<double, std::nano>(std::chrono::steady_clock::now() - t0).count() / dc.batch;
|
||||||
|
best[k] = std::min(best[k], ns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const double ref = best[0];
|
||||||
|
std::printf("%-13s %-9s", dc.name.c_str(), wl.first.c_str());
|
||||||
|
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
||||||
|
{
|
||||||
|
const double us = best[k] / 1e3;
|
||||||
|
std::printf(" %s %s%s (%.2fx)", wl.second[k].name.c_str(), us >= 100 ? "" : "", (us >= 1000 ? std::to_string(static_cast<long>(us)) + "us" : (std::to_string(us).substr(0, 5) + "us")).c_str(), best[k] / ref);
|
||||||
|
std::fprintf(csv, "%s,%zu,%s,%s,%.1f\n", dc.name.c_str(), s.size(), wl.first.c_str(), wl.second[k].name.c_str(), best[k]);
|
||||||
|
}
|
||||||
|
std::printf("\n");
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::fclose(csv);
|
||||||
|
}
|
||||||
Executable
+342
@@ -0,0 +1,342 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# __ _____ _____ _____
|
||||||
|
# __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
# | | |__ | | | | | | version 3.12.0
|
||||||
|
# |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
#
|
||||||
|
# SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
"""Compare json_view with yyjson, simdjson, Boost.JSON, and json::parse.
|
||||||
|
|
||||||
|
Builds bench_view.cpp, bench_corpus.cpp, and bench_edit.cpp against the include/ directory of
|
||||||
|
this checkout, runs them, and writes the results with everything needed to
|
||||||
|
reproduce them (date, commit, CPU, OS, compiler, library versions, flags) to
|
||||||
|
results/<date>-<host>.md and .csv next to this script.
|
||||||
|
|
||||||
|
The other libraries come from the system (--system, the default: pkg-config
|
||||||
|
or Homebrew) or are downloaded as pinned releases and checked against their
|
||||||
|
SHA-256 (--download). Boost.JSON is optional: without Boost headers, its
|
||||||
|
columns are skipped, and the results say so.
|
||||||
|
|
||||||
|
Only the Python 3 standard library is used; a C++17 compiler is needed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import datetime
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import re
|
||||||
|
import shlex
|
||||||
|
import shutil
|
||||||
|
# runs only the compilers and benchmark binaries this script builds
|
||||||
|
import subprocess # nosec B404
|
||||||
|
import sys
|
||||||
|
import tarfile
|
||||||
|
import tempfile
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
REPO = os.path.abspath(os.path.join(HERE, '..', '..', '..'))
|
||||||
|
|
||||||
|
# seconds without data after which a download fails
|
||||||
|
DOWNLOAD_TIMEOUT = 60
|
||||||
|
|
||||||
|
# pinned releases for --download; the hashes are those of the archives
|
||||||
|
#
|
||||||
|
# The yyjson and simdjson archives are the tag archives GitHub generates
|
||||||
|
# (archive/refs/tags/...), whose bytes GitHub does not guarantee to be stable. A SHA-256
|
||||||
|
# mismatch for one of them means that GitHub regenerated the archive: check the upstream
|
||||||
|
# tag, then pin the new hash.
|
||||||
|
PINNED = {
|
||||||
|
'yyjson': {
|
||||||
|
'version': '0.13.0',
|
||||||
|
'url': 'https://github.com/ibireme/yyjson/archive/refs/tags/0.13.0.tar.gz',
|
||||||
|
'sha256': '34e0f62a2bc11ab20d601e8ca1cc2b2079503aa45119a19133d89d19b94a0fae',
|
||||||
|
'dir': 'yyjson-0.13.0',
|
||||||
|
},
|
||||||
|
'simdjson': {
|
||||||
|
'version': '4.6.11',
|
||||||
|
'url': 'https://github.com/simdjson/simdjson/archive/refs/tags/v4.6.11.tar.gz',
|
||||||
|
'sha256': '61d948fc24f0d793829ad658058e7597d064988a89b4607ea02e401a82df98ff',
|
||||||
|
'dir': 'simdjson-4.6.11',
|
||||||
|
},
|
||||||
|
'boost': {
|
||||||
|
'version': '1.92.0',
|
||||||
|
'url': 'https://archives.boost.io/release/1.92.0/source/boost_1_92_0.tar.gz',
|
||||||
|
'sha256': 'c4a3b310ddd2472416e091067166b0713be97c63f38c212c484ada022fd296ce',
|
||||||
|
'dir': 'boost_1_92_0',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# the documents of bench_view.cpp, relative to the json_test_data directory
|
||||||
|
DEFAULT_CORPUS = [
|
||||||
|
'nativejson-benchmark/twitter.json',
|
||||||
|
'nativejson-benchmark/citm_catalog.json',
|
||||||
|
'nativejson-benchmark/canada.json',
|
||||||
|
'jeopardy/jeopardy.json',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def run(cmd, **kwargs):
|
||||||
|
print('+ ' + ' '.join(shlex.quote(c) for c in cmd), flush=True)
|
||||||
|
# cmd is an argument list built by this script, never a shell string
|
||||||
|
return subprocess.run(cmd, check=True, **kwargs) # nosec B603
|
||||||
|
|
||||||
|
|
||||||
|
def output(cmd):
|
||||||
|
try:
|
||||||
|
# cmd is an argument list built by this script, never a shell string
|
||||||
|
return subprocess.run(cmd, check=True, capture_output=True, text=True).stdout.strip() # nosec B603
|
||||||
|
except (OSError, subprocess.CalledProcessError):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# libraries
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class Library:
|
||||||
|
"""include directories, sources to compile, and linker flags of a library"""
|
||||||
|
|
||||||
|
def __init__(self, name, include=None, sources=None, link=None, version=''):
|
||||||
|
self.name = name
|
||||||
|
self.include = include or []
|
||||||
|
self.sources = sources or []
|
||||||
|
self.link = link or []
|
||||||
|
self.version = version
|
||||||
|
|
||||||
|
|
||||||
|
def header_version(path, pattern):
|
||||||
|
try:
|
||||||
|
with open(path, encoding='utf-8', errors='replace') as f:
|
||||||
|
m = re.search(pattern, f.read())
|
||||||
|
return m.group(1) if m else ''
|
||||||
|
except OSError:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
def library_version(name, include_dirs):
|
||||||
|
patterns = {
|
||||||
|
'yyjson': ('yyjson.h', r'#define\s+YYJSON_VERSION_STRING\s+"([^"]+)"'),
|
||||||
|
'simdjson': ('simdjson.h', r'#define\s+SIMDJSON_VERSION\s+"?([0-9.]+)"?'),
|
||||||
|
'boost': (os.path.join('boost', 'version.hpp'), r'#define\s+BOOST_LIB_VERSION\s+"([^"]+)"'),
|
||||||
|
}
|
||||||
|
header, pattern = patterns[name]
|
||||||
|
for d in include_dirs:
|
||||||
|
v = header_version(os.path.join(d, header), pattern)
|
||||||
|
if v:
|
||||||
|
return v.replace('_', '.')
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
def system_library(name):
|
||||||
|
"""a library found with pkg-config or Homebrew, or None"""
|
||||||
|
flags = output(['pkg-config', '--cflags', '--libs', name]).split()
|
||||||
|
if flags:
|
||||||
|
include = [f[2:] for f in flags if f.startswith('-I')]
|
||||||
|
link = [f for f in flags if f.startswith('-L') or f.startswith('-l')]
|
||||||
|
libdirs = [f[2:] for f in link if f.startswith('-L')]
|
||||||
|
link += ['-Wl,-rpath,' + d for d in libdirs]
|
||||||
|
return Library(name, include, [], link, library_version(name, include))
|
||||||
|
prefix = output(['brew', '--prefix', name]) if shutil.which('brew') else ''
|
||||||
|
if prefix and os.path.isdir(os.path.join(prefix, 'include')):
|
||||||
|
include = [os.path.join(prefix, 'include')]
|
||||||
|
link = []
|
||||||
|
if name != 'boost':
|
||||||
|
lib = os.path.join(prefix, 'lib')
|
||||||
|
link = ['-L' + lib, '-l' + name, '-Wl,-rpath,' + lib]
|
||||||
|
return Library(name, include, [], link, library_version(name, include))
|
||||||
|
if name == 'boost':
|
||||||
|
for d in ['/usr/include', '/usr/local/include']:
|
||||||
|
if os.path.isfile(os.path.join(d, 'boost', 'json.hpp')):
|
||||||
|
return Library(name, [d], [], [], library_version(name, [d]))
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def sha256_file(path):
|
||||||
|
"""SHA-256 of a file, read in chunks (the Boost archive is more than 100 MB)"""
|
||||||
|
h = hashlib.sha256()
|
||||||
|
with open(path, 'rb') as f:
|
||||||
|
for chunk in iter(lambda: f.read(1 << 20), b''):
|
||||||
|
h.update(chunk)
|
||||||
|
return h.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def download_library(name, work):
|
||||||
|
"""a pinned release, downloaded and checked, or an error"""
|
||||||
|
pin = PINNED[name]
|
||||||
|
archive = os.path.join(work, 'download', os.path.basename(pin['url']))
|
||||||
|
os.makedirs(os.path.dirname(archive), exist_ok=True)
|
||||||
|
if not os.path.isfile(archive):
|
||||||
|
print(f'downloading {pin["url"]}', flush=True)
|
||||||
|
# the URLs are the https constants in PINNED, and the SHA-256 is checked below
|
||||||
|
# (into a .part file first, so that an interrupted download is not kept)
|
||||||
|
with urllib.request.urlopen(pin['url'], timeout=DOWNLOAD_TIMEOUT) as response: # nosec B310
|
||||||
|
with open(archive + '.part', 'wb') as f:
|
||||||
|
shutil.copyfileobj(response, f)
|
||||||
|
os.replace(archive + '.part', archive)
|
||||||
|
digest = sha256_file(archive)
|
||||||
|
if digest != pin['sha256']:
|
||||||
|
os.remove(archive) # downloaded again by the next run
|
||||||
|
sys.exit(f'error: SHA-256 of {archive} is {digest}, expected {pin["sha256"]} (removed)')
|
||||||
|
src = os.path.join(work, 'download', pin['dir'])
|
||||||
|
if not os.path.isdir(src):
|
||||||
|
# extracted into a temporary directory first, so that an interrupted extraction
|
||||||
|
# is not mistaken for a complete one
|
||||||
|
tmp = tempfile.mkdtemp(prefix=pin['dir'] + '.part-', dir=os.path.join(work, 'download'))
|
||||||
|
try:
|
||||||
|
with tarfile.open(archive) as t:
|
||||||
|
# (the 'data' filter rejects links and paths outside the target where Python has it)
|
||||||
|
kwargs = {'filter': 'data'} if hasattr(tarfile, 'data_filter') else {}
|
||||||
|
t.extractall(tmp, **kwargs) # noqa: S202 (checked archive) # nosec B202
|
||||||
|
os.rename(os.path.join(tmp, pin['dir']), src)
|
||||||
|
finally:
|
||||||
|
shutil.rmtree(tmp, ignore_errors=True)
|
||||||
|
if name == 'yyjson':
|
||||||
|
return Library(name, [os.path.join(src, 'src')], [os.path.join(src, 'src', 'yyjson.c')], [], pin['version'])
|
||||||
|
if name == 'simdjson':
|
||||||
|
single = os.path.join(src, 'singleheader')
|
||||||
|
return Library(name, [single], [os.path.join(single, 'simdjson.cpp')], [], pin['version'])
|
||||||
|
return Library(name, [src], [], [], pin['version'])
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# machine description
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def cpu_model():
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
return output(['sysctl', '-n', 'machdep.cpu.brand_string'])
|
||||||
|
try:
|
||||||
|
with open('/proc/cpuinfo', encoding='utf-8') as f:
|
||||||
|
for line in f:
|
||||||
|
if line.startswith('model name') or line.startswith('Model'):
|
||||||
|
return line.split(':', 1)[1].strip()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
# (AArch64 Linux: /proc/cpuinfo has no model name, lscpu knows it)
|
||||||
|
for line in output(['lscpu']).splitlines():
|
||||||
|
if line.startswith('Model name:'):
|
||||||
|
return line.split(':', 1)[1].strip()
|
||||||
|
return platform.processor() or platform.machine()
|
||||||
|
|
||||||
|
|
||||||
|
def git_commit():
|
||||||
|
commit = output(['git', '-C', REPO, 'rev-parse', '--short=12', 'HEAD'])
|
||||||
|
dirty = output(['git', '-C', REPO, 'status', '--porcelain', '--untracked-files=no'])
|
||||||
|
return commit + (' (with local changes)' if dirty else '')
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# main
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
ap.add_argument('--data', required=True, help='json_test_data directory (with nativejson-benchmark/ and jeopardy/)')
|
||||||
|
ap.add_argument('--download', action='store_true', help='use pinned downloads instead of system libraries')
|
||||||
|
ap.add_argument('--no-boost', action='store_true', help='skip Boost.JSON')
|
||||||
|
ap.add_argument('--native', action='store_true', help='compile for this CPU (-march=native / -mcpu=native)')
|
||||||
|
ap.add_argument('--rounds', type=int, default=30, help='rounds of bench_view (default: 30)')
|
||||||
|
ap.add_argument('--corpus', nargs='*', default=[], help='more files for bench_corpus')
|
||||||
|
ap.add_argument('--build-dir', default=os.path.join(HERE, 'build'), help='where to build (default: build/ next to this script)')
|
||||||
|
args = ap.parse_args()
|
||||||
|
# the benchmarks run in the build directory: make the paths absolute
|
||||||
|
args.data = os.path.abspath(args.data)
|
||||||
|
args.corpus = [os.path.abspath(f) for f in args.corpus]
|
||||||
|
args.build_dir = os.path.abspath(args.build_dir)
|
||||||
|
|
||||||
|
# (CXX and CC may contain arguments, e.g. 'ccache g++')
|
||||||
|
cxx = shlex.split(os.environ.get('CXX', 'c++'))
|
||||||
|
cc = shlex.split(os.environ.get('CC', 'cc'))
|
||||||
|
os.makedirs(args.build_dir, exist_ok=True)
|
||||||
|
|
||||||
|
libs = {}
|
||||||
|
for name in ['yyjson', 'simdjson', 'boost']:
|
||||||
|
if name == 'boost' and args.no_boost:
|
||||||
|
continue
|
||||||
|
lib = download_library(name, args.build_dir) if args.download else system_library(name)
|
||||||
|
if lib is None and name != 'boost':
|
||||||
|
sys.exit(f'error: {name} not found; install it, or use --download')
|
||||||
|
if lib is not None:
|
||||||
|
libs[name] = lib
|
||||||
|
with_boost = 'boost' in libs
|
||||||
|
if not with_boost:
|
||||||
|
print('Boost.JSON not found: its columns are skipped', flush=True)
|
||||||
|
|
||||||
|
flags = ['-std=c++17', '-O3', '-DNDEBUG', f'-DJSON_VIEW_BENCH_BOOST={1 if with_boost else 0}']
|
||||||
|
if args.native:
|
||||||
|
flags.append('-mcpu=native' if platform.machine().lower() in ('arm64', 'aarch64') else '-march=native')
|
||||||
|
include = ['-I' + os.path.join(REPO, 'include')] + ['-I' + d for lib in libs.values() for d in lib.include]
|
||||||
|
link = [f for lib in libs.values() for f in lib.link]
|
||||||
|
|
||||||
|
# C sources of downloaded libraries are compiled once
|
||||||
|
objects = []
|
||||||
|
for lib in libs.values():
|
||||||
|
for src in lib.sources:
|
||||||
|
obj = os.path.join(args.build_dir, os.path.basename(src) + '.o')
|
||||||
|
is_c = src.endswith('.c')
|
||||||
|
run((cc if is_c else cxx) + ([] if is_c else ['-std=c++17']) + ['-O3', '-DNDEBUG', '-c', src, '-o', obj]
|
||||||
|
+ ['-I' + d for d in lib.include])
|
||||||
|
objects.append(obj)
|
||||||
|
|
||||||
|
binaries = {}
|
||||||
|
for bench in ['bench_view', 'bench_corpus', 'bench_edit']:
|
||||||
|
exe = os.path.join(args.build_dir, bench)
|
||||||
|
run(cxx + flags + include + [os.path.join(HERE, bench + '.cpp')] + objects + link + ['-o', exe])
|
||||||
|
binaries[bench] = exe
|
||||||
|
|
||||||
|
# run: bench_view on its documents, bench_corpus on those and the given files
|
||||||
|
corpus = [os.path.join(args.data, f) for f in DEFAULT_CORPUS] + args.corpus
|
||||||
|
outputs = {}
|
||||||
|
outputs['bench_view'] = run([binaries['bench_view'], args.data, str(args.rounds)], cwd=args.build_dir,
|
||||||
|
capture_output=True, text=True).stdout
|
||||||
|
outputs['bench_corpus'] = run([binaries['bench_corpus']] + corpus, cwd=args.build_dir,
|
||||||
|
capture_output=True, text=True).stdout
|
||||||
|
outputs['bench_edit'] = run([binaries['bench_edit'], args.data, str(max(1, args.rounds // 2))], cwd=args.build_dir,
|
||||||
|
capture_output=True, text=True).stdout
|
||||||
|
for name, text in outputs.items():
|
||||||
|
print(text)
|
||||||
|
|
||||||
|
# results with their metadata
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
host = re.sub(r'[^A-Za-z0-9-]+', '-', platform.node().split('.')[0]) or 'host'
|
||||||
|
stem = os.path.join(HERE, 'results', f'{now:%Y-%m-%d}-{host}')
|
||||||
|
os.makedirs(os.path.dirname(stem), exist_ok=True)
|
||||||
|
cxx_version = output(cxx + ['--version'])
|
||||||
|
compiler_version = cxx_version.splitlines()[0] if cxx_version else ' '.join(cxx)
|
||||||
|
meta = [
|
||||||
|
('date', f'{now:%Y-%m-%d %H:%M}'),
|
||||||
|
('commit', git_commit()),
|
||||||
|
('CPU', cpu_model()),
|
||||||
|
('OS', f'{platform.system()} {platform.release()} ({platform.machine()})'),
|
||||||
|
('compiler', compiler_version),
|
||||||
|
('flags', ' '.join(flags)),
|
||||||
|
('yyjson', libs['yyjson'].version),
|
||||||
|
('simdjson', libs['simdjson'].version),
|
||||||
|
('Boost.JSON', libs['boost'].version if with_boost else 'skipped (not found)'),
|
||||||
|
('libraries from', 'pinned downloads' if args.download else 'the system'),
|
||||||
|
('rounds', str(args.rounds)),
|
||||||
|
]
|
||||||
|
with open(stem + '.md', 'w', encoding='utf-8') as f:
|
||||||
|
f.write(f'# json_view comparison, {now:%Y-%m-%d}\n\n')
|
||||||
|
f.write('Generated by `tests/benchmarks/json_view/compare.py`; best of the interleaved rounds.\n\n')
|
||||||
|
f.write('| | |\n|---|---|\n')
|
||||||
|
for key, value in meta:
|
||||||
|
f.write(f'| {key} | {value} |\n')
|
||||||
|
for name, text in outputs.items():
|
||||||
|
f.write(f'\n## {name}\n\n```\n{text.rstrip()}\n```\n')
|
||||||
|
with open(stem + '.csv', 'w', encoding='utf-8') as out:
|
||||||
|
out.write(''.join(f'# {key}: {value}\n' for key, value in meta))
|
||||||
|
for name in ['bench_view', 'bench_corpus', 'bench_edit']:
|
||||||
|
path = os.path.join(args.build_dir, name + '.csv')
|
||||||
|
if os.path.isfile(path):
|
||||||
|
with open(path, encoding='utf-8') as f:
|
||||||
|
out.write(f'# {name}\n' + f.read())
|
||||||
|
print(f'results: {stem}.md, {stem}.csv')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -9,12 +9,15 @@
|
|||||||
#include <benchmark/benchmark.h>
|
#include <benchmark/benchmark.h>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <map>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <test_data.hpp>
|
#include <test_data.hpp>
|
||||||
|
|
||||||
@@ -710,6 +713,584 @@ static void FromCborChunkedString(benchmark::State& state, const std::size_t chu
|
|||||||
|
|
||||||
BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000);
|
BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// SAX-parse JSON from string
|
||||||
|
//
|
||||||
|
// sax_parse() with a handler that does next to nothing runs the lexer and
|
||||||
|
// parser without building values, so the gap to ParseString above is the cost
|
||||||
|
// of building the DOM.
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
static std::string read_file(const char* filename)
|
||||||
|
{
|
||||||
|
std::ifstream f(filename);
|
||||||
|
return std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||||
|
}
|
||||||
|
|
||||||
|
static json load_json(const char* filename)
|
||||||
|
{
|
||||||
|
return json::parse(read_file(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SAX handler that only counts the events, so that the handler costs next to nothing
|
||||||
|
class counting_sax : public nlohmann::json_sax<json>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::size_t events = 0;
|
||||||
|
|
||||||
|
bool null() override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool boolean(bool /*val*/) override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool number_integer(number_integer_t /*val*/) override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool number_unsigned(number_unsigned_t /*val*/) override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool number_float(number_float_t /*val*/, const string_t& /*s*/) override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool string(string_t& /*val*/) override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool binary(binary_t& /*val*/) override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool start_object(std::size_t /*elements*/) override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool key(string_t& /*val*/) override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool end_object() override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool start_array(std::size_t /*elements*/) override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool end_array() override
|
||||||
|
{
|
||||||
|
return count();
|
||||||
|
}
|
||||||
|
bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) override
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool count()
|
||||||
|
{
|
||||||
|
++events;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static void SaxParse(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
const std::string str = read_file(filename);
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
counting_sax sax;
|
||||||
|
bool result = json::sax_parse(str, &sax);
|
||||||
|
benchmark::DoNotOptimize(result);
|
||||||
|
benchmark::DoNotOptimize(sax.events);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SetBytesProcessed(state.iterations() * str.size());
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(SaxParse, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||||
|
BENCHMARK_CAPTURE(SaxParse, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||||
|
BENCHMARK_CAPTURE(SaxParse, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// work with values: copy, destroy, compare, iterate
|
||||||
|
//
|
||||||
|
// The parsed test files stand in for a document an application keeps around.
|
||||||
|
// Each benchmark times one whole-document operation; setup and cleanup run
|
||||||
|
// outside the timed section.
|
||||||
|
//
|
||||||
|
// A copy is always destroyed again, and how a document is freed shapes the
|
||||||
|
// heap the next copy allocates from: a destructor that frees quickly can leave
|
||||||
|
// work to the allocator's next calls. Copy, CopyAssign and CopyRing therefore
|
||||||
|
// time the copy together with the destruction of a copy; CopyOnly and
|
||||||
|
// DestroyOnly time one half each and are meant for telling the halves apart,
|
||||||
|
// not for comparing versions.
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// copy a document and destroy the copy again, as a temporary copy is used
|
||||||
|
static void Copy(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
const json j = load_json(filename);
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
json copy = j;
|
||||||
|
benchmark::DoNotOptimize(copy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(Copy, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||||
|
BENCHMARK_CAPTURE(Copy, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||||
|
BENCHMARK_CAPTURE(Copy, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||||
|
|
||||||
|
/// copy-assign a document over an earlier copy of it, which is freed in the process
|
||||||
|
static void CopyAssign(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
const json j = load_json(filename);
|
||||||
|
json target = j;
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
target = j;
|
||||||
|
benchmark::DoNotOptimize(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(CopyAssign, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||||
|
BENCHMARK_CAPTURE(CopyAssign, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||||
|
BENCHMARK_CAPTURE(CopyAssign, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||||
|
|
||||||
|
/// keep eight copies alive and replace the oldest one, as a cache or an undo
|
||||||
|
/// history does, so that the copy is not made from memory just freed by an
|
||||||
|
/// identical document
|
||||||
|
static void CopyRing(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
const json j = load_json(filename);
|
||||||
|
std::vector<json> ring(8, j);
|
||||||
|
std::size_t oldest = 0;
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
ring[oldest] = j;
|
||||||
|
benchmark::DoNotOptimize(ring[oldest]);
|
||||||
|
oldest = (oldest + 1) % ring.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(CopyRing, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||||
|
BENCHMARK_CAPTURE(CopyRing, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||||
|
BENCHMARK_CAPTURE(CopyRing, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||||
|
|
||||||
|
/// only the copy; the copy is destroyed with the timer paused (diagnostic)
|
||||||
|
static void CopyOnly(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
const json j = load_json(filename);
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
auto* copy = new json(j);
|
||||||
|
|
||||||
|
state.PauseTiming();
|
||||||
|
delete copy;
|
||||||
|
state.ResumeTiming();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(CopyOnly, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||||
|
BENCHMARK_CAPTURE(CopyOnly, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||||
|
BENCHMARK_CAPTURE(CopyOnly, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||||
|
|
||||||
|
/// only the destruction; the copy is made with the timer paused (diagnostic)
|
||||||
|
static void DestroyOnly(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
const json j = load_json(filename);
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
state.PauseTiming();
|
||||||
|
auto* copy = new json(j);
|
||||||
|
state.ResumeTiming();
|
||||||
|
|
||||||
|
delete copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(DestroyOnly, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||||
|
BENCHMARK_CAPTURE(DestroyOnly, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||||
|
BENCHMARK_CAPTURE(DestroyOnly, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||||
|
|
||||||
|
static void Equal(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
// two separate values, so that the comparison cannot stop at identical addresses
|
||||||
|
const json j = load_json(filename);
|
||||||
|
const json copy = j;
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
bool result = (j == copy);
|
||||||
|
benchmark::DoNotOptimize(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(Equal, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||||
|
BENCHMARK_CAPTURE(Equal, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||||
|
BENCHMARK_CAPTURE(Equal, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||||
|
|
||||||
|
/// visit every value with range-based for loops, as client code walks a document
|
||||||
|
static std::size_t count_values(const json& j)
|
||||||
|
{
|
||||||
|
std::size_t result = 1;
|
||||||
|
if (j.is_structured())
|
||||||
|
{
|
||||||
|
for (const auto& element : j)
|
||||||
|
{
|
||||||
|
result += count_values(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Iterate(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
const json j = load_json(filename);
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
std::size_t result = count_values(j);
|
||||||
|
benchmark::DoNotOptimize(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(count_values(j)));
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(Iterate, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||||
|
BENCHMARK_CAPTURE(Iterate, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||||
|
BENCHMARK_CAPTURE(Iterate, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// flatten and unflatten
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
static void Flatten(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
const json j = load_json(filename);
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
json flat = j.flatten();
|
||||||
|
benchmark::DoNotOptimize(flat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(Flatten, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||||
|
BENCHMARK_CAPTURE(Flatten, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||||
|
BENCHMARK_CAPTURE(Flatten, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||||
|
|
||||||
|
// ordered_json objects look keys up linearly, which flatten() used to do for
|
||||||
|
// every value; canada (330,000 values in one flat object) is left out, as it
|
||||||
|
// would take minutes with such versions
|
||||||
|
static void FlattenOrdered(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
const nlohmann::ordered_json j = nlohmann::ordered_json::parse(read_file(filename));
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
nlohmann::ordered_json flat = j.flatten();
|
||||||
|
benchmark::DoNotOptimize(flat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(FlattenOrdered, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||||
|
BENCHMARK_CAPTURE(FlattenOrdered, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||||
|
|
||||||
|
static void Unflatten(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
const json flat = load_json(filename).flatten();
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
json j = flat.unflatten();
|
||||||
|
benchmark::DoNotOptimize(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(Unflatten, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||||
|
BENCHMARK_CAPTURE(Unflatten, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||||
|
BENCHMARK_CAPTURE(Unflatten, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// diff and patch
|
||||||
|
//
|
||||||
|
// The target is the document with every eighth scalar changed, so the patch
|
||||||
|
// has one "replace" operation per changed value and the structure stays the
|
||||||
|
// same.
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
static void change_every_eighth_scalar(json& j, std::size_t& counter)
|
||||||
|
{
|
||||||
|
if (j.is_structured())
|
||||||
|
{
|
||||||
|
for (auto& element : j)
|
||||||
|
{
|
||||||
|
change_every_eighth_scalar(element, counter);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (++counter % 8 != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (j.type())
|
||||||
|
{
|
||||||
|
case json::value_t::number_integer:
|
||||||
|
j = j.get<json::number_integer_t>() + 1;
|
||||||
|
break;
|
||||||
|
case json::value_t::number_unsigned:
|
||||||
|
j = j.get<json::number_unsigned_t>() + 1;
|
||||||
|
break;
|
||||||
|
case json::value_t::number_float:
|
||||||
|
j = j.get<json::number_float_t>() + 0.5;
|
||||||
|
break;
|
||||||
|
case json::value_t::string:
|
||||||
|
j = j.get<std::string>() + "!";
|
||||||
|
break;
|
||||||
|
case json::value_t::boolean:
|
||||||
|
j = !j.get<bool>();
|
||||||
|
break;
|
||||||
|
case json::value_t::null:
|
||||||
|
case json::value_t::object:
|
||||||
|
case json::value_t::array:
|
||||||
|
case json::value_t::binary:
|
||||||
|
case json::value_t::discarded:
|
||||||
|
default:
|
||||||
|
j = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static json changed_copy(const json& j)
|
||||||
|
{
|
||||||
|
json result = j;
|
||||||
|
std::size_t counter = 0;
|
||||||
|
change_every_eighth_scalar(result, counter);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Diff(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
const json source = load_json(filename);
|
||||||
|
const json target = changed_copy(source);
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
json patch = json::diff(source, target);
|
||||||
|
benchmark::DoNotOptimize(patch);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(json::diff(source, target).size()));
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(Diff, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||||
|
BENCHMARK_CAPTURE(Diff, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||||
|
BENCHMARK_CAPTURE(Diff, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||||
|
|
||||||
|
static void Patch(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
const json source = load_json(filename);
|
||||||
|
const json patch = json::diff(source, changed_copy(source));
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
json result = source.patch(patch);
|
||||||
|
benchmark::DoNotOptimize(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(patch.size()));
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(Patch, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||||
|
BENCHMARK_CAPTURE(Patch, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||||
|
BENCHMARK_CAPTURE(Patch, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// JSON Pointer
|
||||||
|
//
|
||||||
|
// The pointers are those of every scalar in the document, i.e., the keys of
|
||||||
|
// its flattened form.
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
static std::vector<std::string> pointer_strings(const json& j)
|
||||||
|
{
|
||||||
|
std::vector<std::string> result;
|
||||||
|
const json flat = j.flatten();
|
||||||
|
result.reserve(flat.size());
|
||||||
|
for (auto it = flat.begin(); it != flat.end(); ++it)
|
||||||
|
{
|
||||||
|
result.push_back(it.key());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<json::json_pointer> pointers(const json& j)
|
||||||
|
{
|
||||||
|
const std::vector<std::string> strings = pointer_strings(j);
|
||||||
|
std::vector<json::json_pointer> result;
|
||||||
|
result.reserve(strings.size());
|
||||||
|
for (const auto& s : strings)
|
||||||
|
{
|
||||||
|
result.emplace_back(s);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void JsonPointerParse(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
const std::vector<std::string> strings = pointer_strings(load_json(filename));
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
for (const auto& s : strings)
|
||||||
|
{
|
||||||
|
json::json_pointer ptr(s);
|
||||||
|
benchmark::DoNotOptimize(ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(strings.size()));
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(JsonPointerParse, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||||
|
BENCHMARK_CAPTURE(JsonPointerParse, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||||
|
|
||||||
|
static void JsonPointerToString(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
const std::vector<json::json_pointer> ptrs = pointers(load_json(filename));
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
for (const auto& ptr : ptrs)
|
||||||
|
{
|
||||||
|
std::string s = ptr.to_string();
|
||||||
|
benchmark::DoNotOptimize(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(ptrs.size()));
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(JsonPointerToString, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||||
|
BENCHMARK_CAPTURE(JsonPointerToString, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||||
|
|
||||||
|
static void JsonPointerAt(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
const json j = load_json(filename);
|
||||||
|
const std::vector<json::json_pointer> ptrs = pointers(j);
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
for (const auto& ptr : ptrs)
|
||||||
|
{
|
||||||
|
const json& value = j.at(ptr);
|
||||||
|
benchmark::DoNotOptimize(&value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(ptrs.size()));
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(JsonPointerAt, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||||
|
BENCHMARK_CAPTURE(JsonPointerAt, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||||
|
|
||||||
|
static void JsonPointerContains(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
const json j = load_json(filename);
|
||||||
|
const std::vector<json::json_pointer> ptrs = pointers(j);
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
for (const auto& ptr : ptrs)
|
||||||
|
{
|
||||||
|
bool result = j.contains(ptr);
|
||||||
|
benchmark::DoNotOptimize(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(ptrs.size()));
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(JsonPointerContains, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||||
|
BENCHMARK_CAPTURE(JsonPointerContains, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// conversions from and to C++ containers
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
static void GetVectorInt(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
const json j = load_json(filename);
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
auto v = j.get<std::vector<std::int64_t>>();
|
||||||
|
benchmark::DoNotOptimize(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(j.size()));
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(GetVectorInt, signed_ints, TEST_DATA_DIRECTORY "/regression/signed_ints.json");
|
||||||
|
|
||||||
|
static void GetVectorDouble(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
const json j = load_json(filename);
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
auto v = j.get<std::vector<double>>();
|
||||||
|
benchmark::DoNotOptimize(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(j.size()));
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(GetVectorDouble, floats, TEST_DATA_DIRECTORY "/regression/floats.json");
|
||||||
|
|
||||||
|
// a large flat object: the flattened document, one member per scalar
|
||||||
|
static void GetMap(benchmark::State& state, const char* filename)
|
||||||
|
{
|
||||||
|
const json j = load_json(filename).flatten();
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
auto m = j.get<std::map<std::string, json>>();
|
||||||
|
benchmark::DoNotOptimize(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(j.size()));
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(GetMap, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||||
|
|
||||||
|
/// 100,000 strings, each too long for the small string optimization
|
||||||
|
static std::vector<std::string> make_strings()
|
||||||
|
{
|
||||||
|
std::vector<std::string> result;
|
||||||
|
result.reserve(100000);
|
||||||
|
for (std::size_t i = 0; i < 100000; ++i)
|
||||||
|
{
|
||||||
|
result.push_back("a string that does not fit in place: " + std::to_string(i));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ToJsonStrings(benchmark::State& state, const bool move)
|
||||||
|
{
|
||||||
|
const std::vector<std::string> strings = make_strings();
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
state.PauseTiming();
|
||||||
|
std::vector<std::string> input = strings;
|
||||||
|
state.ResumeTiming();
|
||||||
|
|
||||||
|
// destroyed inside the timed section, like the copies above
|
||||||
|
auto* j = move ? new json(std::move(input)) : new json(input);
|
||||||
|
delete j;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(strings.size()));
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(ToJsonStrings, copy, false);
|
||||||
|
BENCHMARK_CAPTURE(ToJsonStrings, move, true);
|
||||||
|
|
||||||
// conversion of a JSON array into standard containers
|
// conversion of a JSON array into standard containers
|
||||||
template<typename Container>
|
template<typename Container>
|
||||||
static void GetArray(benchmark::State& state)
|
static void GetArray(benchmark::State& state)
|
||||||
|
|||||||
@@ -492,3 +492,278 @@ TEST_CASE("binary_reserve_hint never over-reserves")
|
|||||||
CHECK(nlohmann::detail::binary_reserve_hint(json({{"a", 1}, {"b", 2}})) == 5);
|
CHECK(nlohmann::detail::binary_reserve_hint(json({{"a", 1}, {"b", 2}})) == 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
// numbers around every boundary of the integer encodings, and floats of each
|
||||||
|
// kind (exact in float, needing double, special values)
|
||||||
|
std::vector<json> boundary_numbers()
|
||||||
|
{
|
||||||
|
std::vector<json> numbers;
|
||||||
|
for (int k = 0; k < 64; ++k)
|
||||||
|
{
|
||||||
|
const std::uint64_t p = std::uint64_t{1} << k;
|
||||||
|
for (const std::uint64_t d :
|
||||||
|
{
|
||||||
|
p - 1, p, p + 1
|
||||||
|
})
|
||||||
|
{
|
||||||
|
numbers.emplace_back(d);
|
||||||
|
if (d <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
|
||||||
|
{
|
||||||
|
numbers.emplace_back(static_cast<std::int64_t>(d));
|
||||||
|
numbers.emplace_back(-static_cast<std::int64_t>(d));
|
||||||
|
numbers.emplace_back(-static_cast<std::int64_t>(d) - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
numbers.emplace_back((std::numeric_limits<std::int64_t>::min)());
|
||||||
|
numbers.emplace_back((std::numeric_limits<std::uint64_t>::max)());
|
||||||
|
for (const double f :
|
||||||
|
{
|
||||||
|
0.0, -0.0, 1.0, -1.0, 0.5, 0.1, 3.25, 1e300, -1e-300, 3.4028234663852886e38, 1e39,
|
||||||
|
std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity(),
|
||||||
|
std::numeric_limits<double>::quiet_NaN()
|
||||||
|
})
|
||||||
|
{
|
||||||
|
numbers.emplace_back(f);
|
||||||
|
}
|
||||||
|
return numbers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the concatenation of the encodings of the elements of @a numbers[first, first + count)
|
||||||
|
template<typename Single>
|
||||||
|
std::vector<std::uint8_t> concat_singles(const std::vector<json>& numbers, const std::size_t first, const std::size_t count, Single single)
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> bytes;
|
||||||
|
for (std::size_t i = first; i < first + count; ++i)
|
||||||
|
{
|
||||||
|
const auto one = single(numbers[i]);
|
||||||
|
bytes.insert(bytes.end(), one.begin(), one.end());
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// Runs of numbers in arrays are encoded straight into space reserved from the
|
||||||
|
// sink, by code that is separate from the encoding of a single value. Both have
|
||||||
|
// to agree on every number: an array is its head and the encodings of its
|
||||||
|
// elements, one after the other.
|
||||||
|
TEST_CASE("runs of numbers in arrays are encoded as the numbers one by one")
|
||||||
|
{
|
||||||
|
const std::vector<json> numbers = boundary_numbers();
|
||||||
|
|
||||||
|
// arrays of every length that a one-byte head covers, so that runs are as
|
||||||
|
// long as, and longer and shorter than, what fits one reservation
|
||||||
|
for (const std::size_t count :
|
||||||
|
{
|
||||||
|
1u, 2u, 3u, 5u, 14u, 15u, 23u
|
||||||
|
})
|
||||||
|
{
|
||||||
|
for (std::size_t first = 0; first + count <= numbers.size(); first += 7)
|
||||||
|
{
|
||||||
|
const json array(std::vector<json>(numbers.begin() + static_cast<std::ptrdiff_t>(first),
|
||||||
|
numbers.begin() + static_cast<std::ptrdiff_t>(first + count)));
|
||||||
|
CAPTURE(array.dump(-1, ' ', false, json::error_handler_t::replace))
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> expected{static_cast<std::uint8_t>(0x80 + count)};
|
||||||
|
auto elements = concat_singles(numbers, first, count, [](const json & j)
|
||||||
|
{
|
||||||
|
return json::to_cbor(j);
|
||||||
|
});
|
||||||
|
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||||
|
CHECK(json::to_cbor(array) == expected);
|
||||||
|
|
||||||
|
expected.assign(1, static_cast<std::uint8_t>(count <= 15 ? 0x90 + count : 0xDC));
|
||||||
|
if (count > 15)
|
||||||
|
{
|
||||||
|
expected.push_back(0);
|
||||||
|
expected.push_back(static_cast<std::uint8_t>(count));
|
||||||
|
}
|
||||||
|
elements = concat_singles(numbers, first, count, [](const json & j)
|
||||||
|
{
|
||||||
|
return json::to_msgpack(j);
|
||||||
|
});
|
||||||
|
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||||
|
CHECK(json::to_msgpack(array) == expected);
|
||||||
|
|
||||||
|
for (const bool bjdata :
|
||||||
|
{
|
||||||
|
false, true
|
||||||
|
})
|
||||||
|
{
|
||||||
|
expected.assign(1, static_cast<std::uint8_t>('['));
|
||||||
|
elements = concat_singles(numbers, first, count, [bjdata](const json & j)
|
||||||
|
{
|
||||||
|
return bjdata ? json::to_bjdata(j) : json::to_ubjson(j);
|
||||||
|
});
|
||||||
|
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||||
|
expected.push_back(static_cast<std::uint8_t>(']'));
|
||||||
|
CHECK((bjdata ? json::to_bjdata(array) : json::to_ubjson(array)) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BON8 has no integers above the int64 range
|
||||||
|
bool representable = true;
|
||||||
|
for (std::size_t i = first; i < first + count; ++i)
|
||||||
|
{
|
||||||
|
representable = representable && bon8_representable(numbers[i]);
|
||||||
|
}
|
||||||
|
if (representable)
|
||||||
|
{
|
||||||
|
expected.assign(1, static_cast<std::uint8_t>(count <= 4 ? 0x80 + count : 0x85));
|
||||||
|
elements = concat_singles(numbers, first, count, [](const json & j)
|
||||||
|
{
|
||||||
|
return json::to_bon8(j);
|
||||||
|
});
|
||||||
|
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||||
|
if (count > 4)
|
||||||
|
{
|
||||||
|
expected.push_back(0xFE);
|
||||||
|
}
|
||||||
|
CHECK(json::to_bon8(array) == expected);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK_THROWS_AS(json::to_bon8(array), json::out_of_range&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An array that is written in more than one reservation (more numbers than fit
|
||||||
|
// one), and arrays of an index with more digits, round trip for every format.
|
||||||
|
TEST_CASE("large arrays and many digits of indices round trip")
|
||||||
|
{
|
||||||
|
json array = json::array();
|
||||||
|
for (int i = 0; i < 12000; ++i)
|
||||||
|
{
|
||||||
|
switch (i % 4)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
array.push_back(i);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
array.push_back(-i);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
array.push_back(i * 0.25);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
array.push_back(i % 8 == 3);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json document = json::object();
|
||||||
|
document["numbers"] = array;
|
||||||
|
document["nested"] = json::array({array, "text", array});
|
||||||
|
|
||||||
|
CHECK(json::from_cbor(json::to_cbor(document)) == document);
|
||||||
|
CHECK(json::from_msgpack(json::to_msgpack(document)) == document);
|
||||||
|
CHECK(json::from_ubjson(json::to_ubjson(document)) == document);
|
||||||
|
CHECK(json::from_ubjson(json::to_ubjson(document, true, true)) == document);
|
||||||
|
CHECK(json::from_bjdata(json::to_bjdata(document)) == document);
|
||||||
|
CHECK(json::from_bson(json::to_bson(document)) == document);
|
||||||
|
CHECK(json::from_bon8(json::to_bon8(document)) == document);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings and keys are written with their head in one reservation up to a
|
||||||
|
// length; around that length, and with every kind of head, they must read back.
|
||||||
|
TEST_CASE("strings of every head length round trip")
|
||||||
|
{
|
||||||
|
json document = json::object();
|
||||||
|
json array = json::array();
|
||||||
|
for (const std::size_t length :
|
||||||
|
{
|
||||||
|
0u, 1u, 2u, 3u, 4u, 7u, 8u, 15u, 16u, 17u, 23u, 24u, 25u, 31u, 32u, 33u, 63u, 127u, 128u, 129u, 200u, 245u, 246u, 247u, 248u, 249u, 250u, 251u, 252u, 253u, 254u, 255u, 256u, 257u, 300u, 1000u, 65535u, 65536u, 70000u
|
||||||
|
})
|
||||||
|
{
|
||||||
|
const std::string text(length, 'x');
|
||||||
|
array.push_back(text);
|
||||||
|
document[std::string(length, 'k')] = text;
|
||||||
|
}
|
||||||
|
document["array"] = array;
|
||||||
|
|
||||||
|
CHECK(json::from_cbor(json::to_cbor(document)) == document);
|
||||||
|
CHECK(json::from_msgpack(json::to_msgpack(document)) == document);
|
||||||
|
CHECK(json::from_ubjson(json::to_ubjson(document)) == document);
|
||||||
|
CHECK(json::from_ubjson(json::to_ubjson(document, true, true)) == document);
|
||||||
|
CHECK(json::from_bjdata(json::to_bjdata(document)) == document);
|
||||||
|
CHECK(json::from_bson(json::to_bson(document)) == document);
|
||||||
|
CHECK(json::from_bon8(json::to_bon8(document)) == document);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
// What a write that throws leaves behind in an output adapter is what was written
|
||||||
|
// before it, whatever the adapter and however the adapter sink buffers. (Without
|
||||||
|
// exceptions, the writes in CHECK_THROWS_AS do not run.)
|
||||||
|
TEST_CASE("a write that throws leaves the same bytes in every kind of output")
|
||||||
|
{
|
||||||
|
json array = json::array();
|
||||||
|
for (int i = 0; i < 700; ++i)
|
||||||
|
{
|
||||||
|
array.push_back(i * 1000);
|
||||||
|
array.push_back("text " + std::to_string(i));
|
||||||
|
}
|
||||||
|
array.push_back("bad \xFF byte");
|
||||||
|
json document = json::object();
|
||||||
|
document["first"] = array;
|
||||||
|
document["second"] = 1;
|
||||||
|
|
||||||
|
const auto strict = json::error_handler_t::strict;
|
||||||
|
|
||||||
|
SECTION("cbor")
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> as_vector;
|
||||||
|
std::string as_string;
|
||||||
|
std::ostringstream as_stream;
|
||||||
|
CHECK_THROWS_AS(json::to_cbor(document, as_vector, strict), json::type_error&);
|
||||||
|
CHECK_THROWS_AS(json::to_cbor(document, as_string, strict), json::type_error&);
|
||||||
|
CHECK_THROWS_AS(json::to_cbor(document, as_stream, strict), json::type_error&);
|
||||||
|
CHECK(!as_vector.empty());
|
||||||
|
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
CHECK(as_stream.str() == as_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("msgpack")
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> as_vector;
|
||||||
|
std::string as_string;
|
||||||
|
std::ostringstream as_stream;
|
||||||
|
CHECK_THROWS_AS(json::to_msgpack(document, as_vector, strict), json::type_error&);
|
||||||
|
CHECK_THROWS_AS(json::to_msgpack(document, as_string, strict), json::type_error&);
|
||||||
|
CHECK_THROWS_AS(json::to_msgpack(document, as_stream, strict), json::type_error&);
|
||||||
|
CHECK(!as_vector.empty());
|
||||||
|
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
CHECK(as_stream.str() == as_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ubjson")
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> as_vector;
|
||||||
|
std::string as_string;
|
||||||
|
std::ostringstream as_stream;
|
||||||
|
CHECK_THROWS_AS(json::to_ubjson(document, as_vector, false, false, strict), json::type_error&);
|
||||||
|
CHECK_THROWS_AS(json::to_ubjson(document, as_string, false, false, strict), json::type_error&);
|
||||||
|
CHECK_THROWS_AS(json::to_ubjson(document, as_stream, false, false, strict), json::type_error&);
|
||||||
|
CHECK(!as_vector.empty());
|
||||||
|
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
CHECK(as_stream.str() == as_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("bson")
|
||||||
|
{
|
||||||
|
// the size pass finds the string before anything is written
|
||||||
|
std::vector<std::uint8_t> as_vector;
|
||||||
|
std::string as_string;
|
||||||
|
std::ostringstream as_stream;
|
||||||
|
CHECK_THROWS_AS(json::to_bson(document, as_vector, strict), json::type_error&);
|
||||||
|
CHECK_THROWS_AS(json::to_bson(document, as_string, strict), json::type_error&);
|
||||||
|
CHECK_THROWS_AS(json::to_bson(document, as_stream, strict), json::type_error&);
|
||||||
|
CHECK(as_vector.empty());
|
||||||
|
CHECK(as_string.empty());
|
||||||
|
CHECK(as_stream.str().empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -13,11 +13,15 @@
|
|||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
#include <array> // array
|
#include <array> // array
|
||||||
|
#include <cctype> // toupper
|
||||||
#include <cstdint> // uint32_t, uint64_t
|
#include <cstdint> // uint32_t, uint64_t
|
||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
|
#include <cmath> // isfinite
|
||||||
#include <cstdlib> // strtod
|
#include <cstdlib> // strtod
|
||||||
#include <cstring> // memcpy
|
#include <cstring> // memcpy
|
||||||
#include <limits> // numeric_limits
|
#include <limits> // numeric_limits
|
||||||
|
#include <initializer_list> // initializer_list
|
||||||
|
#include <list> // list
|
||||||
#include <map> // map
|
#include <map> // map
|
||||||
#include <random> // mt19937
|
#include <random> // mt19937
|
||||||
#include <sstream> // stringstream
|
#include <sstream> // stringstream
|
||||||
@@ -332,6 +336,112 @@ TEST_CASE("lexer number fast path")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
// this section reports parse errors, which abort when exceptions are off
|
||||||
|
SECTION("digits accumulated while scanning")
|
||||||
|
{
|
||||||
|
// The fast path accumulates the digits while it validates them, eight
|
||||||
|
// at a time where possible, and converts tokens with at most 19
|
||||||
|
// digits (floats) or 18 digits (integers) from that value. Every digit
|
||||||
|
// count from 1 to 25, in the integer and in the fraction part, with
|
||||||
|
// and without sign and exponent, must give the byte path's result. A
|
||||||
|
// std::list has no contiguous storage, so parsing it takes the byte
|
||||||
|
// path.
|
||||||
|
// type and value, or the error (values beyond the range of double
|
||||||
|
// are rejected with out_of_range.406 on both paths)
|
||||||
|
const auto outcome = [](const std::string & doc, bool byte_path) -> std::string
|
||||||
|
{
|
||||||
|
JSON_TRY
|
||||||
|
{
|
||||||
|
const std::list<char> chars(doc.begin(), doc.end());
|
||||||
|
const json j = byte_path ? json::parse(chars.begin(), chars.end()) : json::parse(doc);
|
||||||
|
return std::string(j[0].type_name()) + '|' + j.dump();
|
||||||
|
}
|
||||||
|
JSON_CATCH (const json::exception & e)
|
||||||
|
{
|
||||||
|
return e.what();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// the concatenation of @a parts
|
||||||
|
const auto join = [](std::initializer_list<std::string> parts)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
for (const auto& part : parts)
|
||||||
|
{
|
||||||
|
result += part;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> numbers;
|
||||||
|
for (std::size_t digits = 1; digits <= 25; ++digits)
|
||||||
|
{
|
||||||
|
std::string run;
|
||||||
|
for (std::size_t i = 0; i < digits; ++i)
|
||||||
|
{
|
||||||
|
run += static_cast<char>('1' + static_cast<char>((i * 7) % 9));
|
||||||
|
}
|
||||||
|
const std::string nines(digits, '9');
|
||||||
|
for (const char* sign_text :
|
||||||
|
{"", "-"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
const std::string sign = sign_text;
|
||||||
|
numbers.push_back(join({sign, run}));
|
||||||
|
numbers.push_back(join({sign, nines}));
|
||||||
|
numbers.push_back(join({sign, "1", std::string(digits - 1, '0')}));
|
||||||
|
numbers.push_back(join({sign, "0.", run}));
|
||||||
|
numbers.push_back(join({sign, "0.", std::string(digits, '0'), "1"}));
|
||||||
|
numbers.push_back(join({sign, run, ".", run}));
|
||||||
|
numbers.push_back(join({sign, run, "e-", std::to_string(digits)}));
|
||||||
|
numbers.push_back(join({sign, "1.", run, "E+", std::to_string(digits * 13)}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// exponents beyond every range, and zeros
|
||||||
|
numbers.insert(numbers.end(), {"1e400", "1e-400", "-1e99999999999999999999", "1e-99999999999999999999",
|
||||||
|
"0e99999999999999999999", "0.0e-5", "-0.000", "123456789e-0000000000000000000000330"
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const auto& n : numbers)
|
||||||
|
{
|
||||||
|
CAPTURE(n)
|
||||||
|
const std::string doc = "[" + n + "]";
|
||||||
|
const std::string a = outcome(doc, false);
|
||||||
|
CHECK(a == outcome(doc, true));
|
||||||
|
const double expected = std::strtod(n.c_str(), nullptr);
|
||||||
|
if (a.compare(0, 6, "number") == 0 && std::isfinite(expected) && n.find_first_of(".eE") != std::string::npos)
|
||||||
|
{
|
||||||
|
CHECK(json::parse(doc)[0].get<double>() == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SECTION("integers that do not fit a narrower type become floats")
|
||||||
|
{
|
||||||
|
// up to 18 digits, the fast path stores the accumulated value without
|
||||||
|
// an overflow check; a narrower number_integer_t/number_unsigned_t
|
||||||
|
// must still see the value rejected and converted as a float
|
||||||
|
using json32 = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int32_t, std::uint32_t, float>;
|
||||||
|
for (const char* n :
|
||||||
|
{"4294967295", "4294967296", "2147483647", "-2147483648", "-2147483649", "99999999999", "-99999999999"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(n)
|
||||||
|
const std::string doc = std::string("[") + n + "]";
|
||||||
|
const std::list<char> chars(doc.begin(), doc.end());
|
||||||
|
const json32 a = json32::parse(doc);
|
||||||
|
const json32 b = json32::parse(chars.begin(), chars.end());
|
||||||
|
CHECK(a[0].type() == b[0].type());
|
||||||
|
CHECK(a == b);
|
||||||
|
}
|
||||||
|
CHECK(json32::parse("[4294967295]")[0].is_number_unsigned());
|
||||||
|
CHECK(json32::parse("[4294967296]")[0].is_number_float());
|
||||||
|
CHECK(json32::parse("[-2147483648]")[0].is_number_integer());
|
||||||
|
CHECK(json32::parse("[-2147483649]")[0].is_number_float());
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("token type classification")
|
SECTION("token type classification")
|
||||||
{
|
{
|
||||||
CHECK((scan_string("0") == json::lexer::token_type::value_unsigned));
|
CHECK((scan_string("0") == json::lexer::token_type::value_unsigned));
|
||||||
@@ -490,6 +600,67 @@ TEST_CASE("lexer number fast path")
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
// this test case compares the errors of both lexer paths, which abort when
|
||||||
|
// exceptions are off
|
||||||
|
TEST_CASE("lexer literal fast path")
|
||||||
|
{
|
||||||
|
// For contiguous input, the rest of a literal is compared at once, and a
|
||||||
|
// mismatch is left to the byte path, which reports the error at the
|
||||||
|
// character that differs. Every truncation and every wrong character of
|
||||||
|
// each literal, at the end of the input or followed by more text, must
|
||||||
|
// give the byte path's result (a std::list is read one character at a
|
||||||
|
// time).
|
||||||
|
const auto outcome = [](const std::string & doc, bool byte_path) -> std::string
|
||||||
|
{
|
||||||
|
JSON_TRY
|
||||||
|
{
|
||||||
|
const std::list<char> chars(doc.begin(), doc.end());
|
||||||
|
return (byte_path ? json::parse(chars.begin(), chars.end()) : json::parse(doc)).dump();
|
||||||
|
}
|
||||||
|
JSON_CATCH (const json::exception & e)
|
||||||
|
{
|
||||||
|
return e.what();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> mismatches;
|
||||||
|
for (const std::string literal :
|
||||||
|
{"true", "false", "null"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
std::vector<std::string> variants;
|
||||||
|
for (std::size_t length = 1; length <= literal.size(); ++length)
|
||||||
|
{
|
||||||
|
variants.push_back(literal.substr(0, length));
|
||||||
|
}
|
||||||
|
for (std::size_t i = 1; i < literal.size(); ++i)
|
||||||
|
{
|
||||||
|
std::string changed = literal;
|
||||||
|
changed[i] = 'X';
|
||||||
|
variants.push_back(changed);
|
||||||
|
changed[i] = static_cast<char>(std::toupper(static_cast<unsigned char>(literal[i])));
|
||||||
|
variants.push_back(changed);
|
||||||
|
}
|
||||||
|
for (const auto& variant : variants)
|
||||||
|
{
|
||||||
|
for (const std::string& doc :
|
||||||
|
{
|
||||||
|
variant, "[" + variant + "]", "[" + variant, variant + " ", variant + "1", "{\"k\":" + variant + "}"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
if (outcome(doc, false) != outcome(doc, true))
|
||||||
|
{
|
||||||
|
mismatches.push_back(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CAPTURE(mismatches)
|
||||||
|
CHECK(mismatches.empty());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
TEST_CASE("lexer string fast path")
|
TEST_CASE("lexer string fast path")
|
||||||
{
|
{
|
||||||
// Build a byte string from explicit values: a hex escape in a string
|
// Build a byte string from explicit values: a hex escape in a string
|
||||||
@@ -610,6 +781,73 @@ TEST_CASE("lexer string fast path")
|
|||||||
CAPTURE(mismatches)
|
CAPTURE(mismatches)
|
||||||
CHECK(mismatches.empty());
|
CHECK(mismatches.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("special bytes at every offset of the vector blocks")
|
||||||
|
{
|
||||||
|
// With SSE2 or NEON, runs are scanned 16 bytes at a time (after one
|
||||||
|
// SWAR word, or four with NEON), and runs of non-ASCII text are
|
||||||
|
// checked 16 bytes at a time as well. Place every kind of byte that
|
||||||
|
// ends a run at each offset across several blocks, after ASCII and
|
||||||
|
// after CJK text, so sequences also straddle the block boundaries.
|
||||||
|
// The reference is a std::list, which has no contiguous storage and
|
||||||
|
// is therefore read one character at a time.
|
||||||
|
const auto byte_path = [](const std::string & doc) -> std::string
|
||||||
|
{
|
||||||
|
const std::list<char> chars(doc.begin(), doc.end());
|
||||||
|
JSON_TRY
|
||||||
|
{
|
||||||
|
return json::parse(chars.begin(), chars.end()).dump();
|
||||||
|
}
|
||||||
|
JSON_CATCH (const json::exception & e)
|
||||||
|
{
|
||||||
|
return e.what();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const std::vector<std::string> specials =
|
||||||
|
{
|
||||||
|
"\"", "\\", "\\u00e9", bytes({0x01}), bytes({0x1F}), bytes({0x7F}),
|
||||||
|
bytes({0xC3, 0xA9}), bytes({0xE4, 0xB8, 0xAD}), bytes({0xF0, 0x9F, 0x98, 0x80}),
|
||||||
|
bytes({0xFF}), bytes({0xC3}), bytes({0xE4, 0xB8}), bytes({0xED, 0xA0, 0x80}), bytes({0xC0, 0x80})
|
||||||
|
};
|
||||||
|
const std::string cjk = bytes({0xE4, 0xB8, 0xAD}); // U+4E2D
|
||||||
|
|
||||||
|
std::vector<std::string> mismatches;
|
||||||
|
for (std::size_t offset = 0; offset <= 80; ++offset)
|
||||||
|
{
|
||||||
|
std::string text;
|
||||||
|
while (text.size() < offset)
|
||||||
|
{
|
||||||
|
text += cjk;
|
||||||
|
}
|
||||||
|
for (const auto& prefix :
|
||||||
|
{
|
||||||
|
std::string(offset, 'a'), text
|
||||||
|
})
|
||||||
|
{
|
||||||
|
for (const auto& special : specials)
|
||||||
|
{
|
||||||
|
// the closing quote right behind, or in a later block
|
||||||
|
for (const int tail :
|
||||||
|
{
|
||||||
|
0, 1, 3, 20
|
||||||
|
})
|
||||||
|
{
|
||||||
|
std::string doc = "[\"";
|
||||||
|
doc += prefix;
|
||||||
|
doc += special;
|
||||||
|
doc.append(static_cast<std::size_t>(tail), 'b');
|
||||||
|
doc += "\"]";
|
||||||
|
if (outcome(doc, false) != byte_path(doc))
|
||||||
|
{
|
||||||
|
mismatches.push_back(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CAPTURE(mismatches)
|
||||||
|
CHECK(mismatches.empty());
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// json::accept() never throws, so the ranges stay covered without exceptions
|
// json::accept() never throws, so the ranges stay covered without exceptions
|
||||||
@@ -1804,3 +2042,118 @@ TEST_CASE("string scanning kernels")
|
|||||||
CHECK(nlohmann::detail::read_eight_bytes(bytes.data() + 8) == 0xFF0F0E0D0C0B0A09u);
|
CHECK(nlohmann::detail::read_eight_bytes(bytes.data() + 8) == 0xFF0F0E0D0C0B0A09u);
|
||||||
CHECK(nlohmann::detail::read_eight_bytes(reinterpret_cast<const char*>(bytes.data()) + 3) == 0x0B0A090807060504u);
|
CHECK(nlohmann::detail::read_eight_bytes(reinterpret_cast<const char*>(bytes.data()) + 3) == 0x0B0A090807060504u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("find_ascii_copyable_run agrees with is_ascii_copyable for every byte")
|
||||||
|
{
|
||||||
|
// the SWAR mask in find_ascii_copyable_run() encodes the predicate a
|
||||||
|
// second time; check every byte value at every position of a 24-byte buffer
|
||||||
|
// (three words) and for several lengths, with the byte at the end or inside
|
||||||
|
for (unsigned int value = 0; value < 256; ++value)
|
||||||
|
{
|
||||||
|
for (std::size_t position = 0; position < 24; ++position)
|
||||||
|
{
|
||||||
|
std::array<unsigned char, 24> buffer{};
|
||||||
|
buffer.fill(static_cast<unsigned char>('a'));
|
||||||
|
buffer[position] = static_cast<unsigned char>(value);
|
||||||
|
const bool copyable = nlohmann::detail::is_ascii_copyable(static_cast<unsigned char>(value));
|
||||||
|
for (const std::size_t n :
|
||||||
|
{
|
||||||
|
position, position + 1, std::size_t{24}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
if (n > 24)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::size_t expected = (copyable || n <= position) ? n : position;
|
||||||
|
CAPTURE(value)
|
||||||
|
CAPTURE(position)
|
||||||
|
CAPTURE(n)
|
||||||
|
CHECK(nlohmann::detail::find_ascii_copyable_run(buffer.data(), n) == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("UTF-8 validators accept the same sequences")
|
||||||
|
{
|
||||||
|
// The library checks UTF-8 in several places: detail::decode (serializer),
|
||||||
|
// the per-lead-byte switch of the lexer (json::accept), validate_one_utf8
|
||||||
|
// (bulk string scan), utf8_shape, and the byte path of the BON8 reader.
|
||||||
|
// They must accept exactly the same sequences (RFC 3629, section 4).
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
// bytes for the third and fourth position: the first two pairs are checked
|
||||||
|
// against all validators, the others only against the cheap ones
|
||||||
|
const std::array<std::array<unsigned char, 2>, 6> tails =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
{{0x80, 0x80}}, {{0xBF, 0xBF}}, {{0x7F, 0x80}}, {{0xC0, 0x80}}, {{0x80, 0x7F}}, {{0x80, 0xC0}}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (unsigned lead = 0x80; lead <= 0xFF; ++lead)
|
||||||
|
{
|
||||||
|
const auto shape = nlohmann::detail::utf8_shape(static_cast<std::uint8_t>(lead));
|
||||||
|
for (unsigned second = 0; second <= 0xFF; ++second)
|
||||||
|
{
|
||||||
|
for (std::size_t t = 0; t < tails.size(); ++t)
|
||||||
|
{
|
||||||
|
const std::array<unsigned char, 4> bytes = {{static_cast<unsigned char>(lead), static_cast<unsigned char>(second), tails[t][0], tails[t][1]}};
|
||||||
|
|
||||||
|
// expectation from the shape: the sequence is bytes[0..length)
|
||||||
|
const std::size_t length = shape.length;
|
||||||
|
bool expected = length >= 2 && second >= shape.second_lo && second <= shape.second_hi;
|
||||||
|
for (std::size_t i = 2; expected && i < length; ++i)
|
||||||
|
{
|
||||||
|
expected = bytes[i] >= 0x80 && bytes[i] <= 0xBF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the DFA of the serializer
|
||||||
|
bool dfa = length >= 2;
|
||||||
|
if (dfa)
|
||||||
|
{
|
||||||
|
std::uint8_t state = nlohmann::detail::UTF8_ACCEPT;
|
||||||
|
std::uint32_t codep = 0;
|
||||||
|
for (std::size_t i = 0; i < length; ++i)
|
||||||
|
{
|
||||||
|
nlohmann::detail::decode(state, codep, bytes[i]);
|
||||||
|
}
|
||||||
|
dfa = state == nlohmann::detail::UTF8_ACCEPT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the bulk path: exactly the sequence, and one byte short
|
||||||
|
const std::size_t bulk = nlohmann::detail::validate_one_utf8(bytes.data(), length);
|
||||||
|
const std::size_t bulk_short = length > 0 ? nlohmann::detail::validate_one_utf8(bytes.data(), length - 1) : 0;
|
||||||
|
|
||||||
|
CAPTURE(lead)
|
||||||
|
CAPTURE(second)
|
||||||
|
CAPTURE(t)
|
||||||
|
CHECK((dfa == expected && bulk == (expected ? length : 0) && bulk_short == 0));
|
||||||
|
|
||||||
|
if (t < 2 && length >= 2)
|
||||||
|
{
|
||||||
|
// the lexer's byte path
|
||||||
|
const std::string sequence(reinterpret_cast<const char*>(bytes.data()), length); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
CHECK(json::accept("\"" + sequence + "\"") == expected);
|
||||||
|
|
||||||
|
// the byte path of the BON8 reader (a string ends at 0xFF)
|
||||||
|
if (lead >= 0xC2 && lead <= 0xF7)
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> packed(bytes.begin(), bytes.begin() + static_cast<std::ptrdiff_t>(length));
|
||||||
|
packed.push_back(0xFF);
|
||||||
|
const json j = json::from_bon8(packed, true, false);
|
||||||
|
if (expected)
|
||||||
|
{
|
||||||
|
CHECK(j == json(sequence));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK_FALSE((j.is_string() && j.get<std::string>() == sequence));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,9 @@ using nlohmann::json;
|
|||||||
#include <valarray>
|
#include <valarray>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstdint>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <iterator>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -2450,6 +2452,109 @@ std::string parse_error_message_range(IteratorType first, IteratorType last)
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("object keys are moved into the value")
|
||||||
|
{
|
||||||
|
// json_sax_dom_parser::key() moves the key out of the lexer's buffer into
|
||||||
|
// the object, and the lexer starts the next token with an empty buffer.
|
||||||
|
// Keys of every length (inline and allocated strings), keys with escapes,
|
||||||
|
// and duplicate keys must give the same objects as reading the input one
|
||||||
|
// character at a time, and as ordered_json, whose map copies the key.
|
||||||
|
const std::string long_key(100, 'k');
|
||||||
|
const std::string doc = R"({"a": 1, ")" + long_key + R"(": {"b": [2, 3], "sixteen_chars_xx": 4}, )"
|
||||||
|
R"("esc\"apedé": 5, "a": 6, ")" + long_key + R"(x": 7, "": 8, "dup": 9, "dup": 10})";
|
||||||
|
|
||||||
|
const json j = json::parse(doc);
|
||||||
|
const std::list<char> chars(doc.begin(), doc.end());
|
||||||
|
CHECK(j == json::parse(chars.begin(), chars.end()));
|
||||||
|
|
||||||
|
CHECK(j.size() == 6);
|
||||||
|
CHECK(j["a"] == 6);
|
||||||
|
CHECK(j[long_key]["b"] == json({2, 3}));
|
||||||
|
CHECK(j[long_key]["sixteen_chars_xx"] == 4);
|
||||||
|
CHECK(j["esc\"aped\xc3\xa9"] == 5);
|
||||||
|
CHECK(j[long_key + "x"] == 7);
|
||||||
|
CHECK(j[""] == 8);
|
||||||
|
CHECK(j["dup"] == 10);
|
||||||
|
|
||||||
|
const auto o = nlohmann::ordered_json::parse(doc);
|
||||||
|
CHECK(o.size() == 6);
|
||||||
|
CHECK(o[long_key + "x"] == 7);
|
||||||
|
CHECK(json(o) == j);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("text of float tokens")
|
||||||
|
{
|
||||||
|
// For contiguous input, the lexer copies the text of a float token into
|
||||||
|
// its buffer only when a SAX handler asks for it (the library's own
|
||||||
|
// handlers do not). A user handler must still see the exact text, for
|
||||||
|
// every input adapter and in every position of a document.
|
||||||
|
const std::string doc = R"([1.5, -0.0, 0.1, 1e5, -2.5E-4, 12345678901234567890.5, 0.12345678901234567890123,
|
||||||
|
{"a": 3.25, "b": [6.02e23, 1]}, "x", true, 7e-1])";
|
||||||
|
const std::vector<std::string> expected_floats =
|
||||||
|
{
|
||||||
|
"number_float(1.5)", "number_float(-0.0)", "number_float(0.1)", "number_float(1e5)", "number_float(-2.5E-4)",
|
||||||
|
"number_float(12345678901234567890.5)", "number_float(0.12345678901234567890123)", "number_float(3.25)",
|
||||||
|
"number_float(6.02e23)", "number_float(7e-1)"
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto floats = [](const std::vector<std::string>& events)
|
||||||
|
{
|
||||||
|
std::vector<std::string> result;
|
||||||
|
std::copy_if(events.begin(), events.end(), std::back_inserter(result), [](const std::string & e)
|
||||||
|
{
|
||||||
|
return e.compare(0, 13, "number_float(") == 0;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
SECTION("contiguous input")
|
||||||
|
{
|
||||||
|
SaxEventLogger l;
|
||||||
|
CHECK(json::sax_parse(doc, &l));
|
||||||
|
CHECK(floats(l.events) == expected_floats);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("byte input")
|
||||||
|
{
|
||||||
|
const std::vector<std::uint8_t> bytes(doc.begin(), doc.end());
|
||||||
|
SaxEventLogger l;
|
||||||
|
CHECK(json::sax_parse(bytes, &l));
|
||||||
|
CHECK(floats(l.events) == expected_floats);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("stream input")
|
||||||
|
{
|
||||||
|
std::istringstream is(doc);
|
||||||
|
SaxEventLogger l;
|
||||||
|
CHECK(json::sax_parse(is, &l));
|
||||||
|
CHECK(floats(l.events) == expected_floats);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("non-contiguous iterators")
|
||||||
|
{
|
||||||
|
const std::list<char> chars(doc.begin(), doc.end());
|
||||||
|
SaxEventLogger l;
|
||||||
|
CHECK(json::sax_parse(chars.begin(), chars.end(), &l));
|
||||||
|
CHECK(floats(l.events) == expected_floats);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("the library's own handlers do not need the text")
|
||||||
|
{
|
||||||
|
using lexer_adapter = nlohmann::detail::iterator_input_adapter<const char*>;
|
||||||
|
CHECK(nlohmann::detail::sax_ignores_number_text<nlohmann::detail::json_sax_dom_parser<json, lexer_adapter>>::value);
|
||||||
|
CHECK(nlohmann::detail::sax_ignores_number_text<nlohmann::detail::json_sax_dom_callback_parser<json, lexer_adapter>>::value);
|
||||||
|
CHECK(nlohmann::detail::sax_ignores_number_text<nlohmann::detail::json_sax_acceptor<json>>::value);
|
||||||
|
CHECK_FALSE(nlohmann::detail::sax_ignores_number_text<SaxEventLogger>::value);
|
||||||
|
|
||||||
|
// and the values are the same as when the text is read
|
||||||
|
const json j = json::parse(doc);
|
||||||
|
CHECK(j[0] == 1.5);
|
||||||
|
CHECK(j[5].get<double>() == 12345678901234567890.5);
|
||||||
|
CHECK(j[7]["b"][0] == 6.02e23);
|
||||||
|
CHECK(json::accept(doc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("last-read diagnostics are identical across input adapters")
|
TEST_CASE("last-read diagnostics are identical across input adapters")
|
||||||
{
|
{
|
||||||
// The lexer reconstructs the "last read" token lazily for seekable adapters
|
// The lexer reconstructs the "last read" token lazily for seekable adapters
|
||||||
@@ -2847,3 +2952,81 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
TEST_CASE("accept() agrees with parse() on floats around the overflow limit")
|
||||||
|
{
|
||||||
|
// accept() does not convert a float that cannot overflow; the tokens
|
||||||
|
// around the limit (and beyond it) must still be rejected exactly as
|
||||||
|
// parse() rejects them
|
||||||
|
const std::vector<const char*> numbers =
|
||||||
|
{
|
||||||
|
"1e0", "123.456e-5", "1e-400", "-1e-400", "1e307", "1e308", "1e309", "-1e309",
|
||||||
|
"10e307", "100e307", "0.1e309", "0.1e310", "1E+308", "1E+309",
|
||||||
|
"1.7976931348623157e308", "1.7976931348623159e308", "1.8e308", "-1.7976931348623157e308",
|
||||||
|
"12345678901234567e291", "12345678901234567e292", "123456789012345678e290", "123456789012345678e291",
|
||||||
|
"1234567890123456789e289", "1234567890123456789e290", "9999999999999999999e289", "9999999999999999999e290",
|
||||||
|
"0.00000000000000000001e328", "0.00000000000000000001e329"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const char* number : numbers)
|
||||||
|
{
|
||||||
|
for (const std::string& text :
|
||||||
|
{
|
||||||
|
std::string(number), "[" + std::string(number) + "]", "{\"a\":" + std::string(number) + "}"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
INFO(text);
|
||||||
|
const bool parses = !json::parse(text, nullptr, false).is_discarded();
|
||||||
|
CHECK(json::accept(text) == parses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// a DOM parser that reports parse errors without throwing, as in the sax_no_exception example
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
class quiet_dom_parser : public nlohmann::detail::json_sax_dom_parser<BasicJsonType, nlohmann::detail::string_input_adapter_type>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit quiet_dom_parser(BasicJsonType& j)
|
||||||
|
: nlohmann::detail::json_sax_dom_parser<BasicJsonType, nlohmann::detail::string_input_adapter_type>(j, false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const nlohmann::detail::exception& /*unused*/)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
std::string read_before_error(const std::string& text)
|
||||||
|
{
|
||||||
|
BasicJsonType j;
|
||||||
|
quiet_dom_parser<BasicJsonType> sax(j);
|
||||||
|
CHECK(!BasicJsonType::sax_parse(text, &sax));
|
||||||
|
return j.dump();
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("a json_sax_dom_parser keeps the values read before a parse error")
|
||||||
|
{
|
||||||
|
// a json_sax_dom_parser that a user constructs stores every value in place, so a
|
||||||
|
// handler that does not throw on a parse error sees what was read before it
|
||||||
|
const std::vector<std::pair<std::string, std::string>> cases =
|
||||||
|
{
|
||||||
|
{"[1,2,3,]", "[1,2,3]"},
|
||||||
|
{"[[1,[2,3],4],[5,", "[[1,[2,3],4],[5]]"},
|
||||||
|
{R"({"a":1,"b":[1,2,{"c":)", R"({"a":1,"b":[1,2,{"c":null}]})"},
|
||||||
|
{R"({"a":1,"a":)", R"({"a":1})"},
|
||||||
|
{"[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,]", "[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]"},
|
||||||
|
{R"({"k0":0,"k1":1,"k2":{"x":[true,)", R"({"k0":0,"k1":1,"k2":{"x":[true]}})"},
|
||||||
|
{R"([{},[],{"a":[]},x)", R"([{},[],{"a":[]}])"},
|
||||||
|
};
|
||||||
|
for (const auto& c : cases)
|
||||||
|
{
|
||||||
|
CAPTURE(c.first)
|
||||||
|
CHECK(read_before_error<json>(c.first) == c.second);
|
||||||
|
CHECK(read_before_error<nlohmann::ordered_json>(c.first) == c.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -361,6 +361,40 @@ TEST_CASE("constructors")
|
|||||||
CHECK_THROWS_WITH_AS((j.get<std::pair<int, int>>()), "[json.exception.out_of_range.401] array index 1 is out of range", json::out_of_range&);
|
CHECK_THROWS_WITH_AS((j.get<std::pair<int, int>>()), "[json.exception.out_of_range.401] array index 1 is out of range", json::out_of_range&);
|
||||||
CHECK_THROWS_WITH_AS((j.get<std::tuple<int, int>>()), "[json.exception.out_of_range.401] array index 1 is out of range", json::out_of_range&);
|
CHECK_THROWS_WITH_AS((j.get<std::tuple<int, int>>()), "[json.exception.out_of_range.401] array index 1 is out of range", json::out_of_range&);
|
||||||
CHECK_THROWS_WITH_AS((j.get<std::array<int, 3>>()), "[json.exception.out_of_range.401] array index 1 is out of range", json::out_of_range&);
|
CHECK_THROWS_WITH_AS((j.get<std::array<int, 3>>()), "[json.exception.out_of_range.401] array index 1 is out of range", json::out_of_range&);
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
// the exception names the element that has the wrong type
|
||||||
|
CHECK_THROWS_WITH_AS((j.get<std::array<std::string, 3>>()), "[json.exception.type_error.302] (/0) type must be string, but is number", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS((j.get<std::array<std::string, 3>>()), "[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// the elements are converted in order, so a type error comes before a missing element
|
||||||
|
json const j2{1, "two"};
|
||||||
|
std::array<int, 3> a{{0, 0, 0}};
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS((j2.get<std::array<int, 3>>()), "[json.exception.type_error.302] (/1) type must be number, but is string", json::type_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(j2.get_to(a), "[json.exception.type_error.302] (/1) type must be number, but is string", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS((j2.get<std::array<int, 3>>()), "[json.exception.type_error.302] type must be number, but is string", json::type_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(j2.get_to(a), "[json.exception.type_error.302] type must be number, but is string", json::type_error&);
|
||||||
|
#endif
|
||||||
|
CHECK_THROWS_WITH_AS(j.get_to(a), "[json.exception.out_of_range.401] array index 1 is out of range", json::out_of_range&);
|
||||||
|
#if !defined(JSON_NOEXCEPTION) // without exceptions, the get_to calls in CHECK_THROWS_WITH_AS do not run
|
||||||
|
CHECK(a[0] == 1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
json const j3{"one", "two"};
|
||||||
|
CHECK_THROWS_WITH_AS((j3.get<std::array<std::string, 3>>()), "[json.exception.out_of_range.401] array index 2 is out of range", json::out_of_range&);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("std::array from a longer array or from booleans")
|
||||||
|
{
|
||||||
|
json const j{1, 2.5, true, 4u};
|
||||||
|
CHECK(j.get<std::array<int, 3>>() == (std::array<int, 3> {{1, 2, 1}}));
|
||||||
|
std::array<int, 4> a{{0, 0, 0, 0}};
|
||||||
|
j.get_to(a);
|
||||||
|
CHECK(a == (std::array<int, 4> {{1, 2, 1, 4}}));
|
||||||
|
CHECK(j.get<std::array<int, 0>>().empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("std::forward_list<json>")
|
SECTION("std::forward_list<json>")
|
||||||
|
|||||||
@@ -285,6 +285,58 @@ TEST_CASE("custom number types: integers beyond 64 bits")
|
|||||||
CHECK(wide_json::to_bjdata(wide_json::array({uint_big, uint_big}), true, true) == expected_bjdata_array);
|
CHECK(wide_json::to_bjdata(wide_json::array({uint_big, uint_big}), true, true) == expected_bjdata_array);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("integers in arrays and objects")
|
||||||
|
{
|
||||||
|
// runs of numbers and scalar object members are encoded by loops of
|
||||||
|
// their own for the number types up to 64 bits; a wider number type
|
||||||
|
// takes the generic path, which checks the range of every integer
|
||||||
|
const wide_json run = wide_json::array({make_int(1), make_int(-3), make_uint(7), wide_json(1.5), int_big, make_int(5)});
|
||||||
|
const wide_json run_negative = wide_json::array({make_int(1), make_int(-3), int_big_negative});
|
||||||
|
const wide_json run_unsigned = wide_json::array({make_uint(1), make_uint(7), uint_big});
|
||||||
|
const wide_json members = wide_json::object({{"a", make_int(1)}, {"b", wide_json(1.5)}, {"c", int_big}});
|
||||||
|
|
||||||
|
const std::vector<wide_json> failing = {run, run_negative, run_unsigned, members, wrap(run), wrap(members)};
|
||||||
|
for (const auto& value : failing)
|
||||||
|
{
|
||||||
|
CHECK_THROWS_AS(_ = wide_json::to_cbor(value), out_of_range&);
|
||||||
|
CHECK_THROWS_AS(_ = wide_json::to_msgpack(value), out_of_range&);
|
||||||
|
CHECK_THROWS_AS(_ = wide_json::to_bson(wrap(value)), out_of_range&);
|
||||||
|
CHECK_THROWS_AS(_ = wide_json::to_bon8(value), out_of_range&);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
// a failing write leaves no more than the elements before the failing one
|
||||||
|
// (without exceptions, the writes in CHECK_THROWS_AS do not run)
|
||||||
|
std::vector<std::uint8_t> out;
|
||||||
|
CHECK_THROWS_AS(wide_json::to_cbor(run, out), out_of_range&);
|
||||||
|
// (the head of the array, 1, -3, 7, and 1.5 as a single-precision float)
|
||||||
|
CHECK(out == std::vector<std::uint8_t>({0x86, 0x01, 0x22, 0x07, 0xFA, 0x3F, 0xC0, 0x00, 0x00}));
|
||||||
|
out.clear();
|
||||||
|
CHECK_THROWS_AS(wide_json::to_bon8(run, out), out_of_range&);
|
||||||
|
CHECK(!out.empty());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// the same values that fit are written exactly as with 64-bit number types
|
||||||
|
const wide_json fits = wide_json::array({make_int(1), make_int(-3), make_uint(7), wide_json(1.5), make_int(min_int64), make_int(max_int64), make_int(200), make_int(-70000)});
|
||||||
|
const nlohmann::json fits64 = nlohmann::json::array({1, -3, 7, 1.5, (std::numeric_limits<std::int64_t>::min)(), (std::numeric_limits<std::int64_t>::max)(), 200, -70000});
|
||||||
|
CHECK(wide_json::to_cbor(fits) == nlohmann::json::to_cbor(fits64));
|
||||||
|
CHECK(wide_json::to_msgpack(fits) == nlohmann::json::to_msgpack(fits64));
|
||||||
|
CHECK(wide_json::to_ubjson(fits) == nlohmann::json::to_ubjson(fits64));
|
||||||
|
CHECK(wide_json::to_ubjson(fits, true, true) == nlohmann::json::to_ubjson(fits64, true, true));
|
||||||
|
CHECK(wide_json::to_bjdata(fits) == nlohmann::json::to_bjdata(fits64));
|
||||||
|
CHECK(wide_json::to_bjdata(fits, true, true) == nlohmann::json::to_bjdata(fits64, true, true));
|
||||||
|
CHECK(wide_json::to_bon8(fits) == nlohmann::json::to_bon8(fits64));
|
||||||
|
CHECK(wide_json::to_bson(wrap(fits)) == nlohmann::json::to_bson(nlohmann::json::object({{"v", fits64}})));
|
||||||
|
|
||||||
|
const wide_json fit_members = wide_json::object({{"a", make_int(1)}, {"b", make_int(-300)}, {"c", make_uint(max_uint64)}, {"d", wide_json(2.5)}});
|
||||||
|
const nlohmann::json fit_members64 = nlohmann::json::object({{"a", 1}, {"b", -300}, {"c", (std::numeric_limits<std::uint64_t>::max)()}, {"d", 2.5}});
|
||||||
|
CHECK(wide_json::to_cbor(fit_members) == nlohmann::json::to_cbor(fit_members64));
|
||||||
|
CHECK(wide_json::to_msgpack(fit_members) == nlohmann::json::to_msgpack(fit_members64));
|
||||||
|
CHECK(wide_json::to_ubjson(fit_members) == nlohmann::json::to_ubjson(fit_members64));
|
||||||
|
CHECK(wide_json::to_bjdata(fit_members) == nlohmann::json::to_bjdata(fit_members64));
|
||||||
|
CHECK(wide_json::to_bson(fit_members) == nlohmann::json::to_bson(fit_members64));
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("BJData ND-array")
|
SECTION("BJData ND-array")
|
||||||
{
|
{
|
||||||
// an ND-array element beyond 64 bits does not fit any dtype, so the
|
// an ND-array element beyond 64 bits does not fit any dtype, so the
|
||||||
|
|||||||
@@ -185,6 +185,33 @@ 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\"", R"("\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, '[') + R"({"k":[1,{"j":2}]})" + std::string(levels, ']');
|
||||||
|
CHECK(materialized<json_document>(nested) == json::parse(nested));
|
||||||
|
std::string wide = R"({"a":)";
|
||||||
|
wide += nested;
|
||||||
|
wide += R"(,"b":)";
|
||||||
|
wide += nested;
|
||||||
|
wide += "}";
|
||||||
|
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)
|
||||||
@@ -410,6 +437,31 @@ 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());
|
||||||
@@ -1006,6 +1058,49 @@ 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]", R"(["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})");
|
||||||
@@ -1045,6 +1140,28 @@ 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())
|
||||||
@@ -1383,7 +1500,17 @@ 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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -523,6 +523,58 @@ 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;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
using nlohmann::ordered_json;
|
using nlohmann::ordered_json;
|
||||||
using nlohmann::json_document;
|
using nlohmann::json_document;
|
||||||
|
using nlohmann::ordered_json_editable_document;
|
||||||
using nlohmann::json_view;
|
using nlohmann::json_view;
|
||||||
using nlohmann::ordered_json_document;
|
using nlohmann::ordered_json_document;
|
||||||
using nlohmann::ordered_json_view;
|
using nlohmann::ordered_json_view;
|
||||||
@@ -20,6 +21,7 @@ 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>
|
||||||
@@ -28,6 +30,7 @@ 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>
|
||||||
@@ -185,6 +188,133 @@ TEST_CASE("json_view dump")
|
|||||||
CHECK(deep_document.root().dump() == deep);
|
CHECK(deep_document.root().dump() == deep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("ensure_ascii: a character that needs an escape at each position of a string")
|
||||||
|
{
|
||||||
|
// string lengths around the width of the vector steps, in a string value, a key, and a value near the end of the text
|
||||||
|
for (const char* const special :
|
||||||
|
{
|
||||||
|
"\xC3\xA9", "\xE4\xB8\xAD", "\xF0\x9F\x98\x80", "\x7F", "\\n", "\\u0001", "\\\"", "\\\\"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
for (std::size_t length = 0; length <= 40; ++length)
|
||||||
|
{
|
||||||
|
for (std::size_t position = 0; position <= length; ++position)
|
||||||
|
{
|
||||||
|
std::string content(length, 'a');
|
||||||
|
content.insert(position, special);
|
||||||
|
const std::array<std::string, 3> texts =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"\"" + content + "\"",
|
||||||
|
"[\"" + content + "\"]",
|
||||||
|
"{\"" + content + "\":1}",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (const std::string& text : texts)
|
||||||
|
{
|
||||||
|
CAPTURE(text)
|
||||||
|
const ordered_json_document d = ordered_json_document::parse(text);
|
||||||
|
const ordered_json j = ordered_json::parse(text);
|
||||||
|
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
||||||
|
CHECK(d.root().dump(2, ' ', true) == j.dump(2, ' ', true));
|
||||||
|
CHECK(d.root().dump() == j.dump());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ensure_ascii: every character of the Basic Multilingual Plane and some beyond")
|
||||||
|
{
|
||||||
|
// (the two- and three-byte sequences, including the ranges that need a check: E0, ED, EF BF BF)
|
||||||
|
std::string text = "[";
|
||||||
|
std::size_t in_chunk = 0;
|
||||||
|
const auto append = [&](std::uint32_t cp)
|
||||||
|
{
|
||||||
|
if (cp < 0x80)
|
||||||
|
{
|
||||||
|
if (cp < 0x20 || cp == '"' || cp == '\\')
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
text += static_cast<char>(cp);
|
||||||
|
}
|
||||||
|
else if (cp < 0x800)
|
||||||
|
{
|
||||||
|
text += static_cast<char>(0xC0 | (cp >> 6));
|
||||||
|
text += static_cast<char>(0x80 | (cp & 0x3F));
|
||||||
|
}
|
||||||
|
else if (cp < 0x10000)
|
||||||
|
{
|
||||||
|
text += static_cast<char>(0xE0 | (cp >> 12));
|
||||||
|
text += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
|
||||||
|
text += static_cast<char>(0x80 | (cp & 0x3F));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
text += static_cast<char>(0xF0 | (cp >> 18));
|
||||||
|
text += static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
|
||||||
|
text += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
|
||||||
|
text += static_cast<char>(0x80 | (cp & 0x3F));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (std::uint32_t cp = 0x20; cp < 0x10400; ++cp)
|
||||||
|
{
|
||||||
|
if (cp >= 0xD800 && cp < 0xE000)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (in_chunk == 0)
|
||||||
|
{
|
||||||
|
text += text.size() > 1 ? ",\"" : "\"";
|
||||||
|
}
|
||||||
|
append(cp);
|
||||||
|
if (++in_chunk == 37)
|
||||||
|
{
|
||||||
|
text += "\"";
|
||||||
|
in_chunk = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text += in_chunk != 0 ? "\"]" : "]";
|
||||||
|
const ordered_json_document d = ordered_json_document::parse(text);
|
||||||
|
const ordered_json j = ordered_json::parse(text);
|
||||||
|
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
||||||
|
CHECK(d.root().dump(2, ' ', true) == j.dump(2, ' ', true));
|
||||||
|
CHECK(d.root().dump() == j.dump());
|
||||||
|
CHECK(d.root().dump(2) == j.dump(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("deep nesting, indented")
|
||||||
|
{
|
||||||
|
// indentations of up to and beyond the width of the writer's fixed-size copy
|
||||||
|
for (const int depth :
|
||||||
|
{
|
||||||
|
1, 5, 20, 70, 300
|
||||||
|
})
|
||||||
|
{
|
||||||
|
std::string text;
|
||||||
|
for (int i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
text += i % 2 == 0 ? "[1," : R"({"k":2,"\u00e9":)";
|
||||||
|
}
|
||||||
|
text += "[]";
|
||||||
|
for (int i = depth - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
text += i % 2 == 0 ? "]" : "}";
|
||||||
|
}
|
||||||
|
const ordered_json_document d = ordered_json_document::parse(text);
|
||||||
|
const ordered_json j = ordered_json::parse(text);
|
||||||
|
for (const int indent :
|
||||||
|
{
|
||||||
|
0, 1, 2, 3, 8
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CHECK(d.root().dump(indent) == j.dump(indent));
|
||||||
|
CHECK(d.root().dump(indent, '\t') == j.dump(indent, '\t'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("the output buffer of a small value is small")
|
SECTION("the output buffer of a small value is small")
|
||||||
{
|
{
|
||||||
// an escaped key after the value: its node lies in the arena, so the
|
// an escaped key after the value: its node lies in the arena, so the
|
||||||
@@ -219,6 +349,76 @@ TEST_CASE("json_view dump")
|
|||||||
CHECK(d.root()["a"].dump(-1, ' ', true).size() == 2 + (5000 * 6));
|
CHECK(d.root()["a"].dump(-1, ' ', true).size() == 2 + (5000 * 6));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("ensure_ascii with strings of the source, decoded strings, and edited strings")
|
||||||
|
{
|
||||||
|
// plain ASCII, non-ASCII, escaped (decoded), and mixed strings, as values and as keys;
|
||||||
|
// the second text has a DEL, which ensure_ascii writes as an escape
|
||||||
|
for (const char* const del :
|
||||||
|
{"", "\x7F"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
const std::string text = std::string("{\"plain\":\"abc\",\"caf\xC3\xA9\":\"\xC3\xA9\xE2\x82\xAC\xF0\x9F\x98\x80\",")
|
||||||
|
+ "\"esc\\n\\\"\":\"a\\tb\\u00e4\\ud83d\\ude00\",\"del\":\"a" + del + "b\",\"\":\"\"," // NOLINT(modernize-raw-string-literal)
|
||||||
|
+ "\"list\":[\"x\",\"\xC3\xBC\",\"\\\\\",\"long ASCII string with more than thirty-two bytes in it\","
|
||||||
|
+ "\"long string with more than thirty-two bytes and one non-ASCII byte \xC3\xB6\"],"
|
||||||
|
+ "\"n\":[1,2.5,-0,true,null]}";
|
||||||
|
const ordered_json j = ordered_json::parse(text);
|
||||||
|
const ordered_json_document d = ordered_json_document::parse(text);
|
||||||
|
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
||||||
|
CHECK(d.root().dump(4, ' ', true) == j.dump(4, ' ', true));
|
||||||
|
CHECK(d.root().dump() == j.dump());
|
||||||
|
CHECK(d.root()["list"].dump(-1, ' ', true) == j["list"].dump(-1, ' ', true));
|
||||||
|
CHECK(d.root()["del"].dump(-1, ' ', true) == j["del"].dump(-1, ' ', true));
|
||||||
|
|
||||||
|
// strings that an edit wrote (the source strings that they replace were plain)
|
||||||
|
ordered_json_editable_document e = ordered_json_editable_document::parse(text);
|
||||||
|
ordered_json k = j;
|
||||||
|
e.set(e.root()["plain"], std::string("\xC3\xA4gypten"));
|
||||||
|
k["plain"] = "\xC3\xA4gypten";
|
||||||
|
e.set(e.root()["list"][0], std::string("ascii after edit"));
|
||||||
|
k["list"][0] = "ascii after edit";
|
||||||
|
e.set(e.root()["list"][1], std::string("\xC3\xA4"));
|
||||||
|
k["list"][1] = "\xC3\xA4";
|
||||||
|
e.set(e.root()["del"], std::string("tab\there"));
|
||||||
|
k["del"] = "tab\there";
|
||||||
|
CHECK(e.root().dump(-1, ' ', true) == k.dump(-1, ' ', true));
|
||||||
|
CHECK(e.root().dump(2, ' ', true) == k.dump(2, ' ', true));
|
||||||
|
CHECK(e.root().dump() == k.dump());
|
||||||
|
|
||||||
|
// strings that were edited from or to ASCII text
|
||||||
|
e.set(e.root()["list"][2], std::string("\xC3\xA4"));
|
||||||
|
k["list"][2] = "\xC3\xA4";
|
||||||
|
e.set(e.root()["list"][4], std::string("plain again"));
|
||||||
|
k["list"][4] = "plain again";
|
||||||
|
CHECK(e.root().dump(-1, ' ', true) == k.dump(-1, ' ', true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ensure_ascii with DEL or a non-ASCII character at every position")
|
||||||
|
{
|
||||||
|
// the check whether a string of the source has no byte >= 0x7F reads
|
||||||
|
// eight bytes at a time and then the rest one by one
|
||||||
|
for (std::size_t length = 1; length <= 20; ++length)
|
||||||
|
{
|
||||||
|
for (std::size_t pos = 0; pos < length; ++pos)
|
||||||
|
{
|
||||||
|
for (const char* const c :
|
||||||
|
{
|
||||||
|
"\x7F", "\x7E", "\xC3\xA4", "\xC3\xBF" // NOLINT(modernize-raw-string-literal)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
const std::string s = std::string(pos, 'a') + c + std::string(length - pos - 1, 'b');
|
||||||
|
std::string text = "[\"";
|
||||||
|
text.append(s).append("\",{\"").append(s).append("\":1}]");
|
||||||
|
const json_document d = json_document::parse(text);
|
||||||
|
const json j = json::parse(text);
|
||||||
|
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
||||||
|
CHECK(d.root().dump(1, ' ', true) == j.dump(1, ' ', true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("streams and discarded views")
|
SECTION("streams and discarded views")
|
||||||
{
|
{
|
||||||
const json_document d = json_document::parse(R"({"a": [1, 2]})");
|
const json_document d = json_document::parse(R"({"a": [1, 2]})");
|
||||||
@@ -527,6 +727,152 @@ 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 = nlohmann::detail::conditional_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); // NOLINT(abseil-string-find-str-contains)
|
||||||
|
CHECK(max_displacement(numbered(format, 600, 7)) < nlohmann::detail::view::index_max_displacement);
|
||||||
|
}
|
||||||
|
std::mt19937_64 rng(7); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
||||||
|
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;
|
||||||
|
// (there are 26 and 676 keys of one and two letters)
|
||||||
|
std::size_t wanted = 700;
|
||||||
|
if (length == 1)
|
||||||
|
{
|
||||||
|
wanted = 26;
|
||||||
|
}
|
||||||
|
else if (length == 2)
|
||||||
|
{
|
||||||
|
wanted = 500;
|
||||||
|
}
|
||||||
|
while (distinct.size() < wanted)
|
||||||
|
{
|
||||||
|
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); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
||||||
|
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)
|
||||||
|
|||||||
@@ -661,6 +661,115 @@ 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
|
||||||
|
|||||||
@@ -682,6 +682,26 @@ TEST_CASE("json_view images: ownership")
|
|||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
TEST_CASE("json_view images: errors")
|
TEST_CASE("json_view images: errors")
|
||||||
{
|
{
|
||||||
|
SECTION("invalid UTF-8 of a decoded string: the compact dump writes it, the others throw")
|
||||||
|
{
|
||||||
|
const json_document d = json_document::parse(R"({"k\n":"\u00e9x"})");
|
||||||
|
std::vector<std::uint8_t> image = d.save();
|
||||||
|
const std::size_t arena = text_at(image) + header_size_field(image, 16) + 1;
|
||||||
|
image[arena + 3] = 0x28; // the key is "k\n"; the value starts at 3: 0xC3 0x28 is invalid
|
||||||
|
const json_document loaded = json_document::load(image, image_check::bounds);
|
||||||
|
CHECK(loaded.root().dump().size() == 13);
|
||||||
|
for (const int indent :
|
||||||
|
{
|
||||||
|
0, 2
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CHECK_THROWS_AS(loaded.root().dump(indent), json::type_error);
|
||||||
|
CHECK_THROWS_AS(loaded.root().dump(indent, ' ', true), json::type_error);
|
||||||
|
}
|
||||||
|
CHECK_THROWS_AS(loaded.root().dump(-1, ' ', true), json::type_error);
|
||||||
|
CHECK_THROWS_WITH_AS(loaded.root().dump(2), "[json.exception.type_error.316] invalid UTF-8 byte at index 1: 0x28", json::type_error&);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("a literal as the root: dump() after loading")
|
SECTION("a literal as the root: dump() after loading")
|
||||||
{
|
{
|
||||||
for (const char* text :
|
for (const char* text :
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
using nlohmann::ordered_json;
|
using nlohmann::ordered_json;
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
TEST_CASE("ordered_json")
|
TEST_CASE("ordered_json")
|
||||||
{
|
{
|
||||||
json j;
|
json j;
|
||||||
@@ -237,3 +242,276 @@ TEST_CASE("regression test for issue #5673 - ordered_json::emplace with a non-rv
|
|||||||
CHECK(oj.dump() == "{\"a\":1}");
|
CHECK(oj.dump() == "{\"a\":1}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// the sizes around which the DOM parser changes how it stores the elements of
|
||||||
|
// an array (16) and the members of an object in a vector (32, and 64 for the
|
||||||
|
// key index)
|
||||||
|
const std::array<std::size_t, 17> container_sizes = {{0, 1, 2, 3, 15, 16, 17, 18, 31, 32, 33, 34, 63, 64, 65, 66, 200}};
|
||||||
|
|
||||||
|
template<typename Json>
|
||||||
|
std::string array_text(const std::size_t n, const std::size_t depth)
|
||||||
|
{
|
||||||
|
std::string text = "[";
|
||||||
|
for (std::size_t i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
text += (i == 0 ? "" : ",");
|
||||||
|
// alternate scalars, empty containers and nested containers
|
||||||
|
switch (i % 4)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
text += std::to_string(i);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
text += "\"s" + std::to_string(i) + "\"";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
text += depth == 0 ? "[]" : array_text<Json>(n / 2, depth - 1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
text += depth == 0 ? "{}" : "{\"a\":" + array_text<Json>(n / 3, depth - 1) + ",\"b\":null}";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Json>
|
||||||
|
std::string object_text(const std::size_t n, const std::size_t depth, const bool duplicates)
|
||||||
|
{
|
||||||
|
std::string text = "{";
|
||||||
|
for (std::size_t i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
text += (i == 0 ? "" : ",");
|
||||||
|
text += "\"key" + std::to_string(i) + "\":";
|
||||||
|
switch (i % 4)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
text += std::to_string(i);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
text += depth == 0 ? "[]" : array_text<Json>(n / 2, depth - 1);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
text += depth == 0 ? "{}" : object_text<Json>(n / 2, depth - 1, duplicates);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
text += "\"value\"";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (duplicates)
|
||||||
|
{
|
||||||
|
// keys that are there already, the first and the last one, with other values
|
||||||
|
text += n == 0 ? "" : ",";
|
||||||
|
text += R"("key0":[1,2],"key)";
|
||||||
|
text += std::to_string(n - 1);
|
||||||
|
text += R"(":{"again":true})";
|
||||||
|
}
|
||||||
|
return text + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Json>
|
||||||
|
void check_same_dump(const std::string& text)
|
||||||
|
{
|
||||||
|
// the text is what dump() writes for the parsed value, and the value
|
||||||
|
// survives a second round trip: arrays keep their elements in order, an
|
||||||
|
// ordered_json keeps its members in order of appearance
|
||||||
|
const Json parsed = Json::parse(text);
|
||||||
|
const Json again = Json::parse(parsed.dump());
|
||||||
|
CHECK(parsed == again);
|
||||||
|
CHECK(parsed.dump() == again.dump());
|
||||||
|
CHECK(Json::accept(text));
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("parsing containers of sizes around the limits of the DOM parser")
|
||||||
|
{
|
||||||
|
SECTION("arrays")
|
||||||
|
{
|
||||||
|
for (const std::size_t n : container_sizes)
|
||||||
|
{
|
||||||
|
const std::string text = array_text<json>(n, 2);
|
||||||
|
INFO("size ", n);
|
||||||
|
|
||||||
|
const json parsed = json::parse(text);
|
||||||
|
CHECK(parsed.is_array());
|
||||||
|
CHECK(parsed.size() == n);
|
||||||
|
for (std::size_t i = 0; i < n; i += 4)
|
||||||
|
{
|
||||||
|
CHECK(parsed[i] == i);
|
||||||
|
}
|
||||||
|
check_same_dump<json>(text);
|
||||||
|
check_same_dump<ordered_json>(text);
|
||||||
|
CHECK(ordered_json::parse(text).dump() == parsed.dump());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("objects")
|
||||||
|
{
|
||||||
|
for (const bool duplicates :
|
||||||
|
{
|
||||||
|
false, true
|
||||||
|
})
|
||||||
|
{
|
||||||
|
for (const std::size_t n : container_sizes)
|
||||||
|
{
|
||||||
|
const std::string text = object_text<json>(n, 2, duplicates);
|
||||||
|
INFO("size ", n, " duplicates ", duplicates);
|
||||||
|
|
||||||
|
const json parsed = json::parse(text);
|
||||||
|
const ordered_json ordered = ordered_json::parse(text);
|
||||||
|
CHECK(parsed.is_object());
|
||||||
|
CHECK(ordered.is_object());
|
||||||
|
CHECK(parsed.size() == ordered.size());
|
||||||
|
// the same members, in the order of their first appearance
|
||||||
|
std::size_t i = 0;
|
||||||
|
for (auto it = ordered.begin(); it != ordered.end(); ++it, ++i)
|
||||||
|
{
|
||||||
|
if (i < n)
|
||||||
|
{
|
||||||
|
CHECK(it.key() == "key" + std::to_string(i));
|
||||||
|
}
|
||||||
|
CHECK(parsed.at(it.key()) == json(it.value()));
|
||||||
|
}
|
||||||
|
if (duplicates && n > 1)
|
||||||
|
{
|
||||||
|
// the later value replaced the earlier one in its place
|
||||||
|
CHECK(ordered.at("key0") == ordered_json::parse("[1,2]"));
|
||||||
|
CHECK(ordered.at("key" + std::to_string(n - 1)) == ordered_json::parse("{\"again\":true}"));
|
||||||
|
CHECK(ordered.begin().key() == "key0");
|
||||||
|
CHECK(ordered.size() == n);
|
||||||
|
}
|
||||||
|
check_same_dump<json>(text);
|
||||||
|
check_same_dump<ordered_json>(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("objects with many different and repeated keys")
|
||||||
|
{
|
||||||
|
// 300 members of which a third repeat an earlier key
|
||||||
|
std::string text = "{";
|
||||||
|
for (std::size_t i = 0; i < 300; ++i)
|
||||||
|
{
|
||||||
|
text += (i == 0 ? "" : ",");
|
||||||
|
text += "\"k" + std::to_string(i % 200) + "\":" + std::to_string(i);
|
||||||
|
}
|
||||||
|
text += '}';
|
||||||
|
|
||||||
|
const ordered_json ordered = ordered_json::parse(text);
|
||||||
|
const json parsed = json::parse(text);
|
||||||
|
CHECK(ordered.size() == 200);
|
||||||
|
CHECK(parsed.size() == 200);
|
||||||
|
std::size_t i = 0;
|
||||||
|
for (auto it = ordered.begin(); it != ordered.end(); ++it, ++i)
|
||||||
|
{
|
||||||
|
CHECK(it.key() == "k" + std::to_string(i));
|
||||||
|
// the last value written
|
||||||
|
CHECK(it.value() == (i < 100 ? i + 200 : i));
|
||||||
|
CHECK(parsed.at(it.key()) == json(it.value()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// a deterministic source of structure for parsing tests: builds a JSON text
|
||||||
|
// and, at the same time, the value it has to parse to
|
||||||
|
class tree_generator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit tree_generator(std::uint32_t seed) : state(seed) {}
|
||||||
|
|
||||||
|
template<typename Json>
|
||||||
|
Json make(std::string& text, const std::size_t depth)
|
||||||
|
{
|
||||||
|
switch (next(depth == 0 ? 5 : 9))
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
text += "null";
|
||||||
|
return Json(nullptr);
|
||||||
|
case 1:
|
||||||
|
text += "true";
|
||||||
|
return Json(true);
|
||||||
|
case 2:
|
||||||
|
{
|
||||||
|
const int n = static_cast<int>(next(2000)) - 1000;
|
||||||
|
text += std::to_string(n);
|
||||||
|
return Json(n);
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
text += "\"text\"";
|
||||||
|
return Json("text");
|
||||||
|
case 4:
|
||||||
|
text += "2.5";
|
||||||
|
return Json(2.5);
|
||||||
|
case 5:
|
||||||
|
case 6:
|
||||||
|
case 7:
|
||||||
|
{
|
||||||
|
Json array = Json::array();
|
||||||
|
text += '[';
|
||||||
|
const std::size_t n = size();
|
||||||
|
for (std::size_t i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
text += (i == 0 ? "" : ",");
|
||||||
|
array.push_back(make<Json>(text, depth - 1));
|
||||||
|
}
|
||||||
|
text += ']';
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
Json object = Json::object();
|
||||||
|
text += '{';
|
||||||
|
const std::size_t n = size();
|
||||||
|
for (std::size_t i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
const std::string key = "k" + std::to_string(i);
|
||||||
|
text += (i == 0 ? "\"" : ",\"") + key + "\":";
|
||||||
|
object[key] = make<Json>(text, depth - 1);
|
||||||
|
}
|
||||||
|
text += '}';
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::uint32_t next(const std::uint32_t bound)
|
||||||
|
{
|
||||||
|
state = (state * 1664525U) + 1013904223U;
|
||||||
|
return (state >> 8) % bound;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t size()
|
||||||
|
{
|
||||||
|
return container_sizes[next(static_cast<std::uint32_t>(container_sizes.size()))];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t state;
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("parsing random trees of arrays and objects")
|
||||||
|
{
|
||||||
|
for (std::uint32_t seed = 1; seed <= 150; ++seed)
|
||||||
|
{
|
||||||
|
INFO("seed ", seed);
|
||||||
|
|
||||||
|
std::string text;
|
||||||
|
std::string ordered_text;
|
||||||
|
const json expected = tree_generator(seed).make<json>(text, 3);
|
||||||
|
const auto ordered_expected = tree_generator(seed).make<ordered_json>(ordered_text, 3);
|
||||||
|
CHECK(text == ordered_text);
|
||||||
|
|
||||||
|
CHECK(json::parse(text) == expected);
|
||||||
|
CHECK(json::accept(text));
|
||||||
|
const ordered_json ordered = ordered_json::parse(text);
|
||||||
|
CHECK(ordered == ordered_expected);
|
||||||
|
CHECK(ordered.dump() == ordered_expected.dump());
|
||||||
|
CHECK(json::parse(text).dump() == expected.dump());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1417,6 +1417,27 @@ void check_dump_against_reference(const std::string& s, const bool with_strict =
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("every ASCII character at every position of a short string is written like the reference")
|
||||||
|
{
|
||||||
|
// dump_escaped() copies strings of up to 32 bytes of printable ASCII after
|
||||||
|
// a table lookup per byte, and hands everything else to the scanners; put
|
||||||
|
// each ASCII character and a 2-byte character at every position of
|
||||||
|
// strings of up to 40 bytes to cover both sides of that bound
|
||||||
|
for (std::size_t length = 1; length <= 40; ++length)
|
||||||
|
{
|
||||||
|
for (std::size_t pos = 0; pos < length; ++pos)
|
||||||
|
{
|
||||||
|
for (int c = 0; c < 0x80; ++c)
|
||||||
|
{
|
||||||
|
std::string s(length, 'a');
|
||||||
|
s[pos] = static_cast<char>(c);
|
||||||
|
check_dump_against_reference(s);
|
||||||
|
}
|
||||||
|
check_dump_against_reference(std::string(pos, 'a') + "\xC3\xA4" + std::string(length - pos - 1, 'a'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("invalid UTF-8 at every position is handled like the reference")
|
TEST_CASE("invalid UTF-8 at every position is handled like the reference")
|
||||||
{
|
{
|
||||||
// ill-formed byte sequences; those that end in a truncated sequence are
|
// ill-formed byte sequences; those that end in a truncated sequence are
|
||||||
@@ -1559,3 +1580,94 @@ TEST_CASE("invalid UTF-8 at every position is handled like the reference")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("short strings are written like the reference, as values and as keys")
|
||||||
|
{
|
||||||
|
// strings of up to 16 characters (32 with vector instructions) that need no
|
||||||
|
// escaping are written as a whole (together with the quotes and, for a key,
|
||||||
|
// the colon); everything else, including a string that is merely short,
|
||||||
|
// takes the general path.
|
||||||
|
// Every length around the edges of the size classes of the fast path is
|
||||||
|
// combined with each kind of byte that must send a string to the general
|
||||||
|
// path, at each position, and compared with the reference for values and,
|
||||||
|
// via the value, for keys.
|
||||||
|
const std::vector<std::string> specials =
|
||||||
|
{
|
||||||
|
"\"", "\\", "\n", "\x1f", "\x7f", "\xc3\xa9", "\xe2\x82\xac", "\xf0\x9f\x98\x80", "\xff", "\x80", "\xc3"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (std::size_t length = 0; length <= 36; ++length)
|
||||||
|
{
|
||||||
|
const std::string plain(length, 'k');
|
||||||
|
check_dump_against_reference(plain, false);
|
||||||
|
|
||||||
|
for (const auto& special : specials)
|
||||||
|
{
|
||||||
|
for (std::size_t pos = 0; pos <= length; ++pos)
|
||||||
|
{
|
||||||
|
std::string s = plain;
|
||||||
|
s.insert(pos, special);
|
||||||
|
check_dump_against_reference(s, false);
|
||||||
|
|
||||||
|
for (const bool ensure_ascii :
|
||||||
|
{
|
||||||
|
false, true
|
||||||
|
})
|
||||||
|
{
|
||||||
|
// a key and a value are written alike, in compact and
|
||||||
|
// in pretty-printed form
|
||||||
|
const json value = s;
|
||||||
|
const json object = json::object({{s, 1}});
|
||||||
|
for (const auto error_handler :
|
||||||
|
{
|
||||||
|
json::error_handler_t::replace, json::error_handler_t::keep
|
||||||
|
})
|
||||||
|
{
|
||||||
|
const std::string quoted = value.dump(-1, ' ', ensure_ascii, error_handler);
|
||||||
|
CHECK(object.dump(-1, ' ', ensure_ascii, error_handler) == "{" + quoted + ":1}");
|
||||||
|
CHECK(object.dump(2, ' ', ensure_ascii, error_handler) == "{\n " + quoted + ": 1\n}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("runs of multi-byte characters are escaped like the reference under ensure_ascii")
|
||||||
|
{
|
||||||
|
// a run of well-formed multi-byte characters is escaped straight into the
|
||||||
|
// write buffer; it has to flush that mid-run, stop at an ASCII character or
|
||||||
|
// an ill-formed sequence and leave those to the general path, and pick up
|
||||||
|
// behind what the general path has pending
|
||||||
|
const std::vector<std::string> characters =
|
||||||
|
{
|
||||||
|
"\xc2\x80", "\xdf\xbf", "\xe0\xa0\x80", "\xe2\x82\xac", "\xed\x9f\xbf", "\xef\xbf\xbf",
|
||||||
|
"\xf0\x90\x80\x80", "\xf0\x9f\x98\x80", "\xf4\x8f\xbf\xbf"
|
||||||
|
};
|
||||||
|
const std::vector<std::string> interruptions =
|
||||||
|
{
|
||||||
|
"", "a", "\n", "\"", "\x7f", "\xff", "\x80", "\xc3", "\xe2\x82", "\xf0\x9f\x98", "\xed\xa0\x80"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (std::size_t shift = 0; shift <= 13; ++shift)
|
||||||
|
{
|
||||||
|
for (const auto& interruption : interruptions)
|
||||||
|
{
|
||||||
|
for (const std::size_t count :
|
||||||
|
{
|
||||||
|
std::size_t{1}, std::size_t{2}, std::size_t{85}, std::size_t{200}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
std::string s(shift, 'x');
|
||||||
|
for (std::size_t i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
s += characters[(i + shift) % characters.size()];
|
||||||
|
}
|
||||||
|
s += interruption;
|
||||||
|
s += characters[shift % characters.size()];
|
||||||
|
s += characters[(shift + 3) % characters.size()];
|
||||||
|
check_dump_against_reference(s, shift < 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loaded 100 of 105 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user