mirror of
https://github.com/nlohmann/json.git
synced 2026-10-01 04:00:31 +00:00
Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e8e8e219b | ||
|
|
c261578431 | ||
|
|
35802e78d6 | ||
|
|
791cd88dfc | ||
|
|
7d7055ec50 | ||
|
|
3b6ae43c53 | ||
|
|
5f1727cef2 | ||
|
|
c16dd7e4f5 | ||
|
|
792853d725 | ||
|
|
4bdf1b7e74 | ||
|
|
b1e9d98e41 | ||
|
|
f855d257df | ||
|
|
3e683e9c04 | ||
|
|
d1d84ed9af | ||
|
|
de8529f99b | ||
|
|
677794f076 | ||
|
|
437a95cfdb | ||
|
|
c8735246d0 | ||
|
|
9adb510a0d |
@@ -35,6 +35,7 @@ jobs:
|
|||||||
MAIN_DIR: ${{ github.workspace }}/main
|
MAIN_DIR: ${{ github.workspace }}/main
|
||||||
INCLUDE_DIR: ${{ github.workspace }}/main/single_include/nlohmann
|
INCLUDE_DIR: ${{ github.workspace }}/main/single_include/nlohmann
|
||||||
TOOL_DIR: ${{ github.workspace }}/tools/tools/amalgamate
|
TOOL_DIR: ${{ github.workspace }}/tools/tools/amalgamate
|
||||||
|
NATVIS_TOOL_DIR: ${{ github.workspace }}/tools/tools/generate_natvis
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
@@ -61,6 +62,48 @@ jobs:
|
|||||||
python3 -mvenv venv
|
python3 -mvenv venv
|
||||||
venv/bin/pip3 install -r $MAIN_DIR/tools/astyle/requirements.txt
|
venv/bin/pip3 install -r $MAIN_DIR/tools/astyle/requirements.txt
|
||||||
|
|
||||||
|
- name: Install generate_natvis dependencies
|
||||||
|
run: pip3 install -r $NATVIS_TOOL_DIR/requirements.txt
|
||||||
|
|
||||||
|
- name: Regenerate the tools/macro_builder tables in macro_scope.hpp
|
||||||
|
run: |
|
||||||
|
cd $MAIN_DIR
|
||||||
|
|
||||||
|
# Built from this PR's own tools/macro_builder/main.cpp, not a
|
||||||
|
# develop checkout: unlike amalgamate.py and generate_natvis.py,
|
||||||
|
# this tool has no other source of truth to check against (its
|
||||||
|
# own README documents that it must reproduce macro_scope.hpp
|
||||||
|
# byte for byte), so there is nothing to gain from checking out a
|
||||||
|
# separate copy, and doing so would make this step fail on a PR
|
||||||
|
# that adds support for a new dispatch table until that PR itself
|
||||||
|
# merges to develop, the same way generate_natvis.py's --version
|
||||||
|
# requirement briefly did.
|
||||||
|
TMPDIR=$(mktemp -d ./macro_builder_check.XXXXXX)
|
||||||
|
c++ -std=c++11 tools/macro_builder/main.cpp -o "$TMPDIR/macro_builder"
|
||||||
|
"$TMPDIR/macro_builder" > "$TMPDIR/paste.hpp"
|
||||||
|
"$TMPDIR/macro_builder" type_body > "$TMPDIR/type_body.hpp"
|
||||||
|
|
||||||
|
# Splice the (still unindented) generated blocks back into
|
||||||
|
# macro_scope.hpp; the astyle pass below indents their
|
||||||
|
# continuation lines the same way it does for the rest of
|
||||||
|
# include/, so a correctly regenerated file comes out unchanged.
|
||||||
|
awk -v newfile="$TMPDIR/paste.hpp" '
|
||||||
|
BEGIN { while ((getline line < newfile) > 0) { new = new line "\n" } }
|
||||||
|
/^#define NLOHMANN_JSON_EXPAND\( x \) x$/ { printf "%s", new; skip=1 }
|
||||||
|
skip && /^#define NLOHMANN_JSON_DOUBLE_PASTE63\(/ { skip=0; next }
|
||||||
|
skip { next }
|
||||||
|
{ print }
|
||||||
|
' include/nlohmann/detail/macro_scope.hpp > "$TMPDIR/macro_scope_1.hpp"
|
||||||
|
awk -v newfile="$TMPDIR/type_body.hpp" '
|
||||||
|
BEGIN { while ((getline line < newfile) > 0) { new = new line "\n" } }
|
||||||
|
/^#define NLOHMANN_JSON_TYPE_BODY\(Prefix, \.\.\.\)/ { printf "%s", new; skip=1 }
|
||||||
|
skip && /^[[:space:]]*NLOHMANN_JSON_TYPE_BODY_SENTINEL\)\)$/ { skip=0; next }
|
||||||
|
skip { next }
|
||||||
|
{ print }
|
||||||
|
' "$TMPDIR/macro_scope_1.hpp" > "$TMPDIR/macro_scope_2.hpp"
|
||||||
|
mv "$TMPDIR/macro_scope_2.hpp" include/nlohmann/detail/macro_scope.hpp
|
||||||
|
rm -rf "$TMPDIR"
|
||||||
|
|
||||||
- name: Regenerate amalgamation, formatting, and BUILD.bazel
|
- name: Regenerate amalgamation, formatting, and BUILD.bazel
|
||||||
run: |
|
run: |
|
||||||
cd $MAIN_DIR
|
cd $MAIN_DIR
|
||||||
@@ -88,6 +131,19 @@ jobs:
|
|||||||
${{ github.workspace }}/venv/bin/astyle --project=tools/astyle/.astylerc --suffix=none --quiet \
|
${{ github.workspace }}/venv/bin/astyle --project=tools/astyle/.astylerc --suffix=none --quiet \
|
||||||
$(find $SOURCE_DIRS -type f \( -name '*.hpp' -o -name '*.cpp' -o -name '*.cu' \) -not -path 'tests/thirdparty/*' -not -path 'tests/abi/include/nlohmann/*' | sort)
|
$(find $SOURCE_DIRS -type f \( -name '*.hpp' -o -name '*.cpp' -o -name '*.cu' \) -not -path 'tests/thirdparty/*' -not -path 'tests/abi/include/nlohmann/*' | sort)
|
||||||
|
|
||||||
|
- name: Regenerate nlohmann_json.natvis
|
||||||
|
run: |
|
||||||
|
cd $MAIN_DIR
|
||||||
|
# Pass --version explicitly so this step also works with the tool
|
||||||
|
# copy from develop before this repository's own generate_natvis.py
|
||||||
|
# learns to derive the version itself: the older script requires
|
||||||
|
# --version, and the newer one accepts it as an explicit override.
|
||||||
|
ABI_MACROS=include/nlohmann/detail/abi_macros.hpp
|
||||||
|
VERSION_MAJOR=$(grep -m1 'define NLOHMANN_JSON_VERSION_MAJOR' $ABI_MACROS | grep -o '[0-9]\+')
|
||||||
|
VERSION_MINOR=$(grep -m1 'define NLOHMANN_JSON_VERSION_MINOR' $ABI_MACROS | grep -o '[0-9]\+')
|
||||||
|
VERSION_PATCH=$(grep -m1 'define NLOHMANN_JSON_VERSION_PATCH' $ABI_MACROS | grep -o '[0-9]\+')
|
||||||
|
python3 $NATVIS_TOOL_DIR/generate_natvis.py --version "$VERSION_MAJOR.$VERSION_MINOR.$VERSION_PATCH" $MAIN_DIR
|
||||||
|
|
||||||
- name: Build patch and check for differences
|
- name: Build patch and check for differences
|
||||||
id: diff
|
id: diff
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -7,6 +7,16 @@ on:
|
|||||||
- develop
|
- develop
|
||||||
paths:
|
paths:
|
||||||
- docs/mkdocs/**
|
- docs/mkdocs/**
|
||||||
|
# the site also embeds these files via pymdownx.snippets
|
||||||
|
# (mkdocs.yml sets restrict_base_path: false for this)
|
||||||
|
- .clang-tidy
|
||||||
|
- .github/CODE_OF_CONDUCT.md
|
||||||
|
- .github/CONTRIBUTING.md
|
||||||
|
- .github/SECURITY.md
|
||||||
|
- cmake/clang_flags.cmake
|
||||||
|
- cmake/gcc_flags.cmake
|
||||||
|
- tests/fmt_formatter/project/main.cpp
|
||||||
|
- tools/astyle/.astylerc
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
# we don't want to have concurrent jobs, and we don't want to cancel running jobs to avoid broken publications
|
# we don't want to have concurrent jobs, and we don't want to cancel running jobs to avoid broken publications
|
||||||
@@ -23,7 +33,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
if: github.repository == 'nlohmann/json'
|
if: github.repository == 'nlohmann/json'
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
@@ -31,6 +41,8 @@ jobs:
|
|||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install virtual environment
|
- name: Install virtual environment
|
||||||
run: make install_venv -C docs/mkdocs
|
run: make install_venv -C docs/mkdocs
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ jobs:
|
|||||||
container: ubuntu:focal
|
container: ubuntu:focal
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_no_thread_local]
|
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_disabletuplereferenceconversion, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_no_thread_local]
|
||||||
steps:
|
steps:
|
||||||
- name: Install build-essential
|
- name: Install build-essential
|
||||||
run: apt-get update ; apt-get install -y build-essential unzip wget git libssl-dev
|
run: apt-get update ; apt-get install -y build-essential unzip wget git libssl-dev
|
||||||
@@ -346,6 +346,8 @@ jobs:
|
|||||||
container: intel/oneapi-hpckit:latest
|
container: intel/oneapi-hpckit:latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
@@ -358,6 +360,8 @@ jobs:
|
|||||||
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
|
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
- name: Set extra CXX_FLAGS for latest std_version
|
- name: Set extra CXX_FLAGS for latest std_version
|
||||||
@@ -123,6 +125,8 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
- name: Run CMake (Release)
|
- name: Run CMake (Release)
|
||||||
run: cmake -S . -B build -G "Visual Studio 18 2026" -A ARM64 -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX"
|
run: cmake -S . -B build -G "Visual Studio 18 2026" -A ARM64 -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX"
|
||||||
if: matrix.build_type == 'Release'
|
if: matrix.build_type == 'Release'
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ cc_library(
|
|||||||
hdrs = [
|
hdrs = [
|
||||||
"include/nlohmann/adl_serializer.hpp",
|
"include/nlohmann/adl_serializer.hpp",
|
||||||
"include/nlohmann/byte_container_with_subtype.hpp",
|
"include/nlohmann/byte_container_with_subtype.hpp",
|
||||||
"include/nlohmann/detail/abi_config.hpp",
|
|
||||||
"include/nlohmann/detail/abi_macros.hpp",
|
"include/nlohmann/detail/abi_macros.hpp",
|
||||||
"include/nlohmann/detail/bit_ops.hpp",
|
"include/nlohmann/detail/bit_ops.hpp",
|
||||||
"include/nlohmann/detail/conversions/from_json.hpp",
|
"include/nlohmann/detail/conversions/from_json.hpp",
|
||||||
@@ -66,13 +65,6 @@ cc_library(
|
|||||||
"include/nlohmann/detail/string_escape.hpp",
|
"include/nlohmann/detail/string_escape.hpp",
|
||||||
"include/nlohmann/detail/string_utils.hpp",
|
"include/nlohmann/detail/string_utils.hpp",
|
||||||
"include/nlohmann/detail/value_t.hpp",
|
"include/nlohmann/detail/value_t.hpp",
|
||||||
"include/nlohmann/detail/view/builder.hpp",
|
|
||||||
"include/nlohmann/detail/view/document_data.hpp",
|
|
||||||
"include/nlohmann/detail/view/macro_scope.hpp",
|
|
||||||
"include/nlohmann/detail/view/macro_unscope.hpp",
|
|
||||||
"include/nlohmann/detail/view/node.hpp",
|
|
||||||
"include/nlohmann/detail/view/scan.hpp",
|
|
||||||
"include/nlohmann/detail/view/string_ref.hpp",
|
|
||||||
"include/nlohmann/json.hpp",
|
"include/nlohmann/json.hpp",
|
||||||
"include/nlohmann/json_fwd.hpp",
|
"include/nlohmann/json_fwd.hpp",
|
||||||
"include/nlohmann/json_literals.hpp",
|
"include/nlohmann/json_literals.hpp",
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ option(JSON_Diagnostic_Positions "Enable diagnostic positions." OFF)
|
|||||||
option(JSON_GlobalUDLs "Place user-defined string literals in the global namespace." ON)
|
option(JSON_GlobalUDLs "Place user-defined string literals in the global namespace." ON)
|
||||||
option(JSON_ImplicitConversions "Enable implicit conversions." ON)
|
option(JSON_ImplicitConversions "Enable implicit conversions." ON)
|
||||||
option(JSON_DisableEnumSerialization "Disable default integer enum serialization." OFF)
|
option(JSON_DisableEnumSerialization "Disable default integer enum serialization." OFF)
|
||||||
|
option(JSON_DisableTupleReferenceConversion "Disable conversion from a one-element tuple of a JSON reference." OFF)
|
||||||
option(JSON_LegacyDiscardedValueComparison "Enable legacy discarded value comparison." OFF)
|
option(JSON_LegacyDiscardedValueComparison "Enable legacy discarded value comparison." OFF)
|
||||||
option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT})
|
option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT})
|
||||||
option(JSON_MultipleHeaders "Use non-amalgamated version of the library." ON)
|
option(JSON_MultipleHeaders "Use non-amalgamated version of the library." ON)
|
||||||
@@ -101,6 +102,10 @@ if (JSON_DisableEnumSerialization)
|
|||||||
message(STATUS "Enum integer serialization is disabled (JSON_DISABLE_ENUM_SERIALIZATION=1)")
|
message(STATUS "Enum integer serialization is disabled (JSON_DISABLE_ENUM_SERIALIZATION=1)")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (JSON_DisableTupleReferenceConversion)
|
||||||
|
message(STATUS "Tuple reference conversion is disabled (JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1)")
|
||||||
|
endif()
|
||||||
|
|
||||||
if (JSON_LegacyDiscardedValueComparison)
|
if (JSON_LegacyDiscardedValueComparison)
|
||||||
message(STATUS "Legacy discarded value comparison enabled (JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1)")
|
message(STATUS "Legacy discarded value comparison enabled (JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1)")
|
||||||
endif()
|
endif()
|
||||||
@@ -143,6 +148,7 @@ target_compile_definitions(
|
|||||||
$<$<NOT:$<BOOL:${JSON_GlobalUDLs}>>:JSON_USE_GLOBAL_UDLS=0>
|
$<$<NOT:$<BOOL:${JSON_GlobalUDLs}>>:JSON_USE_GLOBAL_UDLS=0>
|
||||||
$<$<NOT:$<BOOL:${JSON_ImplicitConversions}>>:JSON_USE_IMPLICIT_CONVERSIONS=0>
|
$<$<NOT:$<BOOL:${JSON_ImplicitConversions}>>:JSON_USE_IMPLICIT_CONVERSIONS=0>
|
||||||
$<$<BOOL:${JSON_DisableEnumSerialization}>:JSON_DISABLE_ENUM_SERIALIZATION=1>
|
$<$<BOOL:${JSON_DisableEnumSerialization}>:JSON_DISABLE_ENUM_SERIALIZATION=1>
|
||||||
|
$<$<BOOL:${JSON_DisableTupleReferenceConversion}>:JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1>
|
||||||
$<$<BOOL:${JSON_Diagnostics}>:JSON_DIAGNOSTICS=1>
|
$<$<BOOL:${JSON_Diagnostics}>:JSON_DIAGNOSTICS=1>
|
||||||
$<$<BOOL:${JSON_Diagnostic_Positions}>:JSON_DIAGNOSTIC_POSITIONS=1>
|
$<$<BOOL:${JSON_Diagnostic_Positions}>:JSON_DIAGNOSTIC_POSITIONS=1>
|
||||||
$<$<BOOL:${JSON_LegacyDiscardedValueComparison}>:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1>
|
$<$<BOOL:${JSON_LegacyDiscardedValueComparison}>:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel
|
.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel natvis macro_builder_check
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# configuration
|
# configuration
|
||||||
@@ -24,6 +24,9 @@ AMALGAMATED_FWD_FILE=single_include/nlohmann/json_fwd.hpp
|
|||||||
# json_literals.hpp only includes <nlohmann/json.hpp>, so it is copied verbatim
|
# json_literals.hpp only includes <nlohmann/json.hpp>, so it is copied verbatim
|
||||||
AMALGAMATED_LITERALS_FILE=single_include/nlohmann/json_literals.hpp
|
AMALGAMATED_LITERALS_FILE=single_include/nlohmann/json_literals.hpp
|
||||||
|
|
||||||
|
# the header with the argument-counting macros generated by tools/macro_builder
|
||||||
|
MACRO_SCOPE_HPP=include/nlohmann/detail/macro_scope.hpp
|
||||||
|
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# documentation of the Makefile's targets
|
# documentation of the Makefile's targets
|
||||||
@@ -36,13 +39,9 @@ all:
|
|||||||
@echo "ChangeLog.md - generate ChangeLog file"
|
@echo "ChangeLog.md - generate ChangeLog file"
|
||||||
@echo "check-amalgamation - check whether sources have been amalgamated and BUILD.bazel is up to date"
|
@echo "check-amalgamation - check whether sources have been amalgamated and BUILD.bazel is up to date"
|
||||||
@echo "clean - remove built files"
|
@echo "clean - remove built files"
|
||||||
@echo "doctest - compile example files and check their output"
|
@echo "fuzzing - see tests/fuzzing.md for how to build and run the fuzzers"
|
||||||
@echo "fuzz_testing - prepare fuzz testing of the JSON parser"
|
@echo "macro_builder_check - check that macro_scope.hpp matches tools/macro_builder's output"
|
||||||
@echo "fuzz_testing_bon8 - prepare fuzz testing of the BON8 parser"
|
@echo "natvis - regenerate nlohmann_json.natvis from the current ABI tags and version"
|
||||||
@echo "fuzz_testing_bson - prepare fuzz testing of the BSON parser"
|
|
||||||
@echo "fuzz_testing_cbor - prepare fuzz testing of the CBOR parser"
|
|
||||||
@echo "fuzz_testing_msgpack - prepare fuzz testing of the MessagePack parser"
|
|
||||||
@echo "fuzz_testing_ubjson - prepare fuzz testing of the UBJSON parser"
|
|
||||||
@echo "pretty - beautify code with Artistic Style"
|
@echo "pretty - beautify code with Artistic Style"
|
||||||
@echo "run_benchmarks - build and run benchmarks"
|
@echo "run_benchmarks - build and run benchmarks"
|
||||||
@echo "update_hedley - download Hedley and regenerate hedley.hpp / hedley_undef.hpp"
|
@echo "update_hedley - download Hedley and regenerate hedley.hpp / hedley_undef.hpp"
|
||||||
@@ -61,74 +60,6 @@ run_benchmarks:
|
|||||||
cd cmake-build-benchmarks ; ./json_benchmarks
|
cd cmake-build-benchmarks ; ./json_benchmarks
|
||||||
|
|
||||||
|
|
||||||
##########################################################################
|
|
||||||
# fuzzing
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
# the overall fuzz testing target
|
|
||||||
fuzz_testing:
|
|
||||||
rm -fr fuzz-testing
|
|
||||||
mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out
|
|
||||||
$(MAKE) parse_afl_fuzzer -C tests CXX=afl-clang++
|
|
||||||
mv tests/parse_afl_fuzzer fuzz-testing/fuzzer
|
|
||||||
find tests/data/json_tests -size -5k -name *json | xargs -I{} cp "{}" fuzz-testing/testcases
|
|
||||||
@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer"
|
|
||||||
|
|
||||||
fuzz_testing_bon8:
|
|
||||||
rm -fr fuzz-testing
|
|
||||||
mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out
|
|
||||||
$(MAKE) parse_bon8_fuzzer -C tests CXX=afl-clang++
|
|
||||||
mv tests/parse_bon8_fuzzer fuzz-testing/fuzzer
|
|
||||||
find tests/data -size -5k -name *.bon8 | xargs -I{} cp "{}" fuzz-testing/testcases
|
|
||||||
@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer"
|
|
||||||
|
|
||||||
fuzz_testing_bson:
|
|
||||||
rm -fr fuzz-testing
|
|
||||||
mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out
|
|
||||||
$(MAKE) parse_bson_fuzzer -C tests CXX=afl-clang++
|
|
||||||
mv tests/parse_bson_fuzzer fuzz-testing/fuzzer
|
|
||||||
find tests/data -size -5k -name *.bson | xargs -I{} cp "{}" fuzz-testing/testcases
|
|
||||||
@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer"
|
|
||||||
|
|
||||||
fuzz_testing_cbor:
|
|
||||||
rm -fr fuzz-testing
|
|
||||||
mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out
|
|
||||||
$(MAKE) parse_cbor_fuzzer -C tests CXX=afl-clang++
|
|
||||||
mv tests/parse_cbor_fuzzer fuzz-testing/fuzzer
|
|
||||||
find tests/data -size -5k -name *.cbor | xargs -I{} cp "{}" fuzz-testing/testcases
|
|
||||||
@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer"
|
|
||||||
|
|
||||||
fuzz_testing_msgpack:
|
|
||||||
rm -fr fuzz-testing
|
|
||||||
mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out
|
|
||||||
$(MAKE) parse_msgpack_fuzzer -C tests CXX=afl-clang++
|
|
||||||
mv tests/parse_msgpack_fuzzer fuzz-testing/fuzzer
|
|
||||||
find tests/data -size -5k -name *.msgpack | xargs -I{} cp "{}" fuzz-testing/testcases
|
|
||||||
@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer"
|
|
||||||
|
|
||||||
fuzz_testing_ubjson:
|
|
||||||
rm -fr fuzz-testing
|
|
||||||
mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out
|
|
||||||
$(MAKE) parse_ubjson_fuzzer -C tests CXX=afl-clang++
|
|
||||||
mv tests/parse_ubjson_fuzzer fuzz-testing/fuzzer
|
|
||||||
find tests/data -size -5k -name *.ubjson | xargs -I{} cp "{}" fuzz-testing/testcases
|
|
||||||
@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer"
|
|
||||||
|
|
||||||
fuzzing-start:
|
|
||||||
afl-fuzz -S fuzzer1 -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer > /dev/null &
|
|
||||||
afl-fuzz -S fuzzer2 -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer > /dev/null &
|
|
||||||
afl-fuzz -S fuzzer3 -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer > /dev/null &
|
|
||||||
afl-fuzz -S fuzzer4 -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer > /dev/null &
|
|
||||||
afl-fuzz -S fuzzer5 -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer > /dev/null &
|
|
||||||
afl-fuzz -S fuzzer6 -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer > /dev/null &
|
|
||||||
afl-fuzz -S fuzzer7 -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer > /dev/null &
|
|
||||||
afl-fuzz -M fuzzer0 -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer
|
|
||||||
|
|
||||||
fuzzing-stop:
|
|
||||||
-killall fuzzer
|
|
||||||
-killall afl-fuzz
|
|
||||||
|
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# Static analysis
|
# Static analysis
|
||||||
##########################################################################
|
##########################################################################
|
||||||
@@ -158,10 +89,6 @@ install_astyle:
|
|||||||
pretty: install_astyle
|
pretty: install_astyle
|
||||||
$(ASTYLE) --project=tools/astyle/.astylerc $(SRCS) $(TESTS_SRCS) $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE) docs/mkdocs/docs/examples/*.cpp
|
$(ASTYLE) --project=tools/astyle/.astylerc $(SRCS) $(TESTS_SRCS) $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE) docs/mkdocs/docs/examples/*.cpp
|
||||||
|
|
||||||
# call the Clang-Format on all source files
|
|
||||||
pretty_format:
|
|
||||||
for FILE in $(SRCS) $(TESTS_SRCS) $(AMALGAMATED_FILE) docs/mkdocs/docs/examples/*.cpp; do echo $$FILE; clang-format -i $$FILE; done
|
|
||||||
|
|
||||||
# create single header files and pretty print
|
# create single header files and pretty print
|
||||||
amalgamate: $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE)
|
amalgamate: $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE)
|
||||||
$(MAKE) pretty
|
$(MAKE) pretty
|
||||||
@@ -178,8 +105,26 @@ $(AMALGAMATED_FWD_FILE): $(SRCS)
|
|||||||
$(AMALGAMATED_LITERALS_FILE): include/nlohmann/json_literals.hpp
|
$(AMALGAMATED_LITERALS_FILE): include/nlohmann/json_literals.hpp
|
||||||
cp include/nlohmann/json_literals.hpp $(AMALGAMATED_LITERALS_FILE)
|
cp include/nlohmann/json_literals.hpp $(AMALGAMATED_LITERALS_FILE)
|
||||||
|
|
||||||
|
# regenerate nlohmann_json.natvis from the ABI tags and version in include/nlohmann/detail/abi_macros.hpp
|
||||||
|
natvis:
|
||||||
|
python3 tools/generate_natvis/generate_natvis.py .
|
||||||
|
|
||||||
|
# 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
|
||||||
|
macro_builder_check:
|
||||||
|
@set -e; \
|
||||||
|
TMPDIR=$$(mktemp -d ./macro_builder_check.XXXXXX); \
|
||||||
|
trap 'rm -rf "$$TMPDIR"' EXIT; \
|
||||||
|
$(CXX) -std=c++11 tools/macro_builder/main.cpp -o "$$TMPDIR/macro_builder"; \
|
||||||
|
"$$TMPDIR/macro_builder" > "$$TMPDIR/paste.hpp"; \
|
||||||
|
"$$TMPDIR/macro_builder" type_body > "$$TMPDIR/type_body.hpp"; \
|
||||||
|
$(ASTYLE) --project=tools/astyle/.astylerc --suffix=none --quiet "$$TMPDIR/paste.hpp" "$$TMPDIR/type_body.hpp"; \
|
||||||
|
sed -n '/^#define NLOHMANN_JSON_EXPAND( x ) x$$/,/^#define NLOHMANN_JSON_DOUBLE_PASTE63(/p' $(MACRO_SCOPE_HPP) > "$$TMPDIR/paste_actual.hpp"; \
|
||||||
|
sed -n '/^#define NLOHMANN_JSON_TYPE_BODY(Prefix, \.\.\.)/,/^ NLOHMANN_JSON_TYPE_BODY_SENTINEL))$$/p' $(MACRO_SCOPE_HPP) > "$$TMPDIR/type_body_actual.hpp"; \
|
||||||
|
diff "$$TMPDIR/paste.hpp" "$$TMPDIR/paste_actual.hpp" || (echo "===================================================================\n $(MACRO_SCOPE_HPP) (NLOHMANN_JSON_EXPAND..NLOHMANN_JSON_DOUBLE_PASTE63) is out of date!\n Regenerate it, see tools/macro_builder/README.md.\n===================================================================" ; exit 1); \
|
||||||
|
diff "$$TMPDIR/type_body.hpp" "$$TMPDIR/type_body_actual.hpp" || (echo "===================================================================\n $(MACRO_SCOPE_HPP) (NLOHMANN_JSON_TYPE_BODY) is out of date!\n Regenerate it, see tools/macro_builder/README.md.\n===================================================================" ; exit 1)
|
||||||
|
|
||||||
# check if file single_include/nlohmann/json.hpp has been amalgamated from the nlohmann sources
|
# check if file single_include/nlohmann/json.hpp has been amalgamated from the nlohmann sources
|
||||||
# Note: this target is called by Travis
|
|
||||||
check-amalgamation:
|
check-amalgamation:
|
||||||
@mv $(AMALGAMATED_FILE) $(AMALGAMATED_FILE)~
|
@mv $(AMALGAMATED_FILE) $(AMALGAMATED_FILE)~
|
||||||
@mv $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_FWD_FILE)~
|
@mv $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_FWD_FILE)~
|
||||||
@@ -195,6 +140,11 @@ check-amalgamation:
|
|||||||
@$(MAKE) BUILD.bazel
|
@$(MAKE) BUILD.bazel
|
||||||
@diff BUILD.bazel BUILD.bazel~ || (echo "===================================================================\n BUILD.bazel is out of date! Please run 'make BUILD.bazel'.\n===================================================================" ; mv BUILD.bazel~ BUILD.bazel ; false)
|
@diff BUILD.bazel BUILD.bazel~ || (echo "===================================================================\n BUILD.bazel is out of date! Please run 'make BUILD.bazel'.\n===================================================================" ; mv BUILD.bazel~ BUILD.bazel ; false)
|
||||||
@mv BUILD.bazel~ BUILD.bazel
|
@mv BUILD.bazel~ BUILD.bazel
|
||||||
|
@mv nlohmann_json.natvis nlohmann_json.natvis~
|
||||||
|
@$(MAKE) natvis
|
||||||
|
@diff nlohmann_json.natvis nlohmann_json.natvis~ || (echo "===================================================================\n nlohmann_json.natvis is out of date! Please run 'make natvis'.\n===================================================================" ; mv nlohmann_json.natvis~ nlohmann_json.natvis ; false)
|
||||||
|
@mv nlohmann_json.natvis~ nlohmann_json.natvis
|
||||||
|
@$(MAKE) macro_builder_check
|
||||||
|
|
||||||
# generate the Bazel BUILD file; phony, because a removed header would not trigger a rebuild
|
# generate the Bazel BUILD file; phony, because a removed header would not trigger a rebuild
|
||||||
BUILD.bazel:
|
BUILD.bazel:
|
||||||
@@ -246,7 +196,7 @@ release: include.zip json.tar.xz
|
|||||||
cp $(AMALGAMATED_FWD_FILE) release_files
|
cp $(AMALGAMATED_FWD_FILE) release_files
|
||||||
cp $(AMALGAMATED_LITERALS_FILE) release_files
|
cp $(AMALGAMATED_LITERALS_FILE) release_files
|
||||||
mv $(AMALGAMATED_FILE).asc $(AMALGAMATED_FWD_FILE).asc $(AMALGAMATED_LITERALS_FILE).asc json.tar.xz json.tar.xz.asc include.zip include.zip.asc release_files
|
mv $(AMALGAMATED_FILE).asc $(AMALGAMATED_FWD_FILE).asc $(AMALGAMATED_LITERALS_FILE).asc json.tar.xz json.tar.xz.asc include.zip include.zip.asc release_files
|
||||||
cd release_files ; shasum -a 256 json.hpp include.zip json.tar.xz > hashes.txt
|
cd release_files ; shasum -a 256 $$(find . -type f -not -name '*.asc' | sed 's|^\./||' | sort) > hashes.txt
|
||||||
|
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
@@ -256,7 +206,6 @@ release: include.zip json.tar.xz
|
|||||||
# clean up
|
# clean up
|
||||||
clean:
|
clean:
|
||||||
rm -fr fuzz fuzz-testing *.dSYM tests/*.dSYM
|
rm -fr fuzz fuzz-testing *.dSYM tests/*.dSYM
|
||||||
rm -fr benchmarks/files/numbers/*.json
|
|
||||||
rm -fr cmake-build-benchmarks fuzz-testing cmake-build-pvs-studio release_files
|
rm -fr cmake-build-benchmarks fuzz-testing cmake-build-pvs-studio release_files
|
||||||
$(MAKE) clean -Cdocs
|
$(MAKE) clean -Cdocs
|
||||||
|
|
||||||
|
|||||||
@@ -496,7 +496,7 @@ bool key(string_t& val);
|
|||||||
bool parse_error(std::size_t position, const std::string& last_token, const detail::exception& ex);
|
bool parse_error(std::size_t position, const std::string& last_token, const detail::exception& ex);
|
||||||
```
|
```
|
||||||
|
|
||||||
The return value of each function determines whether parsing should proceed.
|
The return value of each function determines whether parsing should proceed. For `parse_error`, returning `true` [recovers from the error](https://json.nlohmann.me/features/parsing/error_recovery/): the parser repairs the input and continues.
|
||||||
|
|
||||||
To implement your own SAX handler, proceed as follows:
|
To implement your own SAX handler, proceed as follows:
|
||||||
|
|
||||||
@@ -504,7 +504,7 @@ To implement your own SAX handler, proceed as follows:
|
|||||||
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
||||||
3. Call `bool json::sax_parse(input, &my_sax)`; where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
|
3. Call `bool json::sax_parse(input, &my_sax)`; where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
|
||||||
|
|
||||||
Note the `sax_parse` function only returns a `bool` indicating the result of the last executed SAX event. It does not return a `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error -- it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file [`json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp).
|
Note the `sax_parse` function only returns a `bool` indicating whether the input was parsed without errors and no SAX event returned `false`. It does not return a `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error -- it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file [`json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp).
|
||||||
|
|
||||||
### STL-like access
|
### STL-like access
|
||||||
|
|
||||||
@@ -1395,7 +1395,7 @@ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR I
|
|||||||
- The class contains a slightly modified version of the Grisu2 algorithm from Florian Loitsch which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright © 2009 [Florian Loitsch](https://florian.loitsch.com/)
|
- The class contains a slightly modified version of the Grisu2 algorithm from Florian Loitsch which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright © 2009 [Florian Loitsch](https://florian.loitsch.com/)
|
||||||
- The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/).
|
- The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/).
|
||||||
- The class contains parts of [Google Abseil](https://github.com/abseil/abseil-cpp) which is licensed under the [Apache 2.0 License](https://opensource.org/licenses/Apache-2.0).
|
- The class contains parts of [Google Abseil](https://github.com/abseil/abseil-cpp) which is licensed under the [Apache 2.0 License](https://opensource.org/licenses/Apache-2.0).
|
||||||
- The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, and its digit comparison for long numbers from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
- The class contains an adapted version of the Eisel-Lemire algorithm and its table of powers of five from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
||||||
|
|
||||||
<img align="right" src="https://git.fsfe.org/reuse/reuse-ci/raw/branch/master/reuse-horizontal.png" alt="REUSE Software">
|
<img align="right" src="https://git.fsfe.org/reuse/reuse-ci/raw/branch/master/reuse-horizontal.png" alt="REUSE Software">
|
||||||
|
|
||||||
|
|||||||
@@ -276,6 +276,20 @@ add_custom_target(ci_test_disableenumserialization
|
|||||||
COMMENT "Compile and test with enum serialization disabled"
|
COMMENT "Compile and test with enum serialization disabled"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Disable conversion from a one-element tuple of a JSON reference.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
add_custom_target(ci_test_disabletuplereferenceconversion
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
||||||
|
-DJSON_BuildTests=ON -DJSON_FastTests=ON -DJSON_DisableTupleReferenceConversion=ON
|
||||||
|
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_disabletuplereferenceconversion
|
||||||
|
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_disabletuplereferenceconversion
|
||||||
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_disabletuplereferenceconversion && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
||||||
|
COMMENT "Compile and test with tuple reference conversion disabled"
|
||||||
|
)
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Skip the multiple-inclusion library version check.
|
# Skip the multiple-inclusion library version check.
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|||||||
+16
-5
@@ -11,20 +11,31 @@ EXAMPLES = $(wildcard mkdocs/docs/examples/*.cpp)
|
|||||||
|
|
||||||
cxx_standard = $(lastword c++11 $(filter c++%, $(subst ., ,$1)))
|
cxx_standard = $(lastword c++11 $(filter c++%, $(subst ., ,$1)))
|
||||||
|
|
||||||
|
# common compile flags for the stand-alone example files
|
||||||
|
EXAMPLE_CPPFLAGS = -I $(SRCDIR) -DJSON_USE_GLOBAL_UDLS=0
|
||||||
|
EXAMPLE_WARNFLAGS = -Werror=deprecated-declarations
|
||||||
|
|
||||||
|
# examples that document deprecated API and are allowed to use it
|
||||||
|
DEPRECATED_EXAMPLES = $(addprefix mkdocs/docs/examples/, \
|
||||||
|
json_pointer__operator__equal_stringtype \
|
||||||
|
json_pointer__operator__notequal_stringtype \
|
||||||
|
json_pointer__operator_string_t)
|
||||||
|
$(DEPRECATED_EXAMPLES:=.output) $(DEPRECATED_EXAMPLES:=.test): EXAMPLE_WARNFLAGS = -Wno-deprecated-declarations
|
||||||
|
|
||||||
# create output from a stand-alone example file
|
# create output from a stand-alone example file
|
||||||
%.output: %.cpp
|
%.output: %.cpp
|
||||||
@echo "standard $(call cxx_standard $(<:.cpp=))"
|
@echo "standard $(call cxx_standard,$(<:.cpp=))"
|
||||||
$(MAKE) $(<:.cpp=) \
|
$(MAKE) $(<:.cpp=) \
|
||||||
CPPFLAGS="-I $(SRCDIR) -DJSON_USE_GLOBAL_UDLS=0" \
|
CPPFLAGS="$(EXAMPLE_CPPFLAGS)" \
|
||||||
CXXFLAGS="-std=$(call cxx_standard,$(<:.cpp=)) -Wno-deprecated-declarations"
|
CXXFLAGS="-std=$(call cxx_standard,$(<:.cpp=)) $(EXAMPLE_WARNFLAGS)"
|
||||||
./$(<:.cpp=) > $@
|
./$(<:.cpp=) > $@
|
||||||
rm $(<:.cpp=)
|
rm $(<:.cpp=)
|
||||||
|
|
||||||
# compare created output with current output of the example files
|
# compare created output with current output of the example files
|
||||||
%.test: %.cpp
|
%.test: %.cpp
|
||||||
$(MAKE) $(<:.cpp=) \
|
$(MAKE) $(<:.cpp=) \
|
||||||
CPPFLAGS="-I $(SRCDIR) -DJSON_USE_GLOBAL_UDLS=0" \
|
CPPFLAGS="$(EXAMPLE_CPPFLAGS)" \
|
||||||
CXXFLAGS="-std=$(call cxx_standard,$(<:.cpp=)) -Wno-deprecated-declarations"
|
CXXFLAGS="-std=$(call cxx_standard,$(<:.cpp=)) $(EXAMPLE_WARNFLAGS)"
|
||||||
./$(<:.cpp=) > $@
|
./$(<:.cpp=) > $@
|
||||||
diff $@ $(<:.cpp=.output)
|
diff $@ $(<:.cpp=.output)
|
||||||
rm $(<:.cpp=) $@
|
rm $(<:.cpp=) $@
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<key>dashIndexFilePath</key>
|
<key>dashIndexFilePath</key>
|
||||||
<string>index.html</string>
|
<string>index.html</string>
|
||||||
<key>DashDocSetFallbackURL</key>
|
<key>DashDocSetFallbackURL</key>
|
||||||
<string>https://nlohmann.github.io/json/</string>
|
<string>https://json.nlohmann.me/</string>
|
||||||
<key>isJavaScriptEnabled</key>
|
<key>isJavaScriptEnabled</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
+12
-22
@@ -52,35 +52,25 @@ install_docset_zeal: JSON_for_Modern_C++.docset
|
|||||||
mkdir -p $$docset_root; \
|
mkdir -p $$docset_root; \
|
||||||
cp -r JSON_for_Modern_C++.docset $$docset_root/
|
cp -r JSON_for_Modern_C++.docset $$docset_root/
|
||||||
|
|
||||||
|
# both targets below compare the docset search index with the mkdocs page
|
||||||
|
# set. They share the same normalization (docs/foo/index.md and
|
||||||
|
# docs/foo.md both become foo/index.html, the URL mkdocs itself would
|
||||||
|
# give the page; the top-level index.md is excluded, as it is not part
|
||||||
|
# of the hand-curated docSet.sql) and use comm(1) on two sorted lists
|
||||||
|
# instead of running a sqlite3 query, or an O(n*m) nested shell loop,
|
||||||
|
# once per page.
|
||||||
|
DOCSET_INDEX_PATHS=$(shell sqlite3 docSet.dsidx "SELECT DISTINCT path FROM searchIndex" | sort)
|
||||||
|
DOCSET_PAGE_PATHS=$(shell echo '$(MKDOCS_PAGES)' | tr ' ' '\n' | grep -v '^index\.md$$' | $(SED) -E 's@/index\.md$$@/index.html@; s@\.md$$@/index.html@' | sort)
|
||||||
|
|
||||||
# list mkdocs pages missing from the docset index
|
# list mkdocs pages missing from the docset index
|
||||||
.PHONY: list_missing_pages
|
.PHONY: list_missing_pages
|
||||||
list_missing_pages: docSet.dsidx
|
list_missing_pages: docSet.dsidx
|
||||||
@for page in $(MKDOCS_PAGES); do \
|
@comm -23 <(echo '$(DOCSET_PAGE_PATHS)' | tr ' ' '\n') <(echo '$(DOCSET_INDEX_PATHS)' | tr ' ' '\n')
|
||||||
case "$$page" in \
|
|
||||||
*/index.md) path=$${page/\/index.md/} ;; \
|
|
||||||
*) path=$${page/.md/} ;; \
|
|
||||||
esac; \
|
|
||||||
if [ "x$$page" != "xindex.md" -a "x$$(sqlite3 docSet.dsidx "SELECT COUNT(*) FROM searchIndex WHERE path='$$path/index.html'")" = "x0" ]; then \
|
|
||||||
echo $$page; \
|
|
||||||
fi \
|
|
||||||
done
|
|
||||||
|
|
||||||
# list paths in the docset index without a corresponding mkdocs page
|
# list paths in the docset index without a corresponding mkdocs page
|
||||||
.PHONY: list_removed_paths
|
.PHONY: list_removed_paths
|
||||||
list_removed_paths: docSet.dsidx
|
list_removed_paths: docSet.dsidx
|
||||||
@for path in $$(sqlite3 docSet.dsidx "SELECT path FROM searchIndex"); do \
|
@comm -13 <(echo '$(DOCSET_PAGE_PATHS)' | tr ' ' '\n') <(echo '$(DOCSET_INDEX_PATHS)' | tr ' ' '\n')
|
||||||
page=$${path/\/index.html/.md}; \
|
|
||||||
page_index=$${path/index.html/index.md}; \
|
|
||||||
page_found=0; \
|
|
||||||
for p in $(MKDOCS_PAGES); do \
|
|
||||||
if [ "x$$p" = "x$$page" -o "x$$p" = "x$$page_index" ]; then \
|
|
||||||
page_found=1; \
|
|
||||||
fi \
|
|
||||||
done; \
|
|
||||||
if [ "x$$page_found" = "x0" ]; then \
|
|
||||||
echo $$path; \
|
|
||||||
fi \
|
|
||||||
done
|
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ documentation browsers like [Dash](https://kapeli.com/dash), [Velocity](https://
|
|||||||
The docset can be created with
|
The docset can be created with
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make nlohmann_json.docset
|
make JSON_for_Modern_C++.docset
|
||||||
```
|
```
|
||||||
|
|
||||||
The generated folder `nlohmann_json.docset` can then be opened in the documentation browser.
|
The generated folder `JSON_for_Modern_C++.docset` can then be opened in the documentation browser. `make all` builds a
|
||||||
|
`JSON_for_Modern_C++.tgz` archive instead, and `make install_docset_zeal` installs the docset for Zeal directly.
|
||||||
|
|
||||||
A recent version is also part of the [Dash user contributions](https://github.com/Kapeli/Dash-User-Contributions/tree/master/docsets/JSON_for_Modern_C%2B%2B).
|
A recent version is also part of the [Dash user contributions](https://github.com/Kapeli/Dash-User-Contributions/tree/master/docsets/JSON_for_Modern_C%2B%2B).
|
||||||
|
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('JSON_CATCH_USER', 'Macro', 'a
|
|||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTICS', 'Macro', 'api/macros/json_diagnostics/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTICS', 'Macro', 'api/macros/json_diagnostics/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTIC_POSITIONS', 'Macro', 'api/macros/json_diagnostic_positions/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTIC_POSITIONS', 'Macro', 'api/macros/json_diagnostic_positions/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_ENUM_SERIALIZATION', 'Macro', 'api/macros/json_disable_enum_serialization/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_ENUM_SERIALIZATION', 'Macro', 'api/macros/json_disable_enum_serialization/index.html');
|
||||||
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_TUPLE_REFERENCE_CONVERSION', 'Macro', 'api/macros/json_disable_tuple_reference_conversion/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_11', 'Macro', 'api/macros/json_has_cpp_11/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_11', 'Macro', 'api/macros/json_has_cpp_11/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_14', 'Macro', 'api/macros/json_has_cpp_11/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_14', 'Macro', 'api/macros/json_has_cpp_11/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_17', 'Macro', 'api/macros/json_has_cpp_11/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_17', 'Macro', 'api/macros/json_has_cpp_11/index.html');
|
||||||
|
|||||||
@@ -159,6 +159,8 @@ basic_json(basic_json&& other) noexcept;
|
|||||||
- `CompatibleType` is not `basic_json` (to avoid hijacking copy/move constructors),
|
- `CompatibleType` is not `basic_json` (to avoid hijacking copy/move constructors),
|
||||||
- `CompatibleType` is not a different `basic_json` type (i.e. with different template arguments)
|
- `CompatibleType` is not a different `basic_json` type (i.e. with different template arguments)
|
||||||
- `CompatibleType` is not a `basic_json` nested type (e.g., `json_pointer`, `iterator`, etc.)
|
- `CompatibleType` is not a `basic_json` nested type (e.g., `json_pointer`, `iterator`, etc.)
|
||||||
|
- if [`JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../macros/json_disable_tuple_reference_conversion.md) is defined
|
||||||
|
to `1`: `CompatibleType` is not a one-element `std::tuple` holding a reference to `basic_json`
|
||||||
- `json_serializer<U>` (with `U = uncvref_t<CompatibleType>`) has a `to_json(basic_json_t&, CompatibleType&&)`
|
- `json_serializer<U>` (with `U = uncvref_t<CompatibleType>`) has a `to_json(basic_json_t&, CompatibleType&&)`
|
||||||
method
|
method
|
||||||
|
|
||||||
|
|||||||
@@ -23,10 +23,9 @@ type to use.
|
|||||||
## Template parameters
|
## Template parameters
|
||||||
|
|
||||||
`NumberFloatType`
|
`NumberFloatType`
|
||||||
: the type to store floating-point numbers. The parser converts `#!cpp float`, `#!cpp double`, and a
|
: the type to store floating-point numbers. Parsing and serialization are implemented in terms of
|
||||||
`#!cpp long double` that is IEEE 754 binary64 itself and other `#!cpp long double` formats with
|
`#!cpp std::strtof`/`#!cpp std::strtod`/`#!cpp std::strtold` and `#!cpp std::snprintf`, so the type must be
|
||||||
`#!cpp std::from_chars` or `#!cpp std::strtold`, and serialization falls back to `#!cpp std::snprintf`, so the
|
`#!cpp float`, `#!cpp double`, or `#!cpp long double`. The
|
||||||
type must be `#!cpp float`, `#!cpp double`, or `#!cpp long double`. The
|
|
||||||
[binary formats](../../features/binary_formats/index.md) additionally require `#!cpp float` or `#!cpp double`,
|
[binary formats](../../features/binary_formats/index.md) additionally require `#!cpp float` or `#!cpp double`,
|
||||||
because they have no encoding for `#!cpp long double`. See
|
because they have no encoding for `#!cpp long double`. See
|
||||||
[Template Parameter Requirements](../../features/types/template_parameters.md#numberfloattype).
|
[Template Parameter Requirements](../../features/types/template_parameters.md#numberfloattype).
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ range will yield over/underflow when used in a constructor. During deserializati
|
|||||||
will automatically be stored as [`number_unsigned_t`](number_unsigned_t.md) or [`number_float_t`](number_float_t.md).
|
will automatically be stored as [`number_unsigned_t`](number_unsigned_t.md) or [`number_float_t`](number_float_t.md).
|
||||||
|
|
||||||
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
|
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
|
||||||
> Note that when such software is used, numbers that are integers and are in the range $[-2^{53}+1, 2^{53}-1]$ are
|
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are
|
||||||
> interoperable in the sense that implementations will agree exactly on their numeric values.
|
> interoperable in the sense that implementations will agree exactly on their numeric values.
|
||||||
|
|
||||||
As this range is a subrange of the exactly supported range [INT64_MIN, INT64_MAX], this class's integer type is
|
As this range is a subrange of the exactly supported range [INT64_MIN, INT64_MAX], this class's integer type is
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ when used in a constructor. During deserialization, too large or small integer n
|
|||||||
as [`number_integer_t`](number_integer_t.md) or [`number_float_t`](number_float_t.md).
|
as [`number_integer_t`](number_integer_t.md) or [`number_float_t`](number_float_t.md).
|
||||||
|
|
||||||
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
|
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
|
||||||
> Note that when such software is used, numbers that are integers and are in the range $[-2^{53}+1, 2^{53}-1]$ are
|
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are
|
||||||
> interoperable in the sense that implementations will agree exactly on their numeric values.
|
> interoperable in the sense that implementations will agree exactly on their numeric values.
|
||||||
|
|
||||||
As this range is a subrange (when considered in conjunction with the `number_integer_t` type) of the exactly supported
|
As this range is a subrange (when considered in conjunction with the `number_integer_t` type) of the exactly supported
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ classDiagram
|
|||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
For an input with $n$ bytes, 1 is the index of the first character and $n+1$ is the index of the terminating null byte
|
For an input with <i>n</i> bytes, 1 is the index of the first character and <i>n</i>+1 is the index of the terminating null byte
|
||||||
or the end of file. This also holds true when reading a byte vector for binary formats.
|
or the end of file. This also holds true when reading a byte vector for binary formats.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|||||||
@@ -90,7 +90,9 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
|||||||
|
|
||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
return value of the last processed SAX event
|
`#!cpp true` if the input was parsed without errors and no SAX event returned `#!cpp false`; `#!cpp false` otherwise.
|
||||||
|
In particular, the result is `#!cpp false` for input with errors, even if the SAX parser recovered from all of them
|
||||||
|
(see [error recovery](../../features/parsing/error_recovery.md)).
|
||||||
|
|
||||||
## Exception safety
|
## Exception safety
|
||||||
|
|
||||||
@@ -138,6 +140,7 @@ A UTF-8 byte order mark is silently ignored.
|
|||||||
- Ignoring comments via `ignore_comments` added in version 3.9.0.
|
- Ignoring comments via `ignore_comments` added in version 3.9.0.
|
||||||
- Added `ignore_trailing_commas` in version 3.13.0.
|
- Added `ignore_trailing_commas` in version 3.13.0.
|
||||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||||
|
- Recovering from parse errors (see [`parse_error`](../json_sax/parse_error.md)) added in version 3.13.0.
|
||||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||||
- `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave a `#!cpp std::istream` positioned right
|
- `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave a `#!cpp std::istream` positioned right
|
||||||
after the parsed value when `strict` is `#!cpp false`.
|
after the parsed value when `strict` is `#!cpp false`.
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ struct json_sax;
|
|||||||
|
|
||||||
This class describes the SAX interface used by [sax_parse](../basic_json/sax_parse.md). Each function is called in
|
This class describes the SAX interface used by [sax_parse](../basic_json/sax_parse.md). Each function is called in
|
||||||
different situations while the input is parsed. The boolean return value informs the parser whether to continue
|
different situations while the input is parsed. The boolean return value informs the parser whether to continue
|
||||||
processing the input.
|
processing the input; for [`parse_error`](parse_error.md), it decides whether to
|
||||||
|
[recover from the error](../../features/parsing/error_recovery.md).
|
||||||
|
|
||||||
## Template parameters
|
## Template parameters
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,14 @@ A parse error occurred.
|
|||||||
|
|
||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
Whether parsing should proceed (**must return `#!cpp false`**).
|
Whether to recover from the error:
|
||||||
|
|
||||||
|
- `#!cpp false` stops parsing.
|
||||||
|
- `#!cpp true` recovers from the error: the error is repaired and parsing continues. If that is not possible, which
|
||||||
|
happens in the binary formats when the end of the item with the error is unknown, the value read so far is completed
|
||||||
|
and parsing stops. See [error recovery](../../features/parsing/error_recovery.md) for how errors are repaired.
|
||||||
|
|
||||||
|
Either way, [`sax_parse`](../basic_json/sax_parse.md) returns `#!cpp false`.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -39,6 +46,22 @@ Whether parsing should proceed (**must return `#!cpp false`**).
|
|||||||
--8<-- "examples/sax_parse.output"
|
--8<-- "examples/sax_parse.output"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
??? example
|
||||||
|
|
||||||
|
The example below shows how a SAX parser recovers from errors.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
--8<-- "examples/sax_parse__error_recovery.cpp"
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
--8<-- "examples/sax_parse__error_recovery.output"
|
||||||
|
```
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
- Added in version 3.2.0.
|
- Added in version 3.2.0.
|
||||||
|
- Returning `#!cpp true` recovers from the error since version 3.13.0; before, parsing stopped, but the result of
|
||||||
|
[`sax_parse`](../basic_json/sax_parse.md) could be wrong.
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ header. See also the [macro overview page](../../features/macros.md).
|
|||||||
|
|
||||||
- [**JSON_BRACE_INIT_COPY_SEMANTICS**](json_brace_init_copy_semantics.md) - opt in to copy/move semantics for single-element brace initialization
|
- [**JSON_BRACE_INIT_COPY_SEMANTICS**](json_brace_init_copy_semantics.md) - opt in to copy/move semantics for single-element brace initialization
|
||||||
- [**JSON_DISABLE_ENUM_SERIALIZATION**](json_disable_enum_serialization.md) - switch off default serialization/deserialization functions for enums
|
- [**JSON_DISABLE_ENUM_SERIALIZATION**](json_disable_enum_serialization.md) - switch off default serialization/deserialization functions for enums
|
||||||
|
- [**JSON_DISABLE_TUPLE_REFERENCE_CONVERSION**](json_disable_tuple_reference_conversion.md) - switch off conversion from a one-element tuple of a JSON reference
|
||||||
- [**JSON_USE_IMPLICIT_CONVERSIONS**](json_use_implicit_conversions.md) - control implicit conversions
|
- [**JSON_USE_IMPLICIT_CONVERSIONS**](json_use_implicit_conversions.md) - control implicit conversions
|
||||||
|
|
||||||
## Comparison behavior
|
## Comparison behavior
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
# JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION /* value */
|
||||||
|
```
|
||||||
|
|
||||||
|
When defined to `1`, a `basic_json` value can no longer be constructed from a one-element `std::tuple` whose element is
|
||||||
|
a reference to that `basic_json` type, such as `std::tuple<json&>`, `std::tuple<const json&>`, or `std::tuple<json&&>`.
|
||||||
|
These are the tuples created by `std::forward_as_tuple(j)`.
|
||||||
|
|
||||||
|
## Default definition
|
||||||
|
|
||||||
|
The default value is `0` (disabled — existing behavior is preserved).
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
!!! note "Background"
|
||||||
|
|
||||||
|
By default, `basic_json` can be constructed from any `std::tuple` whose elements can be converted to JSON; the result
|
||||||
|
is an array. This includes `std::tuple<json&>`, which becomes a one-element array.
|
||||||
|
|
||||||
|
`std::tuple` only converts another tuple element by element if its element type cannot be constructed from the whole
|
||||||
|
source tuple. Because `json` *can* be constructed from `std::tuple<json&>`, `std::tuple` instead converts the whole
|
||||||
|
tuple into a single `json` value. This has two surprising effects:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
json j = true;
|
||||||
|
|
||||||
|
// rejected by some standard libraries (e.g., libc++); with others, the
|
||||||
|
// reference binds to a temporary that is destroyed right away
|
||||||
|
std::tuple<const json&> t1(std::forward_as_tuple(j));
|
||||||
|
|
||||||
|
// compiles, but std::get<0>(t2) is [true], not true
|
||||||
|
std::tuple<json> t2(std::forward_as_tuple(j));
|
||||||
|
```
|
||||||
|
|
||||||
|
Enabling this macro removes the conversion, so both tuples are converted element by element: `std::get<0>(t1)`
|
||||||
|
refers to `j`, and `std::get<0>(t2)` is a copy of `j` (see [#2226](https://github.com/nlohmann/json/issues/2226)).
|
||||||
|
|
||||||
|
!!! warning "Opt-in only"
|
||||||
|
|
||||||
|
This macro must be defined **before** including `<nlohmann/json.hpp>`. Defining it after the include has no effect.
|
||||||
|
|
||||||
|
!!! note "Affected conversions"
|
||||||
|
|
||||||
|
Only one-element tuples holding a reference to the **same** `basic_json` type are affected. Constructing a JSON value
|
||||||
|
from them no longer compiles:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
json j = true;
|
||||||
|
json a = std::forward_as_tuple(j); // error with the macro enabled
|
||||||
|
json b = json::array({j}); // use this instead: [true]
|
||||||
|
```
|
||||||
|
|
||||||
|
Tuples holding a JSON value (`std::make_tuple(j)`), tuples with more than one element, and tuples holding references
|
||||||
|
to other types (including other `basic_json` specializations) are converted to arrays as before.
|
||||||
|
|
||||||
|
!!! hint "CMake option"
|
||||||
|
|
||||||
|
This behavior can also be controlled with the CMake option
|
||||||
|
[`JSON_DisableTupleReferenceConversion`](../../integration/cmake.md#json_disabletuplereferenceconversion)
|
||||||
|
(`OFF` by default) which defines `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION` accordingly.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
??? example "Default behavior (macro not defined)"
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
json j = true;
|
||||||
|
|
||||||
|
std::tuple<json> t(std::forward_as_tuple(j));
|
||||||
|
// std::get<0>(t) is [true] -- the whole tuple was converted
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
??? example "Conversion disabled (macro defined to 1)"
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 1
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
json j = true;
|
||||||
|
|
||||||
|
std::tuple<json> t(std::forward_as_tuple(j));
|
||||||
|
// std::get<0>(t) is true -- a copy of j
|
||||||
|
|
||||||
|
std::tuple<const json&> r(std::forward_as_tuple(j));
|
||||||
|
// std::get<0>(r) refers to j
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [**basic_json(CompatibleType&&)**](../basic_json/basic_json.md) - the affected constructor
|
||||||
|
- [:simple-cmake: JSON_DisableTupleReferenceConversion](../../integration/cmake.md#json_disabletuplereferenceconversion) -
|
||||||
|
CMake option to control the macro
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.13.0.
|
||||||
@@ -1 +0,0 @@
|
|||||||
<a target="_blank" href="https://wandbox.org/permlink/hUJYo1HWmfTBLMGn"><b>online</b></a>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<a target="_blank" href="https://wandbox.org/permlink/AWbpa8e1xRV3y4MM"><b>online</b></a>
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
// a SAX parser that creates a JSON value like json::parse does, but that
|
||||||
|
// recovers from parse errors instead of stopping at the first one
|
||||||
|
class recovering_parser : public nlohmann::detail::json_sax_dom_parser<json>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit recovering_parser(json& result)
|
||||||
|
: nlohmann::detail::json_sax_dom_parser<json>(result, false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool parse_error(std::size_t position,
|
||||||
|
const std::string& /*last_token*/,
|
||||||
|
const json::exception& ex)
|
||||||
|
{
|
||||||
|
std::cout << "byte " << position << ": " << ex.what() << '\n';
|
||||||
|
|
||||||
|
// repair the input and continue
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
// JSON text with several mistakes that ends too early
|
||||||
|
const std::string text = R"({
|
||||||
|
"name": "Hello World",
|
||||||
|
"tags": ["a" "b",],
|
||||||
|
"valid": tru,
|
||||||
|
"size": 1.,
|
||||||
|
"nested": {"x": 1)";
|
||||||
|
|
||||||
|
json result;
|
||||||
|
recovering_parser sax(result);
|
||||||
|
const bool valid = json::sax_parse(text, &sax);
|
||||||
|
|
||||||
|
std::cout << "\nvalid JSON: " << std::boolalpha << valid << '\n'
|
||||||
|
<< std::setw(4) << result << std::endl;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
byte 49: [json.exception.parse_error.101] parse error at line 3, column 20: syntax error while parsing array - unexpected string literal; expected ']'
|
||||||
|
byte 51: [json.exception.parse_error.101] parse error at line 3, column 22: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal
|
||||||
|
byte 70: [json.exception.parse_error.101] parse error at line 4, column 17: syntax error while parsing value - invalid literal; last read: '"valid": tru,'
|
||||||
|
byte 86: [json.exception.parse_error.101] parse error at line 5, column 15: syntax error while parsing value - invalid number; expected digit after '.'; last read: '1.,'
|
||||||
|
byte 109: [json.exception.parse_error.101] parse error at line 6, column 22: syntax error while parsing object - unexpected end of input; expected '}'
|
||||||
|
|
||||||
|
valid JSON: false
|
||||||
|
{
|
||||||
|
"name": "Hello World",
|
||||||
|
"nested": {
|
||||||
|
"x": 1
|
||||||
|
},
|
||||||
|
"size": 1,
|
||||||
|
"tags": [
|
||||||
|
"a",
|
||||||
|
"b"
|
||||||
|
],
|
||||||
|
"valid": null
|
||||||
|
}
|
||||||
@@ -61,7 +61,7 @@ The library uses the following mapping from JSON values types to BJData types ac
|
|||||||
|
|
||||||
The following values can **not** be converted to a BJData value:
|
The following values can **not** be converted to a BJData value:
|
||||||
|
|
||||||
- strings with more than 18446744073709551615 bytes, i.e., $2^{64}-1$ bytes (theoretical)
|
- strings with more than 18446744073709551615 bytes, i.e., 2<sup>64</sup>-1 bytes (theoretical)
|
||||||
|
|
||||||
!!! info "Unused BJData markers"
|
!!! info "Unused BJData markers"
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,13 @@ When defined, default parse and serialize functions for enums are excluded and h
|
|||||||
|
|
||||||
See [full documentation of `JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md).
|
See [full documentation of `JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md).
|
||||||
|
|
||||||
|
## `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`
|
||||||
|
|
||||||
|
When defined to `1`, a JSON value can no longer be created from a one-element `std::tuple` holding a reference to a JSON
|
||||||
|
value, such as the result of `std::forward_as_tuple(j)`. This lets `std::tuple` convert such tuples element-wise.
|
||||||
|
|
||||||
|
See [full documentation of `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md).
|
||||||
|
|
||||||
## `JSON_NO_AUTOMATIC_UDLS`
|
## `JSON_NO_AUTOMATIC_UDLS`
|
||||||
|
|
||||||
When defined, `<nlohmann/json.hpp>` does not include `<nlohmann/json_literals.hpp>` with the user-defined string literals
|
When defined, `<nlohmann/json.hpp>` does not include `<nlohmann/json_literals.hpp>` with the user-defined string literals
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
# Error Recovery
|
||||||
|
|
||||||
|
By default, parsing stops at the first error. With the [SAX interface](sax_interface.md), you can instead ask the
|
||||||
|
parser to *recover*: to repair the error and continue, so that you get as much as possible out of malformed input, for
|
||||||
|
instance a file that was cut off, JSON edited by hand, or the output of a language model.
|
||||||
|
|
||||||
|
## Recovering from errors
|
||||||
|
|
||||||
|
The SAX parser's [`parse_error`](../../api/json_sax/parse_error.md) function is called for every error. Its return value
|
||||||
|
decides what happens next:
|
||||||
|
|
||||||
|
- `#!cpp false` stops parsing. This is what the SAX parsers of the library do, so [`parse`](../../api/basic_json/parse.md)
|
||||||
|
and [`accept`](../../api/basic_json/accept.md) never recover.
|
||||||
|
- `#!cpp true` repairs the error and continues parsing.
|
||||||
|
|
||||||
|
When recovering, the SAX parser still receives well-formed events: every `start_object` or `start_array` is followed by
|
||||||
|
the matching `end_object` or `end_array`, and every `key` is followed by exactly one value. A SAX parser that creates a
|
||||||
|
JSON value, such as the one in the example below, therefore gets a complete value. Parsing always ends, and
|
||||||
|
[`sax_parse`](../../api/basic_json/sax_parse.md) returns `#!cpp false` for input that is not valid JSON, even if every
|
||||||
|
error was repaired. Each token is reported at most once, and the SAX parser can stop at any error by returning
|
||||||
|
`#!cpp false`.
|
||||||
|
|
||||||
|
!!! example
|
||||||
|
|
||||||
|
The example below derives a SAX parser from the library's parser for `json` values (`json_sax_dom_parser`),
|
||||||
|
and recovers from all errors.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
--8<-- "examples/sax_parse__error_recovery.cpp"
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
--8<-- "examples/sax_parse__error_recovery.output"
|
||||||
|
```
|
||||||
|
|
||||||
|
## How errors are repaired
|
||||||
|
|
||||||
|
Each error is repaired with the smallest local edit: a missing separator is inserted, a stray token is removed, what can
|
||||||
|
be read of a broken string or number is kept, and a value that cannot be read at all becomes `#!json null`.
|
||||||
|
|
||||||
|
| Mistake | Repair | Example | Result |
|
||||||
|
|---------------------------|--------------------------------------------------------------------------------|------------------------------------------|----------------------------|
|
||||||
|
| missing `,` or `:` | inserted | `#!json [1 2]`, `#!json {"a" 1}` | `[1,2]`, `{"a":1}` |
|
||||||
|
| missing value | `#!json null` for an object key or between commas in an array | `#!json {"a":}`, `#!json [1,,2]` | `{"a":null}`, `[1,null,2]` |
|
||||||
|
| trailing comma | removed | `#!json [1,2,]` | `[1,2]` |
|
||||||
|
| broken string | invalid escapes and bytes are replaced (see below); a line break ends the string | `#!json ["a\qb"]` | `["aqb"]` |
|
||||||
|
| broken number | the longest valid beginning is kept | `#!json [1., 2e+]` | `[1,2]` |
|
||||||
|
| unreadable value | `#!json null` | `#!json [1, NaN, tru]` | `[1,null,null]` |
|
||||||
|
| number too large | passed as infinity, together with its text | `#!json [1e999]` | infinity (see below) |
|
||||||
|
| stray `:` | removed | `#!json ["a":1]` | `["a",1]` |
|
||||||
|
| member without a key | skipped up to the next `,` or `}` | `#!json {1:2, "b":3}` | `{"b":3}` |
|
||||||
|
| wrong closing bracket | closes the innermost array or object | `#!json {"a":[1,2}, "b":3}` | `{"a":[1,2],"b":3}` |
|
||||||
|
| input ends too early | all open arrays and objects are closed | `#!json {"a":[1,2` | `{"a":[1,2]}` |
|
||||||
|
| text before the value | skipped | `#!json )]}'{"a":1}` | `{"a":1}` |
|
||||||
|
|
||||||
|
In a string, an unknown escape like `\q` stands for the escaped character (`q`), as in JavaScript. An invalid `\u`
|
||||||
|
escape, a lone surrogate, and ill-formed UTF-8 are each replaced by U+FFFD (REPLACEMENT CHARACTER), and control
|
||||||
|
characters are kept. A string without its closing quote ends at the next line break or at the end of the input.
|
||||||
|
|
||||||
|
The input after the top-level value is not repaired: as without recovery, it is reported as an error, and parsing stops.
|
||||||
|
|
||||||
|
## Binary formats
|
||||||
|
|
||||||
|
The binary formats ([BJData](../binary_formats/bjdata.md), [BON8](../binary_formats/bon8.md),
|
||||||
|
[BSON](../binary_formats/bson.md), [CBOR](../binary_formats/cbor.md), [MessagePack](../binary_formats/messagepack.md),
|
||||||
|
and [UBJSON](../binary_formats/ubjson.md)) have no delimiters to find the next value by. So what can be repaired depends
|
||||||
|
on whether the end of the item with the error is known, a distinction that
|
||||||
|
[RFC 8949, Section 5.3](https://www.rfc-editor.org/rfc/rfc8949.html#section-5.3) makes for CBOR, too.
|
||||||
|
|
||||||
|
If the item is complete, but cannot be passed on as it is, it is replaced, and parsing continues after it:
|
||||||
|
|
||||||
|
| Mistake | Formats | Repair |
|
||||||
|
|---------------------------------------------------------------------|-----------------------------------------|-------------------------------------------------------------------------|
|
||||||
|
| tag | CBOR | ignored |
|
||||||
|
| simple value other than `false`, `true`, and `null`, like undefined | CBOR | `#!json null` |
|
||||||
|
| negative integer below the range of `number_integer_t` | CBOR | the nearest floating-point number |
|
||||||
|
| string that is not valid UTF-8 | BJData, BSON, CBOR, MessagePack, UBJSON | each ill-formed sequence becomes U+FFFD |
|
||||||
|
| character (`C`) that is not ASCII | BJData, UBJSON | U+FFFD |
|
||||||
|
| invalid high-precision number (`H`) | BJData, UBJSON | the longest valid beginning is kept, as for JSON text, or `#!json null` |
|
||||||
|
| high-precision number too large | BJData, UBJSON | passed as infinity, together with its text |
|
||||||
|
| object key that is not a string | BON8, CBOR, MessagePack | the member is skipped |
|
||||||
|
| element of a type the library does not read, like ObjectId or date | BSON | `#!json null` |
|
||||||
|
| string without its terminator | BSON | kept |
|
||||||
|
| document whose size does not match its content | BSON | kept |
|
||||||
|
|
||||||
|
CBOR tags and simple values are repaired as [RFC 8949, Section 6.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-6.1)
|
||||||
|
suggests for converting CBOR to JSON. Note that [`sax_parse`](../../api/basic_json/sax_parse.md) has no parameter for
|
||||||
|
CBOR tags, so every tag is an error there; when recovering, tags are ignored like with
|
||||||
|
[`cbor_tag_handler_t::ignore`](../../api/basic_json/cbor_tag_handler_t.md).
|
||||||
|
|
||||||
|
After any other error, the end of the item is unknown: the input ended, a byte is not a valid type marker, or a size
|
||||||
|
cannot be right. Parsing then stops, and the value read so far is completed: a key that waits for its value gets
|
||||||
|
`#!json null`, and all open arrays and objects are closed. This keeps everything before the error of an input that was
|
||||||
|
cut off. The exception is BSON, which stores the size of every document: an element whose end is unknown gets
|
||||||
|
`#!json null`, the rest of its document is skipped, and parsing continues after the document.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- A repair is a guess. For example, `#!json {"a" "b": 1}` could be meant as `#!json {"a": "b"}` or as
|
||||||
|
`#!json {"a": null, "b": 1}`; it is repaired to the former. Treat recovered values as a best effort, and check the
|
||||||
|
reported errors.
|
||||||
|
- A closing bracket always closes the innermost array or object. If a bracket is missing rather than wrong, the
|
||||||
|
repair differs from the intention: `#!json {"a": {"b": [1, 2}, "c": 3}` is repaired to
|
||||||
|
`#!json {"a": {"b": [1, 2], "c": 3}}`, although `#!json {"a": {"b": [1, 2]}, "c": 3}` may have been meant.
|
||||||
|
- Keys without quotes, and strings in single quotes, are not supported; such members are skipped.
|
||||||
|
- In the binary formats, a member that is skipped because its key is not a string is lost, and so are the elements of a
|
||||||
|
BSON document after one whose end is unknown.
|
||||||
|
- A number that is too large for `number_float_t` is passed as positive or negative infinity. The SAX parser's
|
||||||
|
`number_float` also gets the number's text, but a JSON value cannot store it, and
|
||||||
|
[`dump`](../../api/basic_json/dump.md) serializes infinity as `#!json null`.
|
||||||
|
- When parsing is not strict (see [`sax_parse`](../../api/basic_json/sax_parse.md)), a repair may read parts of the
|
||||||
|
input after the value, for instance of the next value in a stream of concatenated values.
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
||||||
|
- [`parse_error`](../../api/json_sax/parse_error.md) - the SAX event for parse errors
|
||||||
|
- [`sax_parse`](../../api/basic_json/sax_parse.md) - generate SAX events
|
||||||
|
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
||||||
@@ -65,7 +65,7 @@ You can influence a DOM parse without switching to the SAX interface by passing
|
|||||||
When the input is not valid JSON, the `parse` function throws an exception by default. If exceptions are undesired or
|
When the input is not valid JSON, the `parse` function throws an exception by default. If exceptions are undesired or
|
||||||
unavailable, the parser can instead return a discarded value, or [`accept`](../../api/basic_json/accept.md) can be used
|
unavailable, the parser can instead return a discarded value, or [`accept`](../../api/basic_json/accept.md) can be used
|
||||||
to only check whether an input is valid JSON. See [parsing and exceptions](parse_exceptions.md) for the available
|
to only check whether an input is valid JSON. See [parsing and exceptions](parse_exceptions.md) for the available
|
||||||
options.
|
options. To get as much as possible out of malformed input, a SAX parser can [recover from errors](error_recovery.md).
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
@@ -76,3 +76,4 @@ options.
|
|||||||
- [parser callbacks](parser_callbacks.md) - influence the parsing by a callback function
|
- [parser callbacks](parser_callbacks.md) - influence the parsing by a callback function
|
||||||
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
||||||
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
||||||
|
- [error recovery](error_recovery.md) - get as much as possible out of malformed input
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ bool parse_error(std::size_t position,
|
|||||||
const json::exception& ex);
|
const json::exception& ex);
|
||||||
```
|
```
|
||||||
|
|
||||||
The return value indicates whether the parsing should continue, so the function should usually return `#!cpp false`.
|
The return value decides whether to stop parsing (`#!cpp false`) or to repair the error and continue
|
||||||
|
(`#!cpp true`); see [error recovery](error_recovery.md) for the latter.
|
||||||
|
|
||||||
??? example
|
??? example
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ bool key(string_t& val);
|
|||||||
bool parse_error(std::size_t position, const std::string& last_token, const json::exception& ex);
|
bool parse_error(std::size_t position, const std::string& last_token, const json::exception& ex);
|
||||||
```
|
```
|
||||||
|
|
||||||
The return value of each function determines whether parsing should proceed.
|
The return value of each function determines whether parsing should proceed. For `parse_error`, returning
|
||||||
|
`#!cpp true` [recovers from the error](error_recovery.md).
|
||||||
|
|
||||||
To implement your own SAX handler, proceed as follows:
|
To implement your own SAX handler, proceed as follows:
|
||||||
|
|
||||||
@@ -68,7 +69,7 @@ To implement your own SAX handler, proceed as follows:
|
|||||||
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
||||||
3. Call `#!cpp bool json::sax_parse(input, &my_sax);` where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
|
3. Call `#!cpp bool json::sax_parse(input, &my_sax);` where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
|
||||||
|
|
||||||
Note the `sax_parse` function only returns a `#!cpp bool` indicating the result of the last executed SAX event. It does not return `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error - it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file `json_sax.hpp`.
|
Note the `sax_parse` function only returns a `#!cpp bool` indicating whether the input was parsed without errors and no SAX event returned `#!cpp false`. It does not return `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error - it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file `json_sax.hpp`.
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ When the default type is used, the maximal unsigned integer number that can be s
|
|||||||
|
|
||||||
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
|
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
|
||||||
|
|
||||||
> Note that when such software is used, numbers that are integers and are in the range $[-2^{53}+1, 2^{53}-1]$ are interoperable in the sense that implementations will agree exactly on their numeric values.
|
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are interoperable in the sense that implementations will agree exactly on their numeric values.
|
||||||
|
|
||||||
As this range is a subrange of the exactly supported range [`INT64_MIN`, `INT64_MAX`], this class's integer type is interoperable.
|
As this range is a subrange of the exactly supported range [`INT64_MIN`, `INT64_MAX`], this class's integer type is interoperable.
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ On number interoperability, the following remarks are made:
|
|||||||
for numeric magnitude and precision than is widely available.
|
for numeric magnitude and precision than is widely available.
|
||||||
|
|
||||||
Note that when such software is used, numbers that are integers and
|
Note that when such software is used, numbers that are integers and
|
||||||
are in the range $[-2^{53}+1, 2^{53}-1]$ are interoperable in the
|
are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are interoperable in the
|
||||||
sense that implementations will agree exactly on their numeric
|
sense that implementations will agree exactly on their numeric
|
||||||
values.
|
values.
|
||||||
|
|
||||||
@@ -71,11 +71,10 @@ otherwise, it uses unsigned integer storage.
|
|||||||
|
|
||||||
- Numbers with a decimal digit or scientific notation are always stored as `#!c double`.
|
- Numbers with a decimal digit or scientific notation are always stored as `#!c double`.
|
||||||
- The number types can be changed, see [Template number types](#template-number-types).
|
- The number types can be changed, see [Template number types](#template-number-types).
|
||||||
- The library converts integers and floating-point numbers itself, independent of the locale. Floating-point
|
- As of version 3.9.1, the conversion is realized by
|
||||||
numbers are correctly rounded (to nearest, ties to even). Only a `#!c long double` that is not IEEE 754 binary64
|
[`std::strtoull`](https://en.cppreference.com/w/cpp/string/byte/strtoul),
|
||||||
(e.g., the 80-bit x87 format) is converted with `#!cpp std::from_chars` where available, or with
|
[`std::strtoll`](https://en.cppreference.com/w/cpp/string/byte/strtol), and
|
||||||
[`std::strtold`](https://en.cppreference.com/w/cpp/string/byte/strtof), which gets the decimal point of the
|
[`std::strtod`](https://en.cppreference.com/w/cpp/string/byte/strtof), respectively.
|
||||||
current locale, also one longer than one byte (e.g., in `fa_IR.UTF-8`).
|
|
||||||
|
|
||||||
!!! example "Examples"
|
!!! example "Examples"
|
||||||
|
|
||||||
@@ -86,19 +85,19 @@ otherwise, it uses unsigned integer storage.
|
|||||||
### Number limits
|
### Number limits
|
||||||
|
|
||||||
- Any 64-bit signed or unsigned integer can be stored without loss of precision.
|
- Any 64-bit signed or unsigned integer can be stored without loss of precision.
|
||||||
- Numbers exceeding the limits of `#!c double` (i.e., numbers whose rounded value is not satisfying
|
- Numbers exceeding the limits of `#!c double` (i.e., numbers that after conversion via
|
||||||
|
[`std::strtod`](https://en.cppreference.com/w/cpp/string/byte/strtof) are not satisfying
|
||||||
[`std::isfinite`](https://en.cppreference.com/w/cpp/numeric/math/isfinite) such as `#!c 1E400`) will throw exception
|
[`std::isfinite`](https://en.cppreference.com/w/cpp/numeric/math/isfinite) such as `#!c 1E400`) will throw exception
|
||||||
[`json.exception.out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406) during parsing. Numbers too
|
[`json.exception.out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406) during parsing.
|
||||||
small for `#!c double` (such as `#!c 1E-400`) become zero, with the sign of the number.
|
|
||||||
- Floating-point numbers are rounded to the next number representable as `double`. For instance
|
- Floating-point numbers are rounded to the next number representable as `double`. For instance
|
||||||
`#!c 3.141592653589793238462643383279` is stored as [`0x400921fb54442d18`](https://float.exposed/0x400921fb54442d18).
|
`#!c 3.141592653589793238462643383279` is stored as [`0x400921fb54442d18`](https://float.exposed/0x400921fb54442d18).
|
||||||
This is the same behavior as the code `#!c double x = 3.141592653589793238462643383279;`.
|
This is the same behavior as the code `#!c double x = 3.141592653589793238462643383279;`.
|
||||||
|
|
||||||
!!! success "Interoperability"
|
!!! success "Interoperability"
|
||||||
|
|
||||||
- The library is interoperable with respect to the specification, because its supported range $[-2^{63}, 2^{64}-1]$ is
|
- The library is interoperable with respect to the specification, because its supported range [-2<sup>63</sup>, 2<sup>64</sup>-1] is
|
||||||
larger than the described range $[-2^{53}+1, 2^{53}-1]$.
|
larger than the described range [-2<sup>53</sup>+1, 2<sup>53</sup>-1].
|
||||||
- All integers outside the range $[-2^{63}, 2^{64}-1]$, as well as floating-point numbers are stored as `double`.
|
- All integers outside the range [-2<sup>63</sup>, 2<sup>64</sup>-1], as well as floating-point numbers are stored as `double`.
|
||||||
This also concurs with the specification above.
|
This also concurs with the specification above.
|
||||||
|
|
||||||
### Zeros
|
### Zeros
|
||||||
|
|||||||
@@ -26,9 +26,8 @@ Requirements are split into two groups:
|
|||||||
diagnosed with dedicated error messages, and violating most of them results in a compiler error somewhere inside
|
diagnosed with dedicated error messages, and violating most of them results in a compiler error somewhere inside
|
||||||
the library. Four violations are not caught at compile time at all:
|
the library. Four violations are not caught at compile time at all:
|
||||||
|
|
||||||
- A [`StringType`](#stringtype) whose `data()` is not null-terminated compiles and silently misparses numbers
|
- A [`StringType`](#stringtype) whose `data()` is not null-terminated compiles and silently misparses numbers,
|
||||||
stored as a `#!cpp long double` that is not IEEE 754 binary64 (e.g., the 80-bit x87 format), because the lexer
|
because the lexer hands the buffer to `#!cpp std::strtoull`/`#!cpp std::strtoll`/`#!cpp std::strtod`.
|
||||||
hands the buffer to `#!cpp std::strtold`.
|
|
||||||
- A stateful [`AllocatorType`](#allocatortype) compiles and silently ignores its state: allocation, deallocation,
|
- A stateful [`AllocatorType`](#allocatortype) compiles and silently ignores its state: allocation, deallocation,
|
||||||
and [`get_allocator()`](../../api/basic_json/get_allocator.md) each use a different default-constructed instance.
|
and [`get_allocator()`](../../api/basic_json/get_allocator.md) each use a different default-constructed instance.
|
||||||
- The two [cross-specialization conversions](#cross-specialization-conversions) below. These abort on an assertion
|
- The two [cross-specialization conversions](#cross-specialization-conversions) below. These abort on an assertion
|
||||||
@@ -536,10 +535,8 @@ therefore silently changes parse results rather than raising an error. See
|
|||||||
|
|
||||||
`NumberFloatType` must be one of `#!cpp float`, `#!cpp double`, or `#!cpp long double`:
|
`NumberFloatType` must be one of `#!cpp float`, `#!cpp double`, or `#!cpp long double`:
|
||||||
|
|
||||||
- The [parser](../parsing/index.md) converts number literals to `#!cpp float`, `#!cpp double`, and a
|
- The [parser](../parsing/index.md) converts number literals with `#!cpp std::strtof`, `#!cpp std::strtod`, or
|
||||||
`#!cpp long double` that is IEEE 754 binary64 itself; other `#!cpp long double` formats are converted with
|
`#!cpp std::strtold`; the library provides overloads for exactly these three types.
|
||||||
`#!cpp std::from_chars` where available, or with `#!cpp std::strtold`. The library provides overloads for exactly
|
|
||||||
these three types.
|
|
||||||
- [`dump`](../../api/basic_json/dump.md) falls back to `#!cpp std::snprintf` with the `%g` and `%Lg` conversion
|
- [`dump`](../../api/basic_json/dump.md) falls back to `#!cpp std::snprintf` with the `%g` and `%Lg` conversion
|
||||||
specifiers, for which the library likewise provides only `#!cpp double` and `#!cpp long double` overloads
|
specifiers, for which the library likewise provides only `#!cpp double` and `#!cpp long double` overloads
|
||||||
(`#!cpp float` is promoted to `#!cpp double`).
|
(`#!cpp float` is promoted to `#!cpp double`).
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ The class contains a slightly modified version of the Grisu2 algorithm from Flor
|
|||||||
|
|
||||||
The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/).
|
The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/).
|
||||||
|
|
||||||
The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, and its digit comparison for long numbers from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
The class contains an adapted version of the Eisel-Lemire algorithm and its table of powers of five from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
||||||
|
|||||||
@@ -169,6 +169,12 @@ Enable position diagnostics by defining macro [`JSON_DIAGNOSTIC_POSITIONS`](../a
|
|||||||
Disable default `enum` serialization by defining the macro
|
Disable default `enum` serialization by defining the macro
|
||||||
[`JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md). This option is `OFF` by default.
|
[`JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md). This option is `OFF` by default.
|
||||||
|
|
||||||
|
### `JSON_DisableTupleReferenceConversion`
|
||||||
|
|
||||||
|
Disable the conversion from a one-element `std::tuple` holding a reference to a JSON value by defining the macro
|
||||||
|
[`JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md). This option is
|
||||||
|
`OFF` by default.
|
||||||
|
|
||||||
### `JSON_FastTests`
|
### `JSON_FastTests`
|
||||||
|
|
||||||
Skip expensive/slow test suites. This option is `OFF` by default. Depends on `JSON_BuildTests`.
|
Skip expensive/slow test suites. This option is `OFF` by default. Depends on `JSON_BuildTests`.
|
||||||
|
|||||||
@@ -678,11 +678,11 @@ to install the [nlohmann-json](https://ports.macports.org/port/nlohmann-json/) p
|
|||||||
1. Create the following files:
|
1. Create the following files:
|
||||||
|
|
||||||
```cpp title="example.cpp"
|
```cpp title="example.cpp"
|
||||||
--8<-- "integration/homebrew/example.cpp"
|
--8<-- "integration/macports/example.cpp"
|
||||||
```
|
```
|
||||||
|
|
||||||
```cmake title="CMakeLists.txt"
|
```cmake title="CMakeLists.txt"
|
||||||
--8<-- "integration/homebrew/CMakeLists.txt"
|
--8<-- "integration/macports/CMakeLists.txt"
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Install the package:
|
2. Install the package:
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ nav:
|
|||||||
- features/object_order.md
|
- features/object_order.md
|
||||||
- Parsing:
|
- Parsing:
|
||||||
- features/parsing/index.md
|
- features/parsing/index.md
|
||||||
|
- features/parsing/error_recovery.md
|
||||||
- features/parsing/json_lines.md
|
- features/parsing/json_lines.md
|
||||||
- features/parsing/parse_exceptions.md
|
- features/parsing/parse_exceptions.md
|
||||||
- features/parsing/parser_callbacks.md
|
- features/parsing/parser_callbacks.md
|
||||||
@@ -287,6 +288,7 @@ nav:
|
|||||||
- 'JSON_DIAGNOSTICS': api/macros/json_diagnostics.md
|
- 'JSON_DIAGNOSTICS': api/macros/json_diagnostics.md
|
||||||
- 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md
|
- 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md
|
||||||
- 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md
|
- 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md
|
||||||
|
- 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION': api/macros/json_disable_tuple_reference_conversion.md
|
||||||
- 'JSON_HAS_CPP_11, JSON_HAS_CPP_14, JSON_HAS_CPP_17, JSON_HAS_CPP_20': api/macros/json_has_cpp_11.md
|
- 'JSON_HAS_CPP_11, JSON_HAS_CPP_14, JSON_HAS_CPP_17, JSON_HAS_CPP_20': api/macros/json_has_cpp_11.md
|
||||||
- 'JSON_HAS_EXPERIMENTAL_FILESYSTEM, JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.md
|
- 'JSON_HAS_EXPERIMENTAL_FILESYSTEM, JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.md
|
||||||
- 'JSON_HAS_RANGES': api/macros/json_has_ranges.md
|
- 'JSON_HAS_RANGES': api/macros/json_has_ranges.md
|
||||||
@@ -350,7 +352,6 @@ markdown_extensions:
|
|||||||
- toc:
|
- toc:
|
||||||
permalink: true
|
permalink: true
|
||||||
- md_in_html
|
- md_in_html
|
||||||
- pymdownx.arithmatex
|
|
||||||
- pymdownx.betterem:
|
- pymdownx.betterem:
|
||||||
smart_enable: all
|
smart_enable: all
|
||||||
- pymdownx.caret
|
- pymdownx.caret
|
||||||
@@ -442,6 +443,3 @@ plugins:
|
|||||||
|
|
||||||
extra_css:
|
extra_css:
|
||||||
- css/custom.css
|
- css/custom.css
|
||||||
|
|
||||||
extra_javascript:
|
|
||||||
- https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-MML-AM_CHTML
|
|
||||||
|
|||||||
@@ -79,9 +79,9 @@ def check_structure() -> None:
|
|||||||
report("whitespace/line_length", f"{file}:{lineno+1} ({current_section})", f"line is too long ({len(line)} vs. 160 chars)")
|
report("whitespace/line_length", f"{file}:{lineno+1} ({current_section})", f"line is too long ({len(line)} vs. 160 chars)")
|
||||||
|
|
||||||
# sections in `<!-- NOLINT -->` comments are treated as present
|
# sections in `<!-- NOLINT -->` comments are treated as present
|
||||||
if line.startswith("<!-- NOLINT"):
|
nolint_match = re.match(r"<!--\s*NOLINT\s+(.*?)\s*-->", line)
|
||||||
current_section = line.strip("<!-- NOLINT")
|
if nolint_match:
|
||||||
current_section = current_section.strip(" -->")
|
current_section = nolint_match.group(1)
|
||||||
existing_sections.append(current_section)
|
existing_sections.append(current_section)
|
||||||
|
|
||||||
# check if sections are correct
|
# check if sections are correct
|
||||||
@@ -97,7 +97,7 @@ def check_structure() -> None:
|
|||||||
if len(unexpected):
|
if len(unexpected):
|
||||||
report("style/numbering", f"{file}:{lineno} ({current_section})", f'unexpected overloads: {", ".join([f"({x})" for x in unexpected])}')
|
report("style/numbering", f"{file}:{lineno} ({current_section})", f'unexpected overloads: {", ".join([f"({x})" for x in unexpected])}')
|
||||||
|
|
||||||
current_section = line.strip("## ")
|
current_section = line[3:]
|
||||||
existing_sections.append(current_section)
|
existing_sections.append(current_section)
|
||||||
|
|
||||||
if current_section in expected_sections:
|
if current_section in expected_sections:
|
||||||
@@ -141,7 +141,7 @@ def check_structure() -> None:
|
|||||||
# check that non-example admonitions have titles
|
# check that non-example admonitions have titles
|
||||||
untitled_admonition = re.match(r"^(\?\?\?|!!!) ([^ ]+)$", line)
|
untitled_admonition = re.match(r"^(\?\?\?|!!!) ([^ ]+)$", line)
|
||||||
if untitled_admonition and untitled_admonition.group(2) != "example":
|
if untitled_admonition and untitled_admonition.group(2) != "example":
|
||||||
report("style/admonition_title", f"{file}:{lineno} ({current_section})", f'"{untitled_admonition.group(2)}" admonitions should have a title')
|
report("style/admonition_title", f"{file}:{lineno+1} ({current_section})", f'"{untitled_admonition.group(2)}" admonitions should have a title')
|
||||||
|
|
||||||
previous_line = line
|
previous_line = line
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <nlohmann/detail/abi_macros.hpp>
|
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
||||||
namespace detail
|
|
||||||
{
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief the configuration macros that change the library's behavior
|
|
||||||
|
|
||||||
json.hpp undefines these macros at its end (see macro_unscope.hpp), so code
|
|
||||||
that builds on the library after it (json_view.hpp) reads them here. Like the
|
|
||||||
macros, they are part of the ABI namespace, so they always match the
|
|
||||||
basic_json they are used with.
|
|
||||||
*/
|
|
||||||
struct abi_config
|
|
||||||
{
|
|
||||||
/// JSON_STRICT_NUL_HANDLING: a null byte is an error, not the end of input
|
|
||||||
static constexpr bool strict_nul_handling = JSON_STRICT_NUL_HANDLING != 0;
|
|
||||||
/// JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
|
||||||
static constexpr bool legacy_discarded_value_comparison = JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON != 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace detail
|
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
|
||||||
@@ -39,25 +39,6 @@ inline int count_leading_zeros(std::uint64_t x) noexcept
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// number of trailing zero bits of x (x != 0)
|
|
||||||
inline int count_trailing_zeros(std::uint64_t x) noexcept
|
|
||||||
{
|
|
||||||
#if defined(__GNUC__) || defined(__clang__)
|
|
||||||
return __builtin_ctzll(x);
|
|
||||||
#else
|
|
||||||
int n = 0;
|
|
||||||
for (int shift = 32; shift != 0; shift >>= 1)
|
|
||||||
{
|
|
||||||
if ((x << (64 - shift)) == 0)
|
|
||||||
{
|
|
||||||
n += shift;
|
|
||||||
x >>= shift;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
#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
|
||||||
{
|
{
|
||||||
@@ -87,19 +68,14 @@ inline uint128_parts full_multiplication(std::uint64_t a, std::uint64_t b) noexc
|
|||||||
|
|
||||||
/// eight bytes as a little-endian word (compilers fold this into one load on
|
/// eight bytes as a little-endian word (compilers fold this into one load on
|
||||||
/// little-endian targets)
|
/// little-endian targets)
|
||||||
inline std::uint64_t read_eight_bytes(const unsigned char* b) noexcept
|
inline std::uint64_t read_eight_bytes(const char* p) noexcept
|
||||||
{
|
{
|
||||||
|
const auto* b = reinterpret_cast<const unsigned char*>(p); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
return static_cast<std::uint64_t>(b[0]) | (static_cast<std::uint64_t>(b[1]) << 8u)
|
return static_cast<std::uint64_t>(b[0]) | (static_cast<std::uint64_t>(b[1]) << 8u)
|
||||||
| (static_cast<std::uint64_t>(b[2]) << 16u) | (static_cast<std::uint64_t>(b[3]) << 24u)
|
| (static_cast<std::uint64_t>(b[2]) << 16u) | (static_cast<std::uint64_t>(b[3]) << 24u)
|
||||||
| (static_cast<std::uint64_t>(b[4]) << 32u) | (static_cast<std::uint64_t>(b[5]) << 40u)
|
| (static_cast<std::uint64_t>(b[4]) << 32u) | (static_cast<std::uint64_t>(b[5]) << 40u)
|
||||||
| (static_cast<std::uint64_t>(b[6]) << 48u) | (static_cast<std::uint64_t>(b[7]) << 56u);
|
| (static_cast<std::uint64_t>(b[6]) << 48u) | (static_cast<std::uint64_t>(b[7]) << 56u);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// eight bytes as a little-endian word
|
|
||||||
inline std::uint64_t read_eight_bytes(const char* p) noexcept
|
|
||||||
{
|
|
||||||
return read_eight_bytes(reinterpret_cast<const unsigned char*>(p)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
|
|||||||
@@ -471,11 +471,13 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
|
|||||||
j = { std::get<Idx>(t)... };
|
j = { std::get<Idx>(t)... };
|
||||||
}
|
}
|
||||||
|
|
||||||
#if JSON_BRACE_INIT_COPY_SEMANTICS
|
// A one-element braced list does not reliably wrap its element: with
|
||||||
// JSON_BRACE_INIT_COPY_SEMANTICS makes a one-element braced list copy its
|
// JSON_BRACE_INIT_COPY_SEMANTICS it copies it, which would serialize
|
||||||
// element instead of wrapping it, which would serialize std::tuple<int>{5} as 5
|
// std::tuple<int>{5} as 5 rather than [5], and some compilers (e.g., Apple clang
|
||||||
// rather than [5]. Build what the default deduction builds instead: an object
|
// 15 and 16) copy an element that is itself a basic_json even without it, so
|
||||||
// if the element is a [string, value] pair, a one-element array otherwise.
|
// std::tuple<json>{true} became true rather than [true]. Build what the default
|
||||||
|
// deduction builds instead: an object if the element is a [string, value] pair,
|
||||||
|
// a one-element array otherwise.
|
||||||
template<typename BasicJsonType, typename Tuple>
|
template<typename BasicJsonType, typename Tuple>
|
||||||
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<0> /*unused*/)
|
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<0> /*unused*/)
|
||||||
{
|
{
|
||||||
@@ -493,7 +495,6 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
|
|||||||
j = BasicJsonType::array({std::move(element)});
|
j = BasicJsonType::array({std::move(element)});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
template<typename BasicJsonType, typename Tuple>
|
template<typename BasicJsonType, typename Tuple>
|
||||||
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/)
|
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -131,7 +131,9 @@ struct json_sax
|
|||||||
@param[in] position the position in the input where the error occurs
|
@param[in] position the position in the input where the error occurs
|
||||||
@param[in] last_token the last read token
|
@param[in] last_token the last read token
|
||||||
@param[in] ex an exception object describing the error
|
@param[in] ex an exception object describing the error
|
||||||
@return whether parsing should proceed (must return false)
|
@return whether to recover from the error: false stops parsing; true
|
||||||
|
repairs the error and continues, or, if that is not possible,
|
||||||
|
stops after completing the value read so far
|
||||||
*/
|
*/
|
||||||
virtual bool parse_error(std::size_t position,
|
virtual bool parse_error(std::size_t position,
|
||||||
const std::string& last_token,
|
const std::string& last_token,
|
||||||
@@ -186,9 +188,12 @@ a pointer to the respective array or object for each recursion depth.
|
|||||||
After successful parsing, the value that is passed by reference to the
|
After successful parsing, the value that is passed by reference to the
|
||||||
constructor contains the parsed value.
|
constructor contains the parsed value.
|
||||||
|
|
||||||
@tparam BasicJsonType the JSON type
|
@tparam BasicJsonType the JSON type
|
||||||
|
@tparam InputAdapterType the input adapter of the lexer that can be passed to
|
||||||
|
the constructor to record diagnostic positions; it
|
||||||
|
does not matter if no lexer is passed
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType = string_input_adapter_type>
|
||||||
class json_sax_dom_parser
|
class json_sax_dom_parser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -505,7 +510,7 @@ class json_sax_dom_parser
|
|||||||
lexer_t* m_lexer_ref = nullptr;
|
lexer_t* m_lexer_ref = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType = string_input_adapter_type>
|
||||||
class json_sax_dom_callback_parser
|
class json_sax_dom_callback_parser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
|
#include <cstdint> // uint8_t
|
||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
#include <initializer_list> // initializer_list
|
#include <initializer_list> // initializer_list
|
||||||
#include <string> // char_traits, string
|
#include <string> // char_traits, string
|
||||||
@@ -219,44 +220,6 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
// scan functions
|
// scan functions
|
||||||
/////////////////////
|
/////////////////////
|
||||||
|
|
||||||
/// contiguous input: try to decode the 4 hex digits following `\u`
|
|
||||||
/// directly from the input buffer via hex_codepoint(), instead of 4 calls
|
|
||||||
/// to get(). On success, advances the adapter and the position counters
|
|
||||||
/// exactly as those 4 get() calls would (a hex digit is never '\n', so
|
|
||||||
/// only the flat counters move) and leaves @a current holding the last of
|
|
||||||
/// the 4 digits, just as the last such get() would; the codepoint is
|
|
||||||
/// written to @a out. Makes no state change and returns false - for a
|
|
||||||
/// pending unget, fewer than 4 remaining bytes, or any of the 4 bytes not
|
|
||||||
/// being a hex digit - so the caller falls back unchanged to the
|
|
||||||
/// per-character loop, which then reports the same diagnostic (stopping
|
|
||||||
/// at the first invalid digit) as before this optimization.
|
|
||||||
bool get_codepoint_bulk(std::true_type /*bulk*/, int& out)
|
|
||||||
{
|
|
||||||
if (next_unget || ia.bulk_remaining() < 4)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const char_type* const raw = ia.bulk_data();
|
|
||||||
const int codepoint = hex_codepoint(reinterpret_cast<const unsigned char*>(raw));
|
|
||||||
if (codepoint < 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ia.bulk_skip(4);
|
|
||||||
// a hex digit is never a newline, so only the flat counters advance
|
|
||||||
position.chars_read_total += 4;
|
|
||||||
position.chars_read_current_line += 4;
|
|
||||||
current = char_traits<char_type>::to_int_type(raw[3]);
|
|
||||||
out = codepoint;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// streaming input: no bulk fast path
|
|
||||||
bool get_codepoint_bulk(std::false_type /*bulk*/, int& /*out*/) const noexcept
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief get codepoint from 4 hex characters following `\u`
|
@brief get codepoint from 4 hex characters following `\u`
|
||||||
|
|
||||||
@@ -276,14 +239,6 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
{
|
{
|
||||||
// this function only makes sense after reading `\u`
|
// this function only makes sense after reading `\u`
|
||||||
JSON_ASSERT(current == 'u');
|
JSON_ASSERT(current == 'u');
|
||||||
|
|
||||||
// contiguous input: decode all 4 hex digits directly from the buffer
|
|
||||||
int fast_codepoint = 0;
|
|
||||||
if (get_codepoint_bulk(std::integral_constant<bool, bulk_scan> {}, fast_codepoint))
|
|
||||||
{
|
|
||||||
return fast_codepoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
int codepoint = 0;
|
int codepoint = 0;
|
||||||
|
|
||||||
const auto factors = { 12u, 8u, 4u, 0u };
|
const auto factors = { 12u, 8u, 4u, 0u };
|
||||||
@@ -483,8 +438,16 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF)
|
if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF)
|
||||||
{
|
{
|
||||||
// expect next \uxxxx entry
|
// expect next \uxxxx entry
|
||||||
if (JSON_HEDLEY_LIKELY(get() == '\\' && get() == 'u'))
|
if (JSON_HEDLEY_LIKELY(get() == '\\'))
|
||||||
{
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(get() != 'u'))
|
||||||
|
{
|
||||||
|
// current is the character escaped by the backslash
|
||||||
|
error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF";
|
||||||
|
string_error_resume = resume_kind::escaped_character;
|
||||||
|
return token_type::parse_error;
|
||||||
|
}
|
||||||
|
|
||||||
const int codepoint2 = get_codepoint();
|
const int codepoint2 = get_codepoint();
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1))
|
if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1))
|
||||||
@@ -509,7 +472,11 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// the second escape was read completely and is a
|
||||||
|
// code point of its own
|
||||||
error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF";
|
error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF";
|
||||||
|
string_error_resume = resume_kind::after_escape;
|
||||||
|
string_error_codepoint = codepoint2;
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -523,7 +490,9 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF))
|
if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF))
|
||||||
{
|
{
|
||||||
|
// the escape was read completely
|
||||||
error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF";
|
error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF";
|
||||||
|
string_error_resume = resume_kind::after_escape;
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1105,11 +1074,9 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
token_type::parse_error otherwise
|
token_type::parse_error otherwise
|
||||||
|
|
||||||
@note The scanner is independent of the current locale: token_buffer
|
@note The scanner is independent of the current locale: token_buffer
|
||||||
always holds `.`. The conversion of float and double does not use
|
always holds `.`. Only the std::strtod fallback of convert_number()
|
||||||
the locale either. Only the std::strtold fallback of
|
depends on the locale, and it looks up the decimal point right
|
||||||
convert_number() for long double formats other than binary64
|
before converting (see detail::convert_float_locale_aware()).
|
||||||
depends on it, and it looks up the decimal point right before
|
|
||||||
converting (see detail::convert_float_locale_aware()).
|
|
||||||
*/
|
*/
|
||||||
token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated.
|
token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated.
|
||||||
{
|
{
|
||||||
@@ -1122,7 +1089,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
|
|
||||||
// offset just past the last mantissa byte in token_buffer (i.e. the
|
// offset just past the last mantissa byte in token_buffer (i.e. the
|
||||||
// index of 'e'/'E', or the whole token when there is no exponent).
|
// index of 'e'/'E', or the whole token when there is no exponent).
|
||||||
// convert_number() uses it to split the token; npos means
|
// convert_number() uses it to count significant digits; npos means
|
||||||
// "not seen an exponent yet" and is resolved at scan_number_done
|
// "not seen an exponent yet" and is resolved at scan_number_done
|
||||||
std::size_t mantissa_end = std::string::npos;
|
std::size_t mantissa_end = std::string::npos;
|
||||||
|
|
||||||
@@ -1452,8 +1419,8 @@ scan_number_done:
|
|||||||
@param[in] mantissa_end offset just past the last mantissa byte in
|
@param[in] mantissa_end offset just past the last mantissa byte in
|
||||||
token_buffer (the index of 'e'/'E', or
|
token_buffer (the index of 'e'/'E', or
|
||||||
token_buffer.size() when there is no exponent);
|
token_buffer.size() when there is no exponent);
|
||||||
with decimal_point_position, it locates the parts
|
used to skip Clinger's fast path when it cannot
|
||||||
of a float token without scanning it again
|
possibly succeed - see detail::mantissa_fits_clinger()
|
||||||
*/
|
*/
|
||||||
token_type convert_number(token_type number_type, std::size_t mantissa_end)
|
token_type convert_number(token_type number_type, std::size_t mantissa_end)
|
||||||
{
|
{
|
||||||
@@ -1522,11 +1489,10 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this code is reached if we parse a floating-point number or if an
|
// this code is reached if we parse a floating-point number or if an
|
||||||
// integer conversion above overflowed. float and double (and long
|
// integer conversion above overflowed. Prefer std::from_chars
|
||||||
// double where it is binary64) are converted by the library itself,
|
// (Eisel-Lemire, locale-independent, correctly rounded) when available;
|
||||||
// correctly rounded and independent of the locale; other long double
|
// otherwise the exact Clinger fast path (double only); otherwise the
|
||||||
// formats use std::from_chars when available, otherwise the
|
// locale-aware strtof/strtod/strtold.
|
||||||
// locale-aware strtold.
|
|
||||||
if (convert_float_fast(num_begin, num_end, decimal_point_position, mantissa_end, value_float))
|
if (convert_float_fast(num_begin, num_end, decimal_point_position, mantissa_end, value_float))
|
||||||
{
|
{
|
||||||
return token_type::value_float;
|
return token_type::value_float;
|
||||||
@@ -2179,6 +2145,573 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/////////////////////
|
||||||
|
// error recovery
|
||||||
|
/////////////////////
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief make the best of the token that scan() rejected
|
||||||
|
|
||||||
|
Called by the parser after scan() returned token_type::parse_error and the
|
||||||
|
SAX parser asked to recover from the error (see #3989). Keeps what can be
|
||||||
|
read of the token and skips the rest:
|
||||||
|
|
||||||
|
- A string keeps its characters. An unknown escape stands for the escaped
|
||||||
|
character itself (as in JavaScript), an invalid `\u` escape and ill-formed
|
||||||
|
UTF-8 become U+FFFD, and a control character is kept. A line break or the
|
||||||
|
end of the input ends a string that lacks its closing quote.
|
||||||
|
- A number keeps its longest valid prefix, e.g. `1` for `1.` or `1e+`.
|
||||||
|
- A block comment that is not closed runs to the end of the input.
|
||||||
|
- Anything else is skipped.
|
||||||
|
|
||||||
|
The rest of an invalid token is skipped up to the next delimiter
|
||||||
|
(whitespace, a structural character, or a quote). A delimiter that the
|
||||||
|
invalid token consumed is returned to the input, so that the next scan()
|
||||||
|
reads it.
|
||||||
|
|
||||||
|
@return token_type::value_string or a number token type if a string or a
|
||||||
|
number could be read, token_type::end_of_input for a block comment
|
||||||
|
that is not closed, token_type::uninitialized otherwise
|
||||||
|
*/
|
||||||
|
token_type recover_token()
|
||||||
|
{
|
||||||
|
const resume_kind resume = string_error_resume;
|
||||||
|
const int codepoint = string_error_codepoint;
|
||||||
|
string_error_resume = resume_kind::character;
|
||||||
|
string_error_codepoint = -1;
|
||||||
|
|
||||||
|
if (error_message_starts_with("invalid string"))
|
||||||
|
{
|
||||||
|
return recover_string(resume, codepoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error_message_starts_with("invalid number"))
|
||||||
|
{
|
||||||
|
return recover_number();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error_message_starts_with("invalid comment; missing"))
|
||||||
|
{
|
||||||
|
// the comment runs to the end of the input
|
||||||
|
return token_type::end_of_input;
|
||||||
|
}
|
||||||
|
|
||||||
|
skip_to_delimiter();
|
||||||
|
return token_type::uninitialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief return the token that scan() read last to the input, so that the
|
||||||
|
next scan() reads it again
|
||||||
|
|
||||||
|
Called by the parser when recovering from an error. The token must be a
|
||||||
|
single character (',', ':', '[', ']', '{', or '}') or the end of the
|
||||||
|
input, and scan() must have read it last.
|
||||||
|
*/
|
||||||
|
void unget_token()
|
||||||
|
{
|
||||||
|
JSON_ASSERT(!next_unget);
|
||||||
|
unget();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief let the token string for the next error begin at the current character
|
||||||
|
|
||||||
|
The token string of an error reaches back to the beginning of the last
|
||||||
|
string or number. After an error, the parser calls this function so that
|
||||||
|
the next error does not report (and, with many errors, copy) everything
|
||||||
|
read since then.
|
||||||
|
*/
|
||||||
|
void restart_token_string()
|
||||||
|
{
|
||||||
|
restart_token_string_impl(std::integral_constant<bool, lazy_token_string> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// how recover_string() continues after the error scan_string() reported
|
||||||
|
enum class resume_kind : std::uint8_t
|
||||||
|
{
|
||||||
|
/// current is the next character of the string (or the end of input)
|
||||||
|
character,
|
||||||
|
/// current is the character escaped by the preceding backslash
|
||||||
|
escaped_character,
|
||||||
|
/// current is the last character of a complete escape
|
||||||
|
after_escape
|
||||||
|
};
|
||||||
|
|
||||||
|
/// whether error_message begins with @a prefix
|
||||||
|
bool error_message_starts_with(const char* prefix) const noexcept
|
||||||
|
{
|
||||||
|
const char* message = error_message;
|
||||||
|
while (*prefix != '\0')
|
||||||
|
{
|
||||||
|
if (*message++ != *prefix++)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// whether current ends an invalid token (see recover_token())
|
||||||
|
bool current_is_delimiter() const noexcept
|
||||||
|
{
|
||||||
|
switch (current)
|
||||||
|
{
|
||||||
|
case ' ':
|
||||||
|
case '\t':
|
||||||
|
case '\n':
|
||||||
|
case '\r':
|
||||||
|
case '[':
|
||||||
|
case ']':
|
||||||
|
case '{':
|
||||||
|
case '}':
|
||||||
|
case ',':
|
||||||
|
case ':':
|
||||||
|
case '\"':
|
||||||
|
#if !JSON_STRICT_NUL_HANDLING
|
||||||
|
case '\0':
|
||||||
|
#endif
|
||||||
|
case char_traits<char_type>::eof():
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case '/':
|
||||||
|
return ignore_comments;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// skip the rest of an invalid token and return its delimiter to the input
|
||||||
|
void skip_to_delimiter()
|
||||||
|
{
|
||||||
|
while (!current_is_delimiter())
|
||||||
|
{
|
||||||
|
get();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current != char_traits<char_type>::eof())
|
||||||
|
{
|
||||||
|
unget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// append U+FFFD REPLACEMENT CHARACTER to token_buffer
|
||||||
|
void add_replacement_character()
|
||||||
|
{
|
||||||
|
add(0xEF);
|
||||||
|
add(0xBF);
|
||||||
|
add(0xBD);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// append the UTF-8 encoding of @a codepoint (not a surrogate) to token_buffer
|
||||||
|
void add_codepoint(const int codepoint)
|
||||||
|
{
|
||||||
|
JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF);
|
||||||
|
const auto cp = static_cast<unsigned int>(codepoint);
|
||||||
|
if (cp < 0x80)
|
||||||
|
{
|
||||||
|
add(static_cast<char_int_type>(cp));
|
||||||
|
}
|
||||||
|
else if (cp <= 0x7FF)
|
||||||
|
{
|
||||||
|
add(static_cast<char_int_type>(0xC0u | (cp >> 6u)));
|
||||||
|
add(static_cast<char_int_type>(0x80u | (cp & 0x3Fu)));
|
||||||
|
}
|
||||||
|
else if (cp <= 0xFFFF)
|
||||||
|
{
|
||||||
|
add(static_cast<char_int_type>(0xE0u | (cp >> 12u)));
|
||||||
|
add(static_cast<char_int_type>(0x80u | ((cp >> 6u) & 0x3Fu)));
|
||||||
|
add(static_cast<char_int_type>(0x80u | (cp & 0x3Fu)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
add(static_cast<char_int_type>(0xF0u | (cp >> 18u)));
|
||||||
|
add(static_cast<char_int_type>(0x80u | ((cp >> 12u) & 0x3Fu)));
|
||||||
|
add(static_cast<char_int_type>(0x80u | ((cp >> 6u) & 0x3Fu)));
|
||||||
|
add(static_cast<char_int_type>(0x80u | (cp & 0x3Fu)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// append a code point read from a `\u` escape; a surrogate becomes U+FFFD
|
||||||
|
void add_escaped_codepoint(const int codepoint)
|
||||||
|
{
|
||||||
|
if (0xD800 <= codepoint && codepoint <= 0xDFFF)
|
||||||
|
{
|
||||||
|
add_replacement_character();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
add_codepoint(codepoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief remove an incomplete UTF-8 sequence from the end of token_buffer
|
||||||
|
|
||||||
|
next_byte_in_range() adds the bytes of a sequence as it checks them, so
|
||||||
|
when it rejects a byte, the beginning of the sequence is already in
|
||||||
|
token_buffer, which otherwise holds only complete sequences.
|
||||||
|
|
||||||
|
@return whether an incomplete sequence was removed
|
||||||
|
*/
|
||||||
|
bool remove_incomplete_utf8_sequence()
|
||||||
|
{
|
||||||
|
std::size_t lead = token_buffer.size();
|
||||||
|
std::size_t continuation_bytes = 0;
|
||||||
|
while (lead > 0 && continuation_bytes < 3
|
||||||
|
&& (static_cast<unsigned char>(token_buffer[lead - 1]) & 0xC0u) == 0x80u)
|
||||||
|
{
|
||||||
|
--lead;
|
||||||
|
++continuation_bytes;
|
||||||
|
}
|
||||||
|
if (lead == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto lead_byte = static_cast<unsigned char>(token_buffer[lead - 1]);
|
||||||
|
std::size_t expected = 0;
|
||||||
|
if (lead_byte >= 0xF0)
|
||||||
|
{
|
||||||
|
expected = 3;
|
||||||
|
}
|
||||||
|
else if (lead_byte >= 0xE0)
|
||||||
|
{
|
||||||
|
expected = 2;
|
||||||
|
}
|
||||||
|
else if (lead_byte >= 0xC0)
|
||||||
|
{
|
||||||
|
expected = 1;
|
||||||
|
}
|
||||||
|
if (continuation_bytes >= expected)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
token_buffer.resize(lead - 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read the UTF-8 sequence that begins with current, which is not ASCII
|
||||||
|
@return whether the next character must be read; false if current still
|
||||||
|
needs to be handled, because it does not belong to the sequence
|
||||||
|
*/
|
||||||
|
bool recover_utf8_sequence()
|
||||||
|
{
|
||||||
|
// the number of continuation bytes and the range of the first one;
|
||||||
|
// see the ranges in scan_string()
|
||||||
|
std::size_t count = 0;
|
||||||
|
char_int_type low = 0x80;
|
||||||
|
char_int_type high = 0xBF;
|
||||||
|
if (current >= 0xC2 && current <= 0xDF)
|
||||||
|
{
|
||||||
|
count = 1;
|
||||||
|
}
|
||||||
|
else if (current >= 0xE0 && current <= 0xEF)
|
||||||
|
{
|
||||||
|
count = 2;
|
||||||
|
low = (current == 0xE0) ? 0xA0 : 0x80;
|
||||||
|
high = (current == 0xED) ? 0x9F : 0xBF;
|
||||||
|
}
|
||||||
|
else if (current >= 0xF0 && current <= 0xF4)
|
||||||
|
{
|
||||||
|
count = 3;
|
||||||
|
low = (current == 0xF0) ? 0x90 : 0x80;
|
||||||
|
high = (current == 0xF4) ? 0x8F : 0xBF;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// an ill-formed byte
|
||||||
|
add_replacement_character();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t start = token_buffer.size();
|
||||||
|
add(current);
|
||||||
|
for (std::size_t i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
get();
|
||||||
|
if (current < low || current > high)
|
||||||
|
{
|
||||||
|
token_buffer.resize(start);
|
||||||
|
add_replacement_character();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
add(current);
|
||||||
|
low = 0x80;
|
||||||
|
high = 0xBF;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read the low surrogate that must follow the high surrogate @a high
|
||||||
|
@return whether the next character must be read; false if current still
|
||||||
|
needs to be handled
|
||||||
|
*/
|
||||||
|
bool recover_low_surrogate(int high)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (get() != '\\')
|
||||||
|
{
|
||||||
|
add_replacement_character();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (get() != 'u')
|
||||||
|
{
|
||||||
|
add_replacement_character();
|
||||||
|
// not 'u', so this does not come back here
|
||||||
|
return recover_escape();
|
||||||
|
}
|
||||||
|
|
||||||
|
const int low = get_codepoint();
|
||||||
|
if (low == -1)
|
||||||
|
{
|
||||||
|
add_replacement_character();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (0xDC00 <= low && low <= 0xDFFF)
|
||||||
|
{
|
||||||
|
add_codepoint(static_cast<int>((static_cast<unsigned int>(high) << 10u)
|
||||||
|
+ static_cast<unsigned int>(low) - 0x35FDC00u));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// high has no low surrogate
|
||||||
|
add_replacement_character();
|
||||||
|
if (low < 0xD800 || low > 0xDBFF)
|
||||||
|
{
|
||||||
|
add_codepoint(low);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// another high surrogate
|
||||||
|
high = low;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read the escape whose backslash was read; current is the escaped character
|
||||||
|
@return whether the next character must be read; false if current still
|
||||||
|
needs to be handled
|
||||||
|
*/
|
||||||
|
bool recover_escape()
|
||||||
|
{
|
||||||
|
switch (current)
|
||||||
|
{
|
||||||
|
case '\"':
|
||||||
|
add('\"');
|
||||||
|
return true;
|
||||||
|
case '\\':
|
||||||
|
add('\\');
|
||||||
|
return true;
|
||||||
|
case '/':
|
||||||
|
add('/');
|
||||||
|
return true;
|
||||||
|
case 'b':
|
||||||
|
add('\b');
|
||||||
|
return true;
|
||||||
|
case 'f':
|
||||||
|
add('\f');
|
||||||
|
return true;
|
||||||
|
case 'n':
|
||||||
|
add('\n');
|
||||||
|
return true;
|
||||||
|
case 'r':
|
||||||
|
add('\r');
|
||||||
|
return true;
|
||||||
|
case 't':
|
||||||
|
add('\t');
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 'u':
|
||||||
|
{
|
||||||
|
const int codepoint = get_codepoint();
|
||||||
|
if (codepoint == -1)
|
||||||
|
{
|
||||||
|
add_replacement_character();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (0xD800 <= codepoint && codepoint <= 0xDBFF)
|
||||||
|
{
|
||||||
|
return recover_low_surrogate(codepoint);
|
||||||
|
}
|
||||||
|
add_escaped_codepoint(codepoint);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// an unknown escape stands for the escaped character
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read the rest of a string after scan_string() rejected it
|
||||||
|
|
||||||
|
token_buffer holds what scan_string() read before the error. See
|
||||||
|
recover_token() for how errors are repaired.
|
||||||
|
|
||||||
|
@param[in] resume how to continue, see resume_kind
|
||||||
|
@param[in] codepoint for a high surrogate followed by an escape of another
|
||||||
|
code point: that code point; -1 otherwise
|
||||||
|
*/
|
||||||
|
token_type recover_string(const resume_kind resume, const int codepoint)
|
||||||
|
{
|
||||||
|
// whether the next character must be read before it can be handled
|
||||||
|
bool fetch = false;
|
||||||
|
|
||||||
|
if (error_message_starts_with("invalid string: surrogate")
|
||||||
|
|| error_message_starts_with("invalid string: '\\u'")
|
||||||
|
|| (error_message_starts_with("invalid string: ill-formed UTF-8")
|
||||||
|
&& remove_incomplete_utf8_sequence()))
|
||||||
|
{
|
||||||
|
add_replacement_character();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (resume)
|
||||||
|
{
|
||||||
|
case resume_kind::escaped_character:
|
||||||
|
fetch = recover_escape();
|
||||||
|
break;
|
||||||
|
case resume_kind::after_escape:
|
||||||
|
if (0xD800 <= codepoint && codepoint <= 0xDBFF)
|
||||||
|
{
|
||||||
|
fetch = recover_low_surrogate(codepoint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (codepoint != -1)
|
||||||
|
{
|
||||||
|
add_escaped_codepoint(codepoint);
|
||||||
|
}
|
||||||
|
fetch = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case resume_kind::character:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (fetch)
|
||||||
|
{
|
||||||
|
get();
|
||||||
|
}
|
||||||
|
fetch = true;
|
||||||
|
|
||||||
|
switch (current)
|
||||||
|
{
|
||||||
|
case '\"':
|
||||||
|
// a line break or the end of the input ends a string that
|
||||||
|
// lacks its closing quote
|
||||||
|
case '\n':
|
||||||
|
case '\r':
|
||||||
|
case char_traits<char_type>::eof():
|
||||||
|
return token_type::value_string;
|
||||||
|
|
||||||
|
#if !JSON_STRICT_NUL_HANDLING
|
||||||
|
case '\0':
|
||||||
|
// the end of the input, see scan()
|
||||||
|
unget();
|
||||||
|
return token_type::value_string;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case '\\':
|
||||||
|
get();
|
||||||
|
fetch = recover_escape();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (current < 0x80)
|
||||||
|
{
|
||||||
|
// including control characters
|
||||||
|
add(current);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fetch = recover_utf8_sequence();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief keep the longest valid prefix of a number that scan_number() rejected
|
||||||
|
|
||||||
|
token_buffer holds the characters scan_number() accepted before the error,
|
||||||
|
so the prefix ends at its last digit.
|
||||||
|
*/
|
||||||
|
token_type recover_number()
|
||||||
|
{
|
||||||
|
// only size(), operator[], and resize() are used, which every string
|
||||||
|
// type the library supports provides
|
||||||
|
std::size_t length = token_buffer.size();
|
||||||
|
while (length != 0 && (token_buffer[length - 1] < '0' || token_buffer[length - 1] > '9'))
|
||||||
|
{
|
||||||
|
--length;
|
||||||
|
}
|
||||||
|
token_buffer.resize(length);
|
||||||
|
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
skip_to_delimiter();
|
||||||
|
return token_type::uninitialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decimal_point_position >= length)
|
||||||
|
{
|
||||||
|
decimal_point_position = std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t exponent = std::string::npos;
|
||||||
|
for (std::size_t i = 0; i < length; ++i)
|
||||||
|
{
|
||||||
|
if (token_buffer[i] == 'e' || token_buffer[i] == 'E')
|
||||||
|
{
|
||||||
|
exponent = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const std::size_t mantissa_end = (exponent == std::string::npos) ? length : exponent;
|
||||||
|
token_type number_type = token_type::value_unsigned;
|
||||||
|
if (decimal_point_position != std::string::npos || exponent != std::string::npos)
|
||||||
|
{
|
||||||
|
number_type = token_type::value_float;
|
||||||
|
}
|
||||||
|
else if (token_buffer[0] == '-')
|
||||||
|
{
|
||||||
|
number_type = token_type::value_integer;
|
||||||
|
}
|
||||||
|
|
||||||
|
const token_type result = convert_number(number_type, mantissa_end);
|
||||||
|
skip_to_delimiter();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// seekable adapter: the token string begins at current, which was consumed
|
||||||
|
void restart_token_string_impl(std::true_type /*lazy*/) noexcept
|
||||||
|
{
|
||||||
|
const std::size_t consumed = ia.get_consumed_count();
|
||||||
|
token_string_start = (consumed > 0 && current != char_traits<char_type>::eof()) ? consumed - 1 : consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// streaming adapter: the token string begins at current; a character
|
||||||
|
/// that was put back is copied again when it is read again
|
||||||
|
void restart_token_string_impl(std::false_type /*lazy*/)
|
||||||
|
{
|
||||||
|
token_string.clear();
|
||||||
|
if (!next_unget && current != char_traits<char_type>::eof())
|
||||||
|
{
|
||||||
|
token_string.push_back(char_traits<char_type>::to_char_type(current));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// input adapter
|
/// input adapter
|
||||||
InputAdapterType ia;
|
InputAdapterType ia;
|
||||||
@@ -2219,6 +2752,13 @@ scan_number_done:
|
|||||||
/// a description of occurred lexer errors
|
/// a description of occurred lexer errors
|
||||||
const char* error_message = "";
|
const char* error_message = "";
|
||||||
|
|
||||||
|
/// how recover_token() continues a string that scan_string() rejected;
|
||||||
|
/// set only on the error paths that need more than error_message
|
||||||
|
resume_kind string_error_resume = resume_kind::character;
|
||||||
|
/// the code point of the second escape when a high surrogate is followed
|
||||||
|
/// by an escape that is not a low surrogate; -1 otherwise
|
||||||
|
int string_error_codepoint = -1;
|
||||||
|
|
||||||
// number values
|
// number values
|
||||||
number_integer_t value_integer = 0;
|
number_integer_t value_integer = 0;
|
||||||
number_unsigned_t value_unsigned = 0;
|
number_unsigned_t value_unsigned = 0;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -98,7 +98,7 @@ class parser
|
|||||||
if (callback)
|
if (callback)
|
||||||
{
|
{
|
||||||
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
|
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
|
||||||
sax_parse_internal(&sdp);
|
sax_parse_internal<false>(&sdp);
|
||||||
|
|
||||||
if (strict)
|
if (strict)
|
||||||
{
|
{
|
||||||
@@ -135,7 +135,7 @@ class parser
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
||||||
sax_parse_internal(&sdp);
|
sax_parse_internal<false>(&sdp);
|
||||||
|
|
||||||
if (strict)
|
if (strict)
|
||||||
{
|
{
|
||||||
@@ -173,26 +173,59 @@ class parser
|
|||||||
bool accept(const bool strict = true)
|
bool accept(const bool strict = true)
|
||||||
{
|
{
|
||||||
json_sax_acceptor<BasicJsonType> sax_acceptor;
|
json_sax_acceptor<BasicJsonType> sax_acceptor;
|
||||||
return sax_parse(&sax_acceptor, strict);
|
return sax_parse_impl<false>(&sax_acceptor, strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief public SAX interface
|
||||||
|
|
||||||
|
If the SAX parser's parse_error() returns true, the parser recovers from
|
||||||
|
the error: it repairs the input and continues (see #3989).
|
||||||
|
|
||||||
|
@param[in] sax the SAX parser
|
||||||
|
@param[in] strict whether to expect the last token to be EOF
|
||||||
|
@return whether the input was parsed without errors and no SAX event
|
||||||
|
returned false
|
||||||
|
*/
|
||||||
template<typename SAX>
|
template<typename SAX>
|
||||||
JSON_HEDLEY_NON_NULL(2)
|
JSON_HEDLEY_NON_NULL(2)
|
||||||
bool sax_parse(SAX* sax, const bool strict = true)
|
bool sax_parse(SAX* sax, const bool strict = true)
|
||||||
|
{
|
||||||
|
return sax_parse_impl<true>(sax, strict);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// what sax_parse_internal() does after an object key was expected
|
||||||
|
enum class next_step : std::uint8_t
|
||||||
|
{
|
||||||
|
/// stop parsing
|
||||||
|
stop,
|
||||||
|
/// parse a value that begins with last_token
|
||||||
|
parse_value,
|
||||||
|
/// evaluate the state of the innermost container, which reads
|
||||||
|
/// last_token again
|
||||||
|
evaluate_state
|
||||||
|
};
|
||||||
|
|
||||||
|
template<bool AllowRecovery, typename SAX>
|
||||||
|
JSON_HEDLEY_NON_NULL(2)
|
||||||
|
bool sax_parse_impl(SAX* sax, const bool strict)
|
||||||
{
|
{
|
||||||
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
||||||
const bool result = sax_parse_internal(sax);
|
const bool result = sax_parse_internal<AllowRecovery>(sax);
|
||||||
|
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
if (strict)
|
if (strict)
|
||||||
{
|
{
|
||||||
// strict mode: next byte must be EOF
|
// strict mode: next byte must be EOF; after recovering from an
|
||||||
if (get_token() != token_type::end_of_input)
|
// error, the end of the input may already have been read
|
||||||
|
if (last_token != token_type::end_of_input && get_token() != token_type::end_of_input)
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
// the value is complete, so there is nothing to recover
|
||||||
m_lexer.get_token_string(),
|
static_cast<void>(report_error(sax, parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr),
|
||||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr));
|
std::integral_constant<bool, AllowRecovery> {}));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -203,14 +236,23 @@ class parser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result && !error_reported;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
/*!
|
||||||
template<typename SAX>
|
@brief parse a JSON value and pass it to a SAX parser
|
||||||
|
|
||||||
|
@tparam AllowRecovery whether to recover from an error if the SAX parser's
|
||||||
|
parse_error() returns true; false for the SAX parsers
|
||||||
|
of parse() and accept(), which never do, so that no
|
||||||
|
code for recovering is generated for them
|
||||||
|
*/
|
||||||
|
template<bool AllowRecovery, typename SAX>
|
||||||
JSON_HEDLEY_NON_NULL(2)
|
JSON_HEDLEY_NON_NULL(2)
|
||||||
bool sax_parse_internal(SAX* sax)
|
bool sax_parse_internal(SAX* sax)
|
||||||
{
|
{
|
||||||
|
const std::integral_constant<bool, AllowRecovery> allow_recovery{};
|
||||||
|
|
||||||
// stack to remember the hierarchy of structured values we are parsing
|
// stack to remember the hierarchy of structured values we are parsing
|
||||||
// true = array; false = object
|
// true = array; false = object
|
||||||
std::vector<bool> states;
|
std::vector<bool> states;
|
||||||
@@ -241,12 +283,18 @@ class parser
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse key
|
// remember we are now inside an object
|
||||||
|
states.push_back(false);
|
||||||
|
|
||||||
|
// parse key (the steps of parse_key(), which are
|
||||||
|
// repeated here and below for speed)
|
||||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
if (!continue_after(key_error(sax, allow_recovery, false), skip_to_state_evaluation))
|
||||||
m_lexer.get_token_string(),
|
{
|
||||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr));
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
||||||
{
|
{
|
||||||
@@ -256,14 +304,13 @@ class parser
|
|||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
if (!continue_after(key_error(sax, allow_recovery, true), skip_to_state_evaluation))
|
||||||
m_lexer.get_token_string(),
|
{
|
||||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr));
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remember we are now inside an object
|
|
||||||
states.push_back(false);
|
|
||||||
|
|
||||||
// parse values
|
// parse values
|
||||||
get_token();
|
get_token();
|
||||||
continue;
|
continue;
|
||||||
@@ -299,9 +346,11 @@ class parser
|
|||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res)))
|
if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res)))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
if (!overflow_error(sax, res, allow_recovery))
|
||||||
m_lexer.get_token_string(),
|
{
|
||||||
out_of_range::create(406, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string())))
|
||||||
@@ -369,23 +418,63 @@ class parser
|
|||||||
case token_type::parse_error:
|
case token_type::parse_error:
|
||||||
{
|
{
|
||||||
// using "uninitialized" to avoid an "expected" message
|
// using "uninitialized" to avoid an "expected" message
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
if (!report_error(sax, parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), nullptr), allow_recovery))
|
||||||
m_lexer.get_token_string(),
|
{
|
||||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), nullptr));
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// recover: keep what can be read of the token
|
||||||
|
recover_token();
|
||||||
|
if (last_token != token_type::uninitialized)
|
||||||
|
{
|
||||||
|
// a string or a number
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (states.empty())
|
||||||
|
{
|
||||||
|
// look for the value after the garbage
|
||||||
|
if (!skip_to_value())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// nothing could be read
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!sax->null()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case token_type::end_of_input:
|
case token_type::end_of_input:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_position().chars_read_total == 1))
|
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_position().chars_read_total == 1))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
// there is nothing to recover
|
||||||
m_lexer.get_token_string(),
|
static_cast<void>(report_error(sax, parse_error::create(101, m_lexer.get_position(),
|
||||||
parse_error::create(101, m_lexer.get_position(),
|
"attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr), allow_recovery));
|
||||||
"attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
if (!report_error(sax, parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr), allow_recovery))
|
||||||
m_lexer.get_token_string(),
|
{
|
||||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr));
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// recover: the input ends where a value is missing
|
||||||
|
if (states.empty())
|
||||||
|
{
|
||||||
|
// there is no value
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!recover_missing_value(sax, states))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// the state evaluation reads the token again
|
||||||
|
m_lexer.unget_token();
|
||||||
|
skip_to_state_evaluation = true;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
case token_type::uninitialized:
|
case token_type::uninitialized:
|
||||||
case token_type::end_array:
|
case token_type::end_array:
|
||||||
@@ -395,9 +484,35 @@ class parser
|
|||||||
case token_type::literal_or_value:
|
case token_type::literal_or_value:
|
||||||
default: // the last token was unexpected
|
default: // the last token was unexpected
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
if (!report_error(sax, parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr), allow_recovery))
|
||||||
m_lexer.get_token_string(),
|
{
|
||||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr));
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// recover
|
||||||
|
if (states.empty())
|
||||||
|
{
|
||||||
|
// look for the value after the garbage
|
||||||
|
if (!skip_to_value())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (last_token == token_type::name_separator)
|
||||||
|
{
|
||||||
|
// a stray ':'; the value may follow
|
||||||
|
get_token();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!recover_missing_value(sax, states))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// the state evaluation reads the token again
|
||||||
|
m_lexer.unget_token();
|
||||||
|
skip_to_state_evaluation = true;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -447,9 +562,30 @@ class parser
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
if (!report_error(sax, parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, "array"), nullptr), allow_recovery))
|
||||||
m_lexer.get_token_string(),
|
{
|
||||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, "array"), nullptr));
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// recover
|
||||||
|
if (last_token == token_type::end_of_input)
|
||||||
|
{
|
||||||
|
// the input ends inside the array
|
||||||
|
return close_containers(sax, states);
|
||||||
|
}
|
||||||
|
if (last_token == token_type::end_object)
|
||||||
|
{
|
||||||
|
// a wrong closing bracket closes the innermost container
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!sax->end_array()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
states.pop_back();
|
||||||
|
skip_to_state_evaluation = true;
|
||||||
|
}
|
||||||
|
// otherwise, a missing ',' (or a stray ':', which value
|
||||||
|
// parsing drops): the next value begins here
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// states.back() is false -> object
|
// states.back() is false -> object
|
||||||
@@ -466,11 +602,12 @@ class parser
|
|||||||
// parse key
|
// parse key
|
||||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
if (!continue_after(key_error(sax, allow_recovery, false), skip_to_state_evaluation))
|
||||||
m_lexer.get_token_string(),
|
{
|
||||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr));
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -479,9 +616,11 @@ class parser
|
|||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
if (!continue_after(key_error(sax, allow_recovery, true), skip_to_state_evaluation))
|
||||||
m_lexer.get_token_string(),
|
{
|
||||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr));
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse values
|
// parse values
|
||||||
@@ -508,12 +647,479 @@ class parser
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
if (!report_error(sax, parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, "object"), nullptr), allow_recovery))
|
||||||
m_lexer.get_token_string(),
|
{
|
||||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, "object"), nullptr));
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// recover
|
||||||
|
if (last_token == token_type::end_of_input)
|
||||||
|
{
|
||||||
|
// the input ends inside the object
|
||||||
|
return close_containers(sax, states);
|
||||||
|
}
|
||||||
|
if (last_token == token_type::end_array)
|
||||||
|
{
|
||||||
|
// a wrong closing bracket closes the innermost container
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!sax->end_object()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
states.pop_back();
|
||||||
|
skip_to_state_evaluation = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!continue_after(recover_member(sax, allow_recovery), skip_to_state_evaluation))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief continue sax_parse_internal() after a recovery
|
||||||
|
@return whether to continue parsing
|
||||||
|
*/
|
||||||
|
bool continue_after(const next_step step, bool& skip_to_state_evaluation)
|
||||||
|
{
|
||||||
|
if (step == next_step::evaluate_state)
|
||||||
|
{
|
||||||
|
// the state evaluation reads the token again
|
||||||
|
m_lexer.unget_token();
|
||||||
|
skip_to_state_evaluation = true;
|
||||||
|
}
|
||||||
|
return step != next_step::stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the parser for parse() and accept() never recovers: stop parsing
|
||||||
|
static std::false_type continue_after(std::false_type /*step*/, bool& /*skip_to_state_evaluation*/) noexcept
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief parse an object key and the name separator (:) after it
|
||||||
|
|
||||||
|
last_token is the token where the key is expected. sax_parse_internal()
|
||||||
|
repeats these steps rather than calling this function, which is used
|
||||||
|
when recovering from an error.
|
||||||
|
|
||||||
|
@return next_step::parse_value if the value follows, with last_token its
|
||||||
|
first token; next_step::evaluate_state if the object's state is
|
||||||
|
to be evaluated after recovering from an error; next_step::stop
|
||||||
|
to stop parsing
|
||||||
|
*/
|
||||||
|
template<typename SAX>
|
||||||
|
next_step parse_key(SAX* sax)
|
||||||
|
{
|
||||||
|
const std::true_type allow_recovery{};
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||||
|
{
|
||||||
|
return key_error(sax, allow_recovery, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
||||||
|
{
|
||||||
|
return next_step::stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse separator (:)
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||||
|
{
|
||||||
|
return key_error(sax, allow_recovery, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the value begins with the next token
|
||||||
|
get_token();
|
||||||
|
return next_step::parse_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief report a number that is too large for number_float_t, and recover
|
||||||
|
from the error by passing the value on; the SAX parser gets the
|
||||||
|
number's text as well
|
||||||
|
|
||||||
|
This is a separate function, as reading other numbers is measurably
|
||||||
|
slower if the error is handled where they are read.
|
||||||
|
|
||||||
|
@param[in] sax the SAX parser
|
||||||
|
@param[in] value the value that is not finite
|
||||||
|
@return whether to continue parsing
|
||||||
|
*/
|
||||||
|
template<typename SAX, typename AllowRecovery>
|
||||||
|
bool overflow_error(SAX* sax, const number_float_t value, AllowRecovery allow_recovery)
|
||||||
|
{
|
||||||
|
if (!report_error(sax, out_of_range::create(406, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr), allow_recovery))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return sax->number_float(value, m_lexer.get_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief report a missing key, or a missing name separator (:) after the
|
||||||
|
key; the parser for parse() and accept() never recovers
|
||||||
|
|
||||||
|
@param[in] key_read whether the key was read, so that the name separator
|
||||||
|
is missing
|
||||||
|
@return std::false_type, see report_error()
|
||||||
|
*/
|
||||||
|
template<typename SAX>
|
||||||
|
std::false_type key_error(SAX* sax, std::false_type allow_recovery, const bool key_read)
|
||||||
|
{
|
||||||
|
return report_error(sax, parse_error::create(101, m_lexer.get_position(), key_read
|
||||||
|
? exception_message(token_type::name_separator, "object separator")
|
||||||
|
: exception_message(token_type::value_string, "object key"), nullptr), allow_recovery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief report a missing key, or a missing name separator (:) after the
|
||||||
|
key, and recover from it
|
||||||
|
|
||||||
|
@param[in] key_read whether the key was read, so that the name separator
|
||||||
|
is missing
|
||||||
|
*/
|
||||||
|
template<typename SAX>
|
||||||
|
next_step key_error(SAX* sax, std::true_type allow_recovery, const bool key_read)
|
||||||
|
{
|
||||||
|
if (!key_read)
|
||||||
|
{
|
||||||
|
if (!report_error(sax, parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr), allow_recovery))
|
||||||
|
{
|
||||||
|
return next_step::stop;
|
||||||
|
}
|
||||||
|
return recover_key(sax);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!report_error(sax, parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr), allow_recovery))
|
||||||
|
{
|
||||||
|
return next_step::stop;
|
||||||
|
}
|
||||||
|
return recover_name_separator(sax);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////
|
||||||
|
// error recovery
|
||||||
|
/////////////////////
|
||||||
|
|
||||||
|
/*
|
||||||
|
The functions below repair an error after the SAX parser's parse_error()
|
||||||
|
returned true (see #3989). Each mistake is repaired by the smallest local
|
||||||
|
edit: a missing ',' or ':' is inserted, a stray token is removed, what can
|
||||||
|
be read of an invalid string or number is kept (see
|
||||||
|
lexer::recover_token()), a missing value becomes null, a wrong closing
|
||||||
|
bracket closes the innermost container, and the end of the input closes
|
||||||
|
all of them. The events stay balanced, and every key() is followed by
|
||||||
|
exactly one value.
|
||||||
|
|
||||||
|
A repair hands a token to the state evaluation, by returning it to the
|
||||||
|
lexer (lexer::unget_token()) so that the state evaluation reads it again,
|
||||||
|
only if it is ',', ']', '}', or the end of the input. The state evaluation
|
||||||
|
hands a token to value or key parsing only if it is none of them, so a
|
||||||
|
token is never handed back and forth. Every other step reads a token or
|
||||||
|
closes a container, so parsing always ends.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief report an error to the SAX parser; the parser for parse() and
|
||||||
|
accept() never recovers
|
||||||
|
|
||||||
|
@return std::false_type rather than false: its value is known where the
|
||||||
|
function is called even if the call is not inlined, so the code
|
||||||
|
for recovering is not generated
|
||||||
|
*/
|
||||||
|
template<typename SAX, typename Exception>
|
||||||
|
std::false_type report_error(SAX* sax, const Exception& ex, std::false_type /*allow_recovery*/)
|
||||||
|
{
|
||||||
|
error_reported = true;
|
||||||
|
static_cast<void>(sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), ex));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief report an error to the SAX parser
|
||||||
|
@return whether to recover from the error
|
||||||
|
*/
|
||||||
|
template<typename SAX, typename Exception>
|
||||||
|
bool report_error(SAX* sax, const Exception& ex, std::true_type /*allow_recovery*/)
|
||||||
|
{
|
||||||
|
const std::size_t position = m_lexer.get_position().chars_read_total;
|
||||||
|
if (error_reported && position == last_error_position && last_token == last_error_token)
|
||||||
|
{
|
||||||
|
// a repair handed on the token of the error it repaired; the
|
||||||
|
// token was reported already, and the SAX parser asked to recover
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_reported = true;
|
||||||
|
last_error_position = position;
|
||||||
|
last_error_token = last_token;
|
||||||
|
|
||||||
|
if (!sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), ex))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the token string of the next error begins here
|
||||||
|
m_lexer.restart_token_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief keep what can be read of the token that the lexer rejected
|
||||||
|
|
||||||
|
The error was reported for the rejected token, so it is not reported again
|
||||||
|
for the token it is repaired to (see lexer::recover_token()).
|
||||||
|
*/
|
||||||
|
token_type recover_token()
|
||||||
|
{
|
||||||
|
last_token = m_lexer.recover_token();
|
||||||
|
last_error_position = m_lexer.get_position().chars_read_total;
|
||||||
|
last_error_token = last_token;
|
||||||
|
return last_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// pass the end events of all open containers
|
||||||
|
template<typename SAX>
|
||||||
|
bool close_containers(SAX* sax, std::vector<bool>& states)
|
||||||
|
{
|
||||||
|
while (!states.empty())
|
||||||
|
{
|
||||||
|
const bool is_array = states.back();
|
||||||
|
states.pop_back();
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(is_array ? !sax->end_array() : !sax->end_object()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read tokens until one begins a value, skipping everything before
|
||||||
|
the top-level value
|
||||||
|
@return whether a value begins with last_token
|
||||||
|
*/
|
||||||
|
bool skip_to_value()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
switch (get_token())
|
||||||
|
{
|
||||||
|
case token_type::begin_array:
|
||||||
|
case token_type::begin_object:
|
||||||
|
case token_type::literal_false:
|
||||||
|
case token_type::literal_null:
|
||||||
|
case token_type::literal_true:
|
||||||
|
case token_type::value_float:
|
||||||
|
case token_type::value_integer:
|
||||||
|
case token_type::value_string:
|
||||||
|
case token_type::value_unsigned:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case token_type::end_of_input:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case token_type::parse_error:
|
||||||
|
recover_token();
|
||||||
|
if (last_token != token_type::uninitialized)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case token_type::uninitialized:
|
||||||
|
case token_type::end_array:
|
||||||
|
case token_type::end_object:
|
||||||
|
case token_type::name_separator:
|
||||||
|
case token_type::value_separator:
|
||||||
|
case token_type::literal_or_value:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief skip the rest of an object member that cannot be read
|
||||||
|
|
||||||
|
Reads tokens, beginning with last_token, until a ',', '}', or ']' that is
|
||||||
|
not inside a container that begins in the skipped tokens, or the end of
|
||||||
|
the input.
|
||||||
|
*/
|
||||||
|
void skip_member()
|
||||||
|
{
|
||||||
|
std::size_t depth = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
switch (last_token)
|
||||||
|
{
|
||||||
|
case token_type::begin_array:
|
||||||
|
case token_type::begin_object:
|
||||||
|
++depth;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case token_type::end_array:
|
||||||
|
case token_type::end_object:
|
||||||
|
if (depth == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
--depth;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case token_type::value_separator:
|
||||||
|
if (depth == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case token_type::end_of_input:
|
||||||
|
return;
|
||||||
|
|
||||||
|
case token_type::parse_error:
|
||||||
|
recover_token();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case token_type::uninitialized:
|
||||||
|
case token_type::literal_true:
|
||||||
|
case token_type::literal_false:
|
||||||
|
case token_type::literal_null:
|
||||||
|
case token_type::value_string:
|
||||||
|
case token_type::value_unsigned:
|
||||||
|
case token_type::value_integer:
|
||||||
|
case token_type::value_float:
|
||||||
|
case token_type::name_separator:
|
||||||
|
case token_type::literal_or_value:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
get_token();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief pass a value where it is missing
|
||||||
|
|
||||||
|
last_token is ',', ']', '}', or the end of the input, where a value was
|
||||||
|
expected. In an object, the key gets null; in an array, a ',' where a
|
||||||
|
value is missing stands for null (as in JavaScript), while an array that
|
||||||
|
ends there just ends.
|
||||||
|
*/
|
||||||
|
template<typename SAX>
|
||||||
|
bool recover_missing_value(SAX* sax, const std::vector<bool>& states)
|
||||||
|
{
|
||||||
|
JSON_ASSERT(!states.empty());
|
||||||
|
if (!states.back() || last_token == token_type::value_separator)
|
||||||
|
{
|
||||||
|
return sax->null();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// recover from a missing key; last_token is where it was expected
|
||||||
|
template<typename SAX>
|
||||||
|
next_step recover_key(SAX* sax)
|
||||||
|
{
|
||||||
|
switch (last_token)
|
||||||
|
{
|
||||||
|
case token_type::value_separator:
|
||||||
|
case token_type::end_object:
|
||||||
|
case token_type::end_array:
|
||||||
|
case token_type::end_of_input:
|
||||||
|
// no member: the object's state handles the token
|
||||||
|
return next_step::evaluate_state;
|
||||||
|
|
||||||
|
case token_type::parse_error:
|
||||||
|
recover_token();
|
||||||
|
if (last_token == token_type::value_string)
|
||||||
|
{
|
||||||
|
// a key that could be repaired
|
||||||
|
return parse_key(sax);
|
||||||
|
}
|
||||||
|
skip_member();
|
||||||
|
return next_step::evaluate_state;
|
||||||
|
|
||||||
|
case token_type::uninitialized:
|
||||||
|
case token_type::literal_true:
|
||||||
|
case token_type::literal_false:
|
||||||
|
case token_type::literal_null:
|
||||||
|
case token_type::value_string:
|
||||||
|
case token_type::value_unsigned:
|
||||||
|
case token_type::value_integer:
|
||||||
|
case token_type::value_float:
|
||||||
|
case token_type::begin_array:
|
||||||
|
case token_type::begin_object:
|
||||||
|
case token_type::name_separator:
|
||||||
|
case token_type::literal_or_value:
|
||||||
|
default:
|
||||||
|
// a member without a key
|
||||||
|
skip_member();
|
||||||
|
return next_step::evaluate_state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// recover from a missing name separator (:) after the key; last_token
|
||||||
|
/// is where it was expected
|
||||||
|
template<typename SAX>
|
||||||
|
next_step recover_name_separator(SAX* sax)
|
||||||
|
{
|
||||||
|
switch (last_token)
|
||||||
|
{
|
||||||
|
case token_type::value_separator:
|
||||||
|
case token_type::end_object:
|
||||||
|
case token_type::end_array:
|
||||||
|
case token_type::end_of_input:
|
||||||
|
// the value is missing as well
|
||||||
|
return sax->null() ? next_step::evaluate_state : next_step::stop;
|
||||||
|
|
||||||
|
case token_type::uninitialized:
|
||||||
|
case token_type::literal_true:
|
||||||
|
case token_type::literal_false:
|
||||||
|
case token_type::literal_null:
|
||||||
|
case token_type::value_string:
|
||||||
|
case token_type::value_unsigned:
|
||||||
|
case token_type::value_integer:
|
||||||
|
case token_type::value_float:
|
||||||
|
case token_type::begin_array:
|
||||||
|
case token_type::begin_object:
|
||||||
|
case token_type::name_separator:
|
||||||
|
case token_type::parse_error:
|
||||||
|
case token_type::literal_or_value:
|
||||||
|
default:
|
||||||
|
// a missing ':'; the value begins here
|
||||||
|
return next_step::parse_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// recover from a token after an object member that is neither ',' nor
|
||||||
|
/// '}' (nor ']' or the end of the input, which the caller handles)
|
||||||
|
template<typename SAX>
|
||||||
|
next_step recover_member(SAX* sax, std::true_type /*allow_recovery*/)
|
||||||
|
{
|
||||||
|
if (last_token == token_type::parse_error)
|
||||||
|
{
|
||||||
|
recover_token();
|
||||||
|
}
|
||||||
|
if (last_token == token_type::value_string)
|
||||||
|
{
|
||||||
|
// a missing ','; the next key begins here
|
||||||
|
return parse_key(sax);
|
||||||
|
}
|
||||||
|
skip_member();
|
||||||
|
return next_step::evaluate_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the parser for parse() and accept() never recovers (and does not come
|
||||||
|
/// here, as report_error() returned false)
|
||||||
|
template<typename SAX>
|
||||||
|
std::false_type recover_member(SAX* /*sax*/, std::false_type /*allow_recovery*/) const noexcept
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
/// get next token from lexer
|
/// get next token from lexer
|
||||||
token_type get_token()
|
token_type get_token()
|
||||||
{
|
{
|
||||||
@@ -560,6 +1166,12 @@ class parser
|
|||||||
const bool allow_exceptions = true;
|
const bool allow_exceptions = true;
|
||||||
/// whether trailing commas in objects and arrays should be ignored (true) or signaled as errors (false)
|
/// whether trailing commas in objects and arrays should be ignored (true) or signaled as errors (false)
|
||||||
const bool ignore_trailing_commas = false;
|
const bool ignore_trailing_commas = false;
|
||||||
|
/// whether an error was reported to the SAX parser
|
||||||
|
bool error_reported = false;
|
||||||
|
/// the position of the last reported error
|
||||||
|
std::size_t last_error_position = 0;
|
||||||
|
/// the token of the last reported error
|
||||||
|
token_type last_error_token = token_type::uninitialized;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|||||||
@@ -8,12 +8,10 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array> // array
|
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint64_t, uint8_t
|
#include <cstdint> // uint64_t
|
||||||
#include <cstring> // memcpy
|
#include <cstring> // memcpy
|
||||||
|
|
||||||
#include <nlohmann/detail/bit_ops.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
|
||||||
@@ -71,12 +69,18 @@ inline std::size_t find_string_special(const unsigned char* data, std::size_t n)
|
|||||||
std::size_t i = 0;
|
std::size_t i = 0;
|
||||||
for (; i + 8 <= n; i += 8)
|
for (; i + 8 <= n; i += 8)
|
||||||
{
|
{
|
||||||
const std::uint64_t special = swar_string_special(read_eight_bytes(data + i));
|
std::uint64_t word = 0;
|
||||||
if (special != 0)
|
std::memcpy(&word, data + i, sizeof(word));
|
||||||
|
if (swar_string_special(word) != 0)
|
||||||
{
|
{
|
||||||
// the lowest flagged byte is the first special one: the borrows of
|
// a special byte is in this word; locate it (endian-agnostic)
|
||||||
// the subtractions can only flag bytes above a true hit
|
for (std::size_t j = 0; j < 8; ++j)
|
||||||
return i + (static_cast<std::size_t>(count_trailing_zeros(special)) / 8);
|
{
|
||||||
|
if (is_string_special(data[i + j]))
|
||||||
|
{
|
||||||
|
return i + j;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (; i < n; ++i)
|
for (; i < n; ++i)
|
||||||
@@ -110,7 +114,8 @@ inline std::size_t find_ascii_copyable_run(const unsigned char* data, std::size_
|
|||||||
std::size_t i = 0;
|
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(data + i);
|
std::uint64_t v = 0;
|
||||||
|
std::memcpy(&v, data + i, sizeof(v));
|
||||||
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"' (0x22)
|
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"' (0x22)
|
||||||
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\' (0x5C)
|
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\' (0x5C)
|
||||||
const std::uint64_t d = v ^ 0x7F7F7F7F7F7F7F7Full; // DEL (0x7F)
|
const std::uint64_t d = v ^ 0x7F7F7F7F7F7F7F7Full; // DEL (0x7F)
|
||||||
@@ -121,9 +126,7 @@ inline std::size_t find_ascii_copyable_run(const unsigned char* data, std::size_
|
|||||||
| (v & high); // >= 0x80
|
| (v & high); // >= 0x80
|
||||||
if (stop != 0)
|
if (stop != 0)
|
||||||
{
|
{
|
||||||
// the lowest flagged byte is the first one to stop at (see
|
break;
|
||||||
// find_string_special())
|
|
||||||
return i + (static_cast<std::size_t>(count_trailing_zeros(stop)) / 8);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (; i < n; ++i)
|
for (; i < n; ++i)
|
||||||
@@ -250,18 +253,12 @@ inline std::size_t scalar_string_bulk_run(const unsigned char* data, std::size_t
|
|||||||
{
|
{
|
||||||
break; // end of buffer, or a quote/escape/control byte
|
break; // end of buffer, or a quote/escape/control byte
|
||||||
}
|
}
|
||||||
// a run of multi-byte sequences (e.g. CJK text) is validated sequence
|
const std::size_t seq = validate_one_utf8(data + pos, n - pos);
|
||||||
// by sequence without searching for the next special byte in between
|
if (seq == 0)
|
||||||
do
|
|
||||||
{
|
{
|
||||||
const std::size_t seq = validate_one_utf8(data + pos, n - pos);
|
break; // ill-formed or truncated: let the byte path diagnose it
|
||||||
if (seq == 0)
|
|
||||||
{
|
|
||||||
return pos; // ill-formed or truncated: let the byte path diagnose it
|
|
||||||
}
|
|
||||||
pos += seq;
|
|
||||||
}
|
}
|
||||||
while (pos < n && data[pos] >= 0x80u);
|
pos += seq;
|
||||||
}
|
}
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
@@ -276,7 +273,8 @@ inline std::size_t find_string_delimiter(const unsigned char* data, std::size_t
|
|||||||
std::size_t i = 0;
|
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(data + i);
|
std::uint64_t v = 0;
|
||||||
|
std::memcpy(&v, data + i, sizeof(v));
|
||||||
const std::uint64_t q = v ^ 0x2222222222222222ull;
|
const std::uint64_t q = v ^ 0x2222222222222222ull;
|
||||||
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull;
|
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull;
|
||||||
const std::uint64_t hit = ((q - ones) & ~q & high)
|
const std::uint64_t hit = ((q - ones) & ~q & high)
|
||||||
@@ -284,8 +282,14 @@ inline std::size_t find_string_delimiter(const unsigned char* data, std::size_t
|
|||||||
| ((v - 0x2020202020202020ull) & ~v & high);
|
| ((v - 0x2020202020202020ull) & ~v & high);
|
||||||
if (hit != 0)
|
if (hit != 0)
|
||||||
{
|
{
|
||||||
// the lowest flagged byte is the first delimiter (see find_string_special())
|
for (std::size_t j = 0; j < 8; ++j)
|
||||||
return i + (static_cast<std::size_t>(count_trailing_zeros(hit)) / 8);
|
{
|
||||||
|
const unsigned char c = data[i + j];
|
||||||
|
if (c == '\"' || c == '\\' || c < 0x20u)
|
||||||
|
{
|
||||||
|
return i + j;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (; i < n; ++i)
|
for (; i < n; ++i)
|
||||||
@@ -316,50 +320,5 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
// (after yyjson's read_hex_u16), or return -1 if any of the 4 bytes is not a
|
|
||||||
// hex digit ('0'..'9', 'A'..'F', 'a'..'f'). The caller must already have
|
|
||||||
// checked that 4 bytes are available; used by lexer::get_codepoint()'s
|
|
||||||
// contiguous fast path. On -1 it falls back to the byte-at-a-time loop, which
|
|
||||||
// stops at the first invalid digit, so the reported error and position are
|
|
||||||
// unaffected by this fast path.
|
|
||||||
inline int hex_codepoint(const unsigned char* data) noexcept
|
|
||||||
{
|
|
||||||
static const std::array<std::uint8_t, 256> hex_digit_table = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 00..0F
|
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 10..1F
|
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 20..2F
|
|
||||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 30..3F ('0'..'9')
|
|
||||||
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 40..4F ('A'..'F')
|
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 50..5F
|
|
||||||
0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 60..6F ('a'..'f')
|
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 70..7F
|
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 80..8F
|
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 90..9F
|
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // A0..AF
|
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // B0..BF
|
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // C0..CF
|
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // D0..DF
|
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // E0..EF
|
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // F0..FF
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const std::uint8_t d0 = hex_digit_table[data[0]];
|
|
||||||
const std::uint8_t d1 = hex_digit_table[data[1]];
|
|
||||||
const std::uint8_t d2 = hex_digit_table[data[2]];
|
|
||||||
const std::uint8_t d3 = hex_digit_table[data[3]];
|
|
||||||
// every valid digit is <= 0xF; the combined OR only exceeds it if at
|
|
||||||
// least one of the four bytes was not a hex digit (looked up as 0xFF)
|
|
||||||
if ((d0 | d1 | d2 | d3) > 0x0F)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return (d0 << 12) | (d1 << 8) | (d2 << 4) | d3;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
|
|||||||
@@ -359,6 +359,7 @@ void templated_json_throw(ExceptionType exception)
|
|||||||
|
|
||||||
// Macros to simplify conversion from/to types
|
// Macros to simplify conversion from/to types
|
||||||
|
|
||||||
|
// NLOHMANN_JSON_EXPAND to NLOHMANN_JSON_DOUBLE_PASTE63 are generated by tools/macro_builder (see its README.md)
|
||||||
#define NLOHMANN_JSON_EXPAND( x ) x
|
#define NLOHMANN_JSON_EXPAND( x ) x
|
||||||
#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME
|
#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME
|
||||||
#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \
|
#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \
|
||||||
@@ -621,6 +622,10 @@ void templated_json_throw(ExceptionType exception)
|
|||||||
// arguments, so dispatching on Type,BaseType,member... directly would run out
|
// arguments, so dispatching on Type,BaseType,member... directly would run out
|
||||||
// one slot early and cap the derived-type macros at 62 members instead of the
|
// one slot early and cap the derived-type macros at 62 members instead of the
|
||||||
// 63 that NLOHMANN_JSON_PASTE supports.
|
// 63 that NLOHMANN_JSON_PASTE supports.
|
||||||
|
//
|
||||||
|
// The slot table below (down to the closing NLOHMANN_JSON_TYPE_BODY_SENTINEL))
|
||||||
|
// is generated by tools/macro_builder (see its README.md; run with the
|
||||||
|
// "type_body" argument).
|
||||||
#define NLOHMANN_JSON_TYPE_BODY(Prefix, ...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \
|
#define NLOHMANN_JSON_TYPE_BODY(Prefix, ...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \
|
||||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
||||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
||||||
@@ -915,3 +920,7 @@ void templated_json_throw(ExceptionType exception)
|
|||||||
#ifndef JSON_DISABLE_ENUM_SERIALIZATION
|
#ifndef JSON_DISABLE_ENUM_SERIALIZATION
|
||||||
#define JSON_DISABLE_ENUM_SERIALIZATION 0
|
#define JSON_DISABLE_ENUM_SERIALIZATION 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||||
|
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#undef JSON_INLINE_VARIABLE
|
#undef JSON_INLINE_VARIABLE
|
||||||
#undef JSON_NO_UNIQUE_ADDRESS
|
#undef JSON_NO_UNIQUE_ADDRESS
|
||||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||||
|
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||||
|
|
||||||
#ifndef JSON_TEST_KEEP_MACROS
|
#ifndef JSON_TEST_KEEP_MACROS
|
||||||
#undef JSON_CATCH
|
#undef JSON_CATCH
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array> // array
|
|
||||||
#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_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
|
||||||
@@ -161,11 +160,5 @@ struct static_const
|
|||||||
constexpr T static_const<T>::value;
|
constexpr T static_const<T>::value;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
template<typename T, typename... Args>
|
|
||||||
constexpr std::array<T, sizeof...(Args)> make_array(Args&& ... args)
|
|
||||||
{
|
|
||||||
return std::array<T, sizeof...(Args)> {{static_cast<T>(std::forward<Args>(args))...}};
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
|
|||||||
@@ -636,6 +636,18 @@ template<typename BasicJsonType, typename CompatibleType>
|
|||||||
struct is_compatible_type
|
struct is_compatible_type
|
||||||
: is_compatible_type_impl<BasicJsonType, CompatibleType> {};
|
: is_compatible_type_impl<BasicJsonType, CompatibleType> {};
|
||||||
|
|
||||||
|
// a one-element std::tuple holding a reference to BasicJsonType, as created by
|
||||||
|
// std::forward_as_tuple(j); see JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||||
|
template<typename BasicJsonType, typename T>
|
||||||
|
struct is_basic_json_reference_tuple : std::false_type {};
|
||||||
|
|
||||||
|
template<typename BasicJsonType, typename T>
|
||||||
|
struct is_basic_json_reference_tuple<BasicJsonType, std::tuple<T>>
|
||||||
|
{
|
||||||
|
static constexpr bool value =
|
||||||
|
std::is_reference<T>::value && std::is_same<uncvref_t<T>, BasicJsonType>::value;
|
||||||
|
};
|
||||||
|
|
||||||
template<typename BasicJsonType, typename CompatibleArrayType>
|
template<typename BasicJsonType, typename CompatibleArrayType>
|
||||||
struct is_compatible_binary_type
|
struct is_compatible_binary_type
|
||||||
{
|
{
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@
|
|||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint8_t, uint32_t
|
#include <cstdint> // uint8_t, uint32_t
|
||||||
#include <string> // string, to_string
|
#include <string> // string, to_string
|
||||||
|
#include <utility> // move
|
||||||
|
|
||||||
#include <nlohmann/detail/abi_macros.hpp>
|
#include <nlohmann/detail/abi_macros.hpp>
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
@@ -133,5 +134,78 @@ inline bool is_valid_utf8(const StringType& s, const std::size_t first = 0) noex
|
|||||||
return state == UTF8_ACCEPT;
|
return state == UTF8_ACCEPT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief append U+FFFD REPLACEMENT CHARACTER, encoded in UTF-8
|
||||||
|
@param[in,out] s the string to append to
|
||||||
|
*/
|
||||||
|
template<typename StringType>
|
||||||
|
inline void append_replacement_character(StringType& s)
|
||||||
|
{
|
||||||
|
s.push_back(static_cast<typename StringType::value_type>(0xEFu));
|
||||||
|
s.push_back(static_cast<typename StringType::value_type>(0xBFu));
|
||||||
|
s.push_back(static_cast<typename StringType::value_type>(0xBDu));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief replace ill-formed UTF-8 with U+FFFD REPLACEMENT CHARACTER
|
||||||
|
|
||||||
|
Each maximal subpart of an ill-formed sequence becomes one U+FFFD, as the
|
||||||
|
Unicode Standard recommends (Section 3.9, "U+FFFD Substitution of Maximal
|
||||||
|
Subparts"), and as the parser for JSON text does when it recovers from errors.
|
||||||
|
|
||||||
|
@param[in,out] s the string to repair
|
||||||
|
@param[in] first index of the first byte to repair; the bytes before it are
|
||||||
|
assumed to be valid UTF-8 that ends on a code point boundary
|
||||||
|
*/
|
||||||
|
template<typename StringType>
|
||||||
|
inline void replace_invalid_utf8(StringType& s, const std::size_t first = 0)
|
||||||
|
{
|
||||||
|
StringType result = s;
|
||||||
|
result.resize(first);
|
||||||
|
|
||||||
|
std::uint8_t state = UTF8_ACCEPT;
|
||||||
|
std::uint32_t codepoint = 0;
|
||||||
|
// the first byte of the sequence being decoded
|
||||||
|
std::size_t sequence_start = first;
|
||||||
|
|
||||||
|
std::size_t i = first;
|
||||||
|
while (i < s.size())
|
||||||
|
{
|
||||||
|
switch (decode(state, codepoint, static_cast<std::uint8_t>(s[i])))
|
||||||
|
{
|
||||||
|
case UTF8_ACCEPT:
|
||||||
|
for (++i; sequence_start < i; ++sequence_start)
|
||||||
|
{
|
||||||
|
result.push_back(s[sequence_start]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UTF8_REJECT:
|
||||||
|
append_replacement_character(result);
|
||||||
|
// the byte that made the sequence ill-formed begins the next
|
||||||
|
// one, unless it began this one
|
||||||
|
if (i == sequence_start)
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
state = UTF8_ACCEPT;
|
||||||
|
sequence_start = i;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: // in the middle of a sequence
|
||||||
|
++i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// a sequence that the string ends in the middle of
|
||||||
|
if (state != UTF8_ACCEPT)
|
||||||
|
{
|
||||||
|
append_replacement_character(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
s = std::move(result);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,130 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array> // array
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstring> // memcpy
|
|
||||||
#include <new> // operator new, placement new
|
|
||||||
#include <string> // string
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
|
||||||
#include <nlohmann/detail/view/node.hpp>
|
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
||||||
namespace detail
|
|
||||||
{
|
|
||||||
namespace view
|
|
||||||
{
|
|
||||||
|
|
||||||
/// storage of a parsed document; heap-allocated (header and an initial node
|
|
||||||
/// array in one block) so that views survive moves of the owning document
|
|
||||||
struct document_data
|
|
||||||
{
|
|
||||||
const char* src = nullptr;
|
|
||||||
std::size_t size = 0;
|
|
||||||
node* tape = nullptr;
|
|
||||||
std::size_t tape_size = 0;
|
|
||||||
std::size_t tape_cap = 0;
|
|
||||||
node* inline_tape = nullptr; ///< node array allocated together with this header
|
|
||||||
std::size_t inline_cap = 0;
|
|
||||||
std::string arena{}; ///< decoded strings that contained escapes // NOLINT(readability-redundant-member-init)
|
|
||||||
std::string owned{}; ///< owned copy of the input, if any // NOLINT(readability-redundant-member-init)
|
|
||||||
std::array<const char*, 4> base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena (indexed by flags & node_flags::storage)
|
|
||||||
bool discarded = true;
|
|
||||||
|
|
||||||
/// one allocation for the header and room for `nodes` nodes; large
|
|
||||||
/// documents get a separate node array instead (so it can be trimmed)
|
|
||||||
static document_data* create(std::size_t nodes)
|
|
||||||
{
|
|
||||||
nodes = nodes <= 256 ? nodes : 0;
|
|
||||||
void* mem = ::operator new (sizeof(document_data) + (nodes * sizeof(node)));
|
|
||||||
auto* d = new (mem) document_data(); // NOLINT(cppcoreguidelines-owning-memory): owned by the returned pointer, freed by deleter
|
|
||||||
// (aligned: sizeof is a multiple of the alignment; through void*, as GCC's -Wcast-align wants)
|
|
||||||
d->inline_tape = static_cast<node*>(static_cast<void*>(static_cast<char*>(mem) + sizeof(document_data))); // NOLINT(bugprone-casting-through-void)
|
|
||||||
d->inline_cap = nodes;
|
|
||||||
d->tape = d->inline_tape;
|
|
||||||
d->tape_cap = nodes;
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct deleter
|
|
||||||
{
|
|
||||||
void operator()(document_data* d) const noexcept
|
|
||||||
{
|
|
||||||
d->~document_data();
|
|
||||||
::operator delete (d);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document_data() noexcept = default;
|
|
||||||
document_data(const document_data&) = delete;
|
|
||||||
document_data(document_data&&) = delete;
|
|
||||||
document_data& operator=(const document_data&) = delete;
|
|
||||||
document_data& operator=(document_data&&) = delete;
|
|
||||||
~document_data()
|
|
||||||
{
|
|
||||||
release();
|
|
||||||
}
|
|
||||||
|
|
||||||
void release() noexcept
|
|
||||||
{
|
|
||||||
if (tape != inline_tape)
|
|
||||||
{
|
|
||||||
::operator delete (tape);
|
|
||||||
}
|
|
||||||
tape = inline_tape;
|
|
||||||
tape_cap = inline_cap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// make room for n nodes; keeps the first tape_size nodes
|
|
||||||
void reserve(std::size_t n)
|
|
||||||
{
|
|
||||||
if (n <= tape_cap)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
node* fresh = static_cast<node*>(::operator new (n * sizeof(node)));
|
|
||||||
if (tape_size != 0)
|
|
||||||
{
|
|
||||||
std::memcpy(fresh, tape, tape_size * sizeof(node));
|
|
||||||
}
|
|
||||||
release();
|
|
||||||
tape = fresh;
|
|
||||||
tape_cap = n;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* str(const node& n) const noexcept
|
|
||||||
{
|
|
||||||
return base[n.flags & node_flags::storage] + n.off;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the node after n's subtree (containers span `next` nodes, scalars one)
|
|
||||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* after(const node* n) noexcept
|
|
||||||
{
|
|
||||||
return n + (is_container(*n) ? n->next : 1u);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// first element (array) or first key (object) of a container
|
|
||||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* first_child(const node* n) noexcept
|
|
||||||
{
|
|
||||||
return n + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// end of the elements of a container
|
|
||||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* child_end(const node* n) noexcept
|
|
||||||
{
|
|
||||||
return n + n->next;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace view
|
|
||||||
} // namespace detail
|
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
// Macros of json_view.hpp and its detail headers. json.hpp undefines its own
|
|
||||||
// macros at its end (macro_unscope.hpp), so the view defines the few it needs
|
|
||||||
// under its own prefix; json_view.hpp undefines them all at its end
|
|
||||||
// (detail/view/macro_unscope.hpp). Configuration that json.hpp undefines is
|
|
||||||
// read from detail::abi_config instead.
|
|
||||||
|
|
||||||
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
|
|
||||||
#define NLOHMANN_VIEW_HAS_CPP_17 1
|
|
||||||
#else
|
|
||||||
#define NLOHMANN_VIEW_HAS_CPP_17 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(__GNUC__) || defined(__clang__)
|
|
||||||
#define NLOHMANN_VIEW_LIKELY(x) __builtin_expect(!!(x), 1)
|
|
||||||
#define NLOHMANN_VIEW_UNLIKELY(x) __builtin_expect(!!(x), 0)
|
|
||||||
#define NLOHMANN_VIEW_ALWAYS_INLINE inline __attribute__((always_inline))
|
|
||||||
#define NLOHMANN_VIEW_NOINLINE __attribute__((noinline))
|
|
||||||
#elif defined(_MSC_VER)
|
|
||||||
#define NLOHMANN_VIEW_LIKELY(x) (x)
|
|
||||||
#define NLOHMANN_VIEW_UNLIKELY(x) (x)
|
|
||||||
#define NLOHMANN_VIEW_ALWAYS_INLINE __forceinline
|
|
||||||
#define NLOHMANN_VIEW_NOINLINE __declspec(noinline)
|
|
||||||
#else
|
|
||||||
#define NLOHMANN_VIEW_LIKELY(x) (x)
|
|
||||||
#define NLOHMANN_VIEW_UNLIKELY(x) (x)
|
|
||||||
#define NLOHMANN_VIEW_ALWAYS_INLINE inline
|
|
||||||
#define NLOHMANN_VIEW_NOINLINE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// exceptions as in json.hpp (JSON_NOEXCEPTION, JSON_THROW_USER)
|
|
||||||
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
|
||||||
#define NLOHMANN_VIEW_THROW(exception) throw exception
|
|
||||||
#else
|
|
||||||
#include <cstdlib>
|
|
||||||
// (the exception is built first, so that the arguments of the throwing
|
|
||||||
// helpers count as used; the program ends anyway)
|
|
||||||
#define NLOHMANN_VIEW_THROW(exception) (static_cast<void>(exception), std::abort())
|
|
||||||
#endif
|
|
||||||
#if defined(JSON_THROW_USER)
|
|
||||||
#undef NLOHMANN_VIEW_THROW
|
|
||||||
#define NLOHMANN_VIEW_THROW JSON_THROW_USER
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// the parser stores a node's first word at once where the layout of `node` is
|
|
||||||
// known to be little-endian (MSVC targets are); elsewhere field by field
|
|
||||||
#if (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || defined(_MSC_VER)
|
|
||||||
#define NLOHMANN_VIEW_LITTLE_ENDIAN 1
|
|
||||||
#else
|
|
||||||
#define NLOHMANN_VIEW_LITTLE_ENDIAN 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// sixteen checks at fixed offsets 0..15
|
|
||||||
#define NLOHMANN_VIEW_REPEAT16(X) X(0) X(1) X(2) X(3) X(4) X(5) X(6) X(7) X(8) X(9) X(10) X(11) X(12) X(13) X(14) X(15)
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
// undefine the macros of detail/view/macro_scope.hpp (at the end of json_view.hpp)
|
|
||||||
|
|
||||||
#undef NLOHMANN_VIEW_HAS_CPP_17
|
|
||||||
#undef NLOHMANN_VIEW_LIKELY
|
|
||||||
#undef NLOHMANN_VIEW_UNLIKELY
|
|
||||||
#undef NLOHMANN_VIEW_ALWAYS_INLINE
|
|
||||||
#undef NLOHMANN_VIEW_NOINLINE
|
|
||||||
#undef NLOHMANN_VIEW_THROW
|
|
||||||
#undef NLOHMANN_VIEW_LITTLE_ENDIAN
|
|
||||||
#undef NLOHMANN_VIEW_REPEAT16
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
|
|
||||||
#include <cstring> // memcpy
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
||||||
namespace detail
|
|
||||||
{
|
|
||||||
namespace view
|
|
||||||
{
|
|
||||||
|
|
||||||
// the node kinds are value_t values; the tests of is_container() and of the
|
|
||||||
// number kinds depend on this numbering
|
|
||||||
static_assert(static_cast<std::uint8_t>(value_t::null) == 0 && static_cast<std::uint8_t>(value_t::object) == 1
|
|
||||||
&& static_cast<std::uint8_t>(value_t::array) == 2 && static_cast<std::uint8_t>(value_t::string) == 3
|
|
||||||
&& static_cast<std::uint8_t>(value_t::boolean) == 4 && static_cast<std::uint8_t>(value_t::number_integer) == 5
|
|
||||||
&& static_cast<std::uint8_t>(value_t::number_unsigned) == 6 && static_cast<std::uint8_t>(value_t::number_float) == 7,
|
|
||||||
"the node format depends on the numbering of value_t");
|
|
||||||
|
|
||||||
/// node flags
|
|
||||||
struct node_flags
|
|
||||||
{
|
|
||||||
static constexpr std::uint8_t escaped = 1; ///< string payload lives in the decode arena, not the source
|
|
||||||
static constexpr std::uint8_t storage = 3; ///< mask: where a string or number token lives (index into document_data::base)
|
|
||||||
static constexpr std::uint8_t is_true = 4; ///< boolean value
|
|
||||||
};
|
|
||||||
|
|
||||||
/// One entry of the flat index, in document order. An object's members are
|
|
||||||
/// stored as key node followed by the value's subtree. Integers keep their
|
|
||||||
/// converted 64-bit value in the len/next bytes (the node after a scalar is
|
|
||||||
/// always the next one, and the token length follows from `extra`).
|
|
||||||
struct node
|
|
||||||
{
|
|
||||||
std::uint8_t kind; ///< value_t
|
|
||||||
std::uint8_t flags; ///< node_flags
|
|
||||||
std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; otherwise 0
|
|
||||||
std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if node_flags::escaped
|
|
||||||
std::uint32_t len; ///< string: decoded bytes; float: token bytes; array/object: element count
|
|
||||||
std::uint32_t next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence)
|
|
||||||
};
|
|
||||||
static_assert(sizeof(node) == 16, "node must stay 16 bytes");
|
|
||||||
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE bool is_container(const node& n) noexcept
|
|
||||||
{
|
|
||||||
return static_cast<unsigned>(n.kind) - 1u <= 1u;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the converted value of an integer node (stored in len/next)
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t integer_bits(const node& n) noexcept
|
|
||||||
{
|
|
||||||
std::uint64_t v = 0;
|
|
||||||
std::memcpy(&v, reinterpret_cast<const unsigned char*>(&n) + 8, 8); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE void set_integer_bits(node& n, std::uint64_t v) noexcept
|
|
||||||
{
|
|
||||||
std::memcpy(reinterpret_cast<unsigned char*>(&n) + 8, &v, 8); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// token length of a number node
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t number_length(const node& n) noexcept
|
|
||||||
{
|
|
||||||
return n.kind == static_cast<std::uint8_t>(value_t::number_float) ? n.len
|
|
||||||
: (n.extra & 0xFFu) + (n.kind == static_cast<std::uint8_t>(value_t::number_integer) ? 1u : 0u);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// estimated number of nodes for an input of `size` bytes (one node per ~12
|
|
||||||
/// bytes covers typical documents without regrowth)
|
|
||||||
inline std::size_t estimate_nodes(std::size_t size) noexcept
|
|
||||||
{
|
|
||||||
return (size / 12) + 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// estimated number of nodes for the input [src, src + size): pretty-printed
|
|
||||||
/// input (whitespace after the first byte) needs about a node per 12 bytes,
|
|
||||||
/// minified input up to one per 4 (yyjson tells the two apart the same way)
|
|
||||||
inline std::size_t estimate_nodes(const char* src, std::size_t size) noexcept
|
|
||||||
{
|
|
||||||
return size >= 2 && (src[1] == ' ' || src[1] == '\n' || src[1] == '\r' || src[1] == '\t') ? estimate_nodes(size) : (size / 4) + 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace view
|
|
||||||
} // namespace detail
|
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-FileCopyrightText: 2020 YaoYuan <https://github.com/ibireme/yyjson>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array> // array
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdint> // uint8_t, uint16_t, uint64_t
|
|
||||||
#include <cstring> // memcpy
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
|
||||||
|
|
||||||
// Scanning primitives of the view's parser. The unrolled checks at fixed
|
|
||||||
// offsets follow yyjson (https://github.com/ibireme/yyjson, MIT license): the
|
|
||||||
// loads do not depend on each other, so the CPU can run ahead. Words are read
|
|
||||||
// with read_eight_bytes(), so nothing here depends on the byte order.
|
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
||||||
namespace detail
|
|
||||||
{
|
|
||||||
namespace view
|
|
||||||
{
|
|
||||||
|
|
||||||
/// 1 for bytes that may appear verbatim in a string: 0x20..0x7F except '"' and '\\'
|
|
||||||
inline const std::uint8_t* string_plain() noexcept
|
|
||||||
{
|
|
||||||
static const std::array<std::uint8_t, 256> table =
|
|
||||||
{
|
|
||||||
{
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00..0x1F
|
|
||||||
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20..0x3F ('"')
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 0x40..0x5F ('\\')
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60..0x7F
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80..0x9F
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xA0..0xBF
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xC0..0xDF
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xE0..0xFF
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return table.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE bool is_digit(unsigned char c) noexcept
|
|
||||||
{
|
|
||||||
return static_cast<unsigned char>(c - '0') <= 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// two bytes as they are in memory (only compared with byte-symmetric patterns)
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint16_t load16(const unsigned char* p) noexcept
|
|
||||||
{
|
|
||||||
std::uint16_t w = 0;
|
|
||||||
std::memcpy(&w, p, 2);
|
|
||||||
return w;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Advance over plain string bytes and well-formed UTF-8. Stops at a quote,
|
|
||||||
/// a backslash, a control character, ill-formed UTF-8, or the end. The first
|
|
||||||
/// 16 bytes are checked one by one, so that the position advances by
|
|
||||||
/// constants in predicted branches (most strings are short); longer runs
|
|
||||||
/// continue eight bytes at a time.
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* scan_string_run(const unsigned char* p, const unsigned char* e) noexcept
|
|
||||||
{
|
|
||||||
const std::uint8_t* plain = string_plain();
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
if (e - p >= 16)
|
|
||||||
{
|
|
||||||
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { p += (i); goto stop; }
|
|
||||||
NLOHMANN_VIEW_REPEAT16(NLOHMANN_VIEW_STEP)
|
|
||||||
#undef NLOHMANN_VIEW_STEP
|
|
||||||
p += 16;
|
|
||||||
while (e - p >= 8)
|
|
||||||
{
|
|
||||||
const std::uint64_t special = swar_string_special(read_eight_bytes(p));
|
|
||||||
if (special != 0)
|
|
||||||
{
|
|
||||||
p += count_trailing_zeros(special) / 8;
|
|
||||||
goto stop;
|
|
||||||
}
|
|
||||||
p += 8;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
while (p != e && plain[*p] != 0)
|
|
||||||
{
|
|
||||||
++p;
|
|
||||||
}
|
|
||||||
if (p == e)
|
|
||||||
{
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
stop:
|
|
||||||
if (*p < 0x80)
|
|
||||||
{
|
|
||||||
return p; // quote, backslash, or control character
|
|
||||||
}
|
|
||||||
// non-ASCII: a run of well-formed sequences (the library's check, so
|
|
||||||
// that exactly what json::parse accepts is accepted)
|
|
||||||
do
|
|
||||||
{
|
|
||||||
const std::size_t n = validate_one_utf8(p, static_cast<std::size_t>(e - p));
|
|
||||||
if (n == 0)
|
|
||||||
{
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
p += n;
|
|
||||||
}
|
|
||||||
while (p != e && *p >= 0x80);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// advance over ASCII digits
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_digits(const unsigned char* p, const unsigned char* e) noexcept
|
|
||||||
{
|
|
||||||
while (e - p >= 16)
|
|
||||||
{
|
|
||||||
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(is_digit(p[i]))) {} else { return p + (i); }
|
|
||||||
NLOHMANN_VIEW_REPEAT16(NLOHMANN_VIEW_STEP)
|
|
||||||
#undef NLOHMANN_VIEW_STEP
|
|
||||||
p += 16;
|
|
||||||
}
|
|
||||||
while (p != e && is_digit(*p))
|
|
||||||
{
|
|
||||||
++p;
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// powers of ten up to 10^19 as integers
|
|
||||||
inline std::uint64_t int_pow10(unsigned k) noexcept
|
|
||||||
{
|
|
||||||
static const std::array<std::uint64_t, 20> table =
|
|
||||||
{
|
|
||||||
{
|
|
||||||
1u, 10u, 100u, 1000u, 10000u, 100000u, 1000000u, 10000000u, 100000000u, 1000000000u,
|
|
||||||
10000000000u, 100000000000u, 1000000000000u, 10000000000000u, 100000000000000u, 1000000000000000u,
|
|
||||||
10000000000000000u, 100000000000000000u, 1000000000000000000u, 10000000000000000000u
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return table[k];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// value of 0 < k < 8 digits at p in one step if [p, p + 8) lies below
|
|
||||||
/// limit, else one digit at a time (whole blocks of eight digits are read by
|
|
||||||
/// parse_upto19() directly)
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t parse_upto8(const unsigned char* p, unsigned k, const unsigned char* limit) noexcept
|
|
||||||
{
|
|
||||||
if (NLOHMANN_VIEW_LIKELY(limit - p >= 8))
|
|
||||||
{
|
|
||||||
// move the k digits to the top and pad the vacated low bytes with '0'
|
|
||||||
const unsigned shift = 8 * (8 - k);
|
|
||||||
return parse_eight_digits((read_eight_bytes(p) << shift) | (0x3030303030303030u >> (8 * k)));
|
|
||||||
}
|
|
||||||
std::uint64_t v = 0;
|
|
||||||
for (unsigned i = 0; i < k; ++i)
|
|
||||||
{
|
|
||||||
v = (v * 10) + static_cast<std::uint64_t>(p[i] - '0');
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// value of k <= 19 digits at p
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t parse_upto19(const unsigned char* p, unsigned k, const unsigned char* limit) noexcept
|
|
||||||
{
|
|
||||||
std::uint64_t w = 0;
|
|
||||||
while (k >= 8)
|
|
||||||
{
|
|
||||||
// (eight digits of the token: they lie below limit)
|
|
||||||
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
|
|
||||||
p += 8;
|
|
||||||
k -= 8;
|
|
||||||
}
|
|
||||||
if (k != 0)
|
|
||||||
{
|
|
||||||
w = (w * int_pow10(k)) + parse_upto8(p, k, limit);
|
|
||||||
}
|
|
||||||
return w;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace view
|
|
||||||
} // namespace detail
|
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <algorithm> // min
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstring> // memcmp, strlen
|
|
||||||
#include <string> // basic_string
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
|
||||||
|
|
||||||
#if NLOHMANN_VIEW_HAS_CPP_17
|
|
||||||
#include <string_view> // string_view
|
|
||||||
#endif
|
|
||||||
#ifndef JSON_NO_IO
|
|
||||||
#include <ostream> // ostream
|
|
||||||
#endif
|
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
||||||
namespace detail
|
|
||||||
{
|
|
||||||
namespace view
|
|
||||||
{
|
|
||||||
|
|
||||||
#if NLOHMANN_VIEW_HAS_CPP_17
|
|
||||||
using string_ref = std::string_view;
|
|
||||||
#else
|
|
||||||
/// minimal C++11 stand-in for std::string_view
|
|
||||||
class string_ref
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using size_type = std::size_t;
|
|
||||||
using const_iterator = const char*;
|
|
||||||
|
|
||||||
string_ref() noexcept = default;
|
|
||||||
string_ref(const char* s) : m_data(s), m_size(std::strlen(s)) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
|
||||||
string_ref(const char* s, std::size_t n) noexcept : m_data(s), m_size(n) {}
|
|
||||||
template<typename Traits, typename Alloc>
|
|
||||||
string_ref(const std::basic_string<char, Traits, Alloc>& s) noexcept : m_data(s.data()), m_size(s.size()) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
|
||||||
|
|
||||||
const char* data() const noexcept
|
|
||||||
{
|
|
||||||
return m_data;
|
|
||||||
}
|
|
||||||
std::size_t size() const noexcept
|
|
||||||
{
|
|
||||||
return m_size;
|
|
||||||
}
|
|
||||||
std::size_t length() const noexcept
|
|
||||||
{
|
|
||||||
return m_size;
|
|
||||||
}
|
|
||||||
bool empty() const noexcept
|
|
||||||
{
|
|
||||||
return m_size == 0;
|
|
||||||
}
|
|
||||||
const char* begin() const noexcept
|
|
||||||
{
|
|
||||||
return m_data;
|
|
||||||
}
|
|
||||||
const char* end() const noexcept
|
|
||||||
{
|
|
||||||
return m_data + m_size;
|
|
||||||
}
|
|
||||||
char operator[](std::size_t i) const noexcept
|
|
||||||
{
|
|
||||||
return m_data[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Traits, typename Alloc>
|
|
||||||
explicit operator std::basic_string<char, Traits, Alloc>() const
|
|
||||||
{
|
|
||||||
return std::basic_string<char, Traits, Alloc>(m_data, m_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
friend bool operator==(string_ref a, string_ref b) noexcept
|
|
||||||
{
|
|
||||||
return a.m_size == b.m_size && (a.m_size == 0 || std::memcmp(a.m_data, b.m_data, a.m_size) == 0);
|
|
||||||
}
|
|
||||||
friend bool operator!=(string_ref a, string_ref b) noexcept
|
|
||||||
{
|
|
||||||
return !(a == b);
|
|
||||||
}
|
|
||||||
friend bool operator<(string_ref a, string_ref b) noexcept
|
|
||||||
{
|
|
||||||
const int c = std::memcmp(a.m_data, b.m_data, (std::min)(a.m_size, b.m_size));
|
|
||||||
return c != 0 ? c < 0 : a.m_size < b.m_size;
|
|
||||||
}
|
|
||||||
#ifndef JSON_NO_IO
|
|
||||||
friend std::ostream& operator<<(std::ostream& o, string_ref s)
|
|
||||||
{
|
|
||||||
return o.write(s.m_data, static_cast<std::streamsize>(s.m_size));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private:
|
|
||||||
const char* m_data = "";
|
|
||||||
std::size_t m_size = 0;
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace view
|
|
||||||
} // namespace detail
|
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
|
||||||
+253
-69
@@ -45,7 +45,6 @@
|
|||||||
|
|
||||||
#include <nlohmann/adl_serializer.hpp>
|
#include <nlohmann/adl_serializer.hpp>
|
||||||
#include <nlohmann/byte_container_with_subtype.hpp>
|
#include <nlohmann/byte_container_with_subtype.hpp>
|
||||||
#include <nlohmann/detail/abi_config.hpp>
|
|
||||||
#include <nlohmann/detail/conversions/from_json.hpp>
|
#include <nlohmann/detail/conversions/from_json.hpp>
|
||||||
#include <nlohmann/detail/conversions/to_json.hpp>
|
#include <nlohmann/detail/conversions/to_json.hpp>
|
||||||
#include <nlohmann/detail/exceptions.hpp>
|
#include <nlohmann/detail/exceptions.hpp>
|
||||||
@@ -146,7 +145,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
friend class ::nlohmann::detail::iter_impl;
|
friend class ::nlohmann::detail::iter_impl;
|
||||||
template<typename BasicJsonType, typename CharType, typename OutputSinkType>
|
template<typename BasicJsonType, typename CharType, typename OutputSinkType>
|
||||||
friend class ::nlohmann::detail::binary_writer;
|
friend class ::nlohmann::detail::binary_writer;
|
||||||
template<typename BasicJsonType, typename InputType, typename SAX>
|
template<typename BasicJsonType, typename InputType, typename SAX, bool AllowRecovery>
|
||||||
friend class ::nlohmann::detail::binary_reader;
|
friend class ::nlohmann::detail::binary_reader;
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
friend class ::nlohmann::detail::json_sax_dom_parser;
|
friend class ::nlohmann::detail::json_sax_dom_parser;
|
||||||
@@ -907,11 +906,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
/*!
|
/*!
|
||||||
@brief how many levels the operation going on in this thread has descended into
|
@brief how many levels the operation going on in this thread has descended into
|
||||||
|
|
||||||
Copying a value and comparing two values share this count. The library never
|
Copying a value, converting one from another specialization, and comparing
|
||||||
nests one inside the other - copying a value does not compare one, and
|
two values share this count. The library never nests one of them inside
|
||||||
comparing two values does not copy them - and where user code nests them
|
another - none of them does either of the other two on the way - and where
|
||||||
anyway, sharing the count only ends a descent sooner than it had to, which
|
user code nests them anyway, sharing the count only ends a descent sooner
|
||||||
costs a little speed and is never wrong.
|
than it had to, which costs a little speed and is never wrong.
|
||||||
|
|
||||||
A byte is enough: the count never exceeds the limit by more than the single
|
A byte is enough: the count never exceeds the limit by more than the single
|
||||||
level that notices the limit has been reached.
|
level that notices the limit has been reached.
|
||||||
@@ -1268,6 +1267,222 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
copy_iteratively(src);
|
copy_iteratively(src);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief convert the value @a val of another specialization into this null
|
||||||
|
value; @a val must be neither an object nor an array
|
||||||
|
|
||||||
|
Converting such a value never descends, so both ways of converting an
|
||||||
|
object or an array (@ref convert_structured) leave their elements of this
|
||||||
|
kind to the converting constructor, which leaves them to this.
|
||||||
|
*/
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
void convert_leaf(const BasicJsonType& val)
|
||||||
|
{
|
||||||
|
using other_boolean_t = typename BasicJsonType::boolean_t;
|
||||||
|
using other_number_float_t = typename BasicJsonType::number_float_t;
|
||||||
|
using other_number_integer_t = typename BasicJsonType::number_integer_t;
|
||||||
|
using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||||
|
using other_string_t = typename BasicJsonType::string_t;
|
||||||
|
using other_binary_t = typename BasicJsonType::binary_t;
|
||||||
|
|
||||||
|
switch (val.type())
|
||||||
|
{
|
||||||
|
case value_t::boolean:
|
||||||
|
JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>());
|
||||||
|
break;
|
||||||
|
case value_t::number_float:
|
||||||
|
JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>());
|
||||||
|
break;
|
||||||
|
case value_t::number_integer:
|
||||||
|
JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>());
|
||||||
|
break;
|
||||||
|
case value_t::number_unsigned:
|
||||||
|
JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>());
|
||||||
|
break;
|
||||||
|
case value_t::string:
|
||||||
|
JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t&>());
|
||||||
|
break;
|
||||||
|
case value_t::binary:
|
||||||
|
JSONSerializer<other_binary_t>::to_json(*this, val.template get_ref<const other_binary_t&>());
|
||||||
|
break;
|
||||||
|
case value_t::null:
|
||||||
|
// m_data.m_type is already value_t::null
|
||||||
|
break;
|
||||||
|
case value_t::discarded:
|
||||||
|
m_data.m_type = value_t::discarded;
|
||||||
|
break;
|
||||||
|
case value_t::object: // LCOV_EXCL_LINE
|
||||||
|
case value_t::array: // LCOV_EXCL_LINE
|
||||||
|
default: // LCOV_EXCL_LINE
|
||||||
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// scratch space for the converted elements of the arrays that
|
||||||
|
/// @ref convert_iteratively has yet to create
|
||||||
|
using convert_scratch_t = std::vector<basic_json, AllocatorType<basic_json>>;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief create the object or array @a val converted into this null value
|
||||||
|
|
||||||
|
Its converted elements are the last `val.size()` entries of @a elements (an
|
||||||
|
array) or of @a members (an object); they are moved into the container in
|
||||||
|
one go and then removed.
|
||||||
|
*/
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
void convert_level(const BasicJsonType& val, convert_scratch_t& elements, copy_scratch_t& members)
|
||||||
|
{
|
||||||
|
if (val.is_object())
|
||||||
|
{
|
||||||
|
const auto first = members.end() - static_cast<typename copy_scratch_t::difference_type>(val.size());
|
||||||
|
m_data.m_value.object = create<object_t>(std::make_move_iterator(first),
|
||||||
|
std::make_move_iterator(members.end()));
|
||||||
|
// only now that the object exists may this stop being a null value
|
||||||
|
m_data.m_type = value_t::object;
|
||||||
|
members.erase(first, members.end());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto first = elements.end() - static_cast<typename convert_scratch_t::difference_type>(val.size());
|
||||||
|
m_data.m_value.array = create<array_t>(std::make_move_iterator(first),
|
||||||
|
std::make_move_iterator(elements.end()));
|
||||||
|
// only now that the array exists may this stop being a null value
|
||||||
|
m_data.m_type = value_t::array;
|
||||||
|
elements.erase(first, elements.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
set_parents();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief convert the object or array @a val of another specialization into
|
||||||
|
this null value without recursing
|
||||||
|
|
||||||
|
The containers whose conversion has begun are kept on an explicit stack
|
||||||
|
rather than on the call stack. Unlike @ref copy_iteratively, this builds
|
||||||
|
every container from the bottom up: all its elements are converted first,
|
||||||
|
and the container is then created from them in one go, the way the range
|
||||||
|
constructor that converts the levels above the bound does. The two object
|
||||||
|
types need not enumerate their members in the same order, so the members
|
||||||
|
could not be paired up by position anyway, and building from a range keeps
|
||||||
|
what the range constructor does with keys that become equal on conversion.
|
||||||
|
|
||||||
|
Every value is complete before it is handed on, and a container gets its
|
||||||
|
type only once it exists, so whatever throws, every value left behind can
|
||||||
|
be destroyed.
|
||||||
|
*/
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
void convert_iteratively(const BasicJsonType& val)
|
||||||
|
{
|
||||||
|
using other_const_iterator = typename BasicJsonType::const_iterator;
|
||||||
|
|
||||||
|
// the containers whose conversion has begun, innermost last, each with
|
||||||
|
// its element to convert next
|
||||||
|
std::vector<std::pair<const BasicJsonType*, other_const_iterator>> pending;
|
||||||
|
|
||||||
|
// the converted elements of the pending arrays and the converted
|
||||||
|
// members of the pending objects, those of the innermost one last
|
||||||
|
convert_scratch_t elements;
|
||||||
|
copy_scratch_t members;
|
||||||
|
|
||||||
|
pending.emplace_back(&val, val.cbegin());
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
const BasicJsonType& container = *pending.back().first;
|
||||||
|
// a copy, as descending below can reallocate pending; the
|
||||||
|
// iterator kept in pending is only advanced through pending.back()
|
||||||
|
const other_const_iterator next = pending.back().second;
|
||||||
|
|
||||||
|
if (next != container.cend())
|
||||||
|
{
|
||||||
|
if (next->is_structured())
|
||||||
|
{
|
||||||
|
// convert its elements first; next stays where it is until
|
||||||
|
// the converted container is handed back to this one
|
||||||
|
pending.emplace_back(&*next, next->cbegin());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the converting constructor does not descend into this value
|
||||||
|
if (container.is_object())
|
||||||
|
{
|
||||||
|
members.emplace_back(next.key(), *next);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
elements.emplace_back(*next);
|
||||||
|
}
|
||||||
|
++pending.back().second;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// all elements of the container are converted: create it
|
||||||
|
pending.pop_back();
|
||||||
|
|
||||||
|
if (pending.empty())
|
||||||
|
{
|
||||||
|
convert_level(container, elements, members);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
basic_json converted;
|
||||||
|
converted.convert_level(container, elements, members);
|
||||||
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
|
converted.start_position = container.start_pos();
|
||||||
|
converted.end_position = container.end_pos();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// hand it to the container it is an element of
|
||||||
|
if (pending.back().first->is_object())
|
||||||
|
{
|
||||||
|
members.emplace_back(pending.back().second.key(), std::move(converted));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
elements.push_back(std::move(converted));
|
||||||
|
}
|
||||||
|
++pending.back().second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief convert the object or array @a val of another specialization into
|
||||||
|
this null value
|
||||||
|
|
||||||
|
Converting a container converts its elements, so a value nested deeply
|
||||||
|
enough used to exhaust the call stack. The descent is bounded here as in
|
||||||
|
@ref copy_structured: the first `detail::recursion_depth_limit()` levels
|
||||||
|
are converted by the containers' range constructors, just as they always were,
|
||||||
|
and anything below that is converted without the call stack by
|
||||||
|
@ref convert_iteratively.
|
||||||
|
|
||||||
|
@sa https://github.com/nlohmann/json/issues/5650
|
||||||
|
*/
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
void convert_structured(const BasicJsonType& val)
|
||||||
|
{
|
||||||
|
const nesting_depth_guard guard;
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(guard.okay()))
|
||||||
|
{
|
||||||
|
// every element comes back to the converting constructor
|
||||||
|
if (val.is_object())
|
||||||
|
{
|
||||||
|
using other_object_t = typename BasicJsonType::object_t;
|
||||||
|
JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t&>());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using other_array_t = typename BasicJsonType::array_t;
|
||||||
|
JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
convert_iteratively(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// the result of comparing two values, including values that cannot be
|
/// the result of comparing two values, including values that cannot be
|
||||||
/// ordered at all, such as a discarded value or a NaN
|
/// ordered at all, such as a discarded value or a NaN
|
||||||
@@ -1597,7 +1812,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
template < typename CompatibleType,
|
template < typename CompatibleType,
|
||||||
typename U = detail::uncvref_t<CompatibleType>,
|
typename U = detail::uncvref_t<CompatibleType>,
|
||||||
detail::enable_if_t <
|
detail::enable_if_t <
|
||||||
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 >
|
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value
|
||||||
|
#if JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||||
|
// see https://github.com/nlohmann/json/issues/2226
|
||||||
|
&& !detail::is_basic_json_reference_tuple<basic_json_t, U>::value
|
||||||
|
#endif
|
||||||
|
, int > = 0 >
|
||||||
basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
|
basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
|
||||||
JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
|
JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
|
||||||
std::forward<CompatibleType>(val))))
|
std::forward<CompatibleType>(val))))
|
||||||
@@ -1618,49 +1838,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
end_position(val.end_pos())
|
end_position(val.end_pos())
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
using other_boolean_t = typename BasicJsonType::boolean_t;
|
if (val.is_structured())
|
||||||
using other_number_float_t = typename BasicJsonType::number_float_t;
|
|
||||||
using other_number_integer_t = typename BasicJsonType::number_integer_t;
|
|
||||||
using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
|
||||||
using other_string_t = typename BasicJsonType::string_t;
|
|
||||||
using other_object_t = typename BasicJsonType::object_t;
|
|
||||||
using other_array_t = typename BasicJsonType::array_t;
|
|
||||||
using other_binary_t = typename BasicJsonType::binary_t;
|
|
||||||
|
|
||||||
switch (val.type())
|
|
||||||
{
|
{
|
||||||
case value_t::boolean:
|
convert_structured(val);
|
||||||
JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>());
|
}
|
||||||
break;
|
else
|
||||||
case value_t::number_float:
|
{
|
||||||
JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>());
|
convert_leaf(val);
|
||||||
break;
|
|
||||||
case value_t::number_integer:
|
|
||||||
JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>());
|
|
||||||
break;
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>());
|
|
||||||
break;
|
|
||||||
case value_t::string:
|
|
||||||
JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t&>());
|
|
||||||
break;
|
|
||||||
case value_t::object:
|
|
||||||
JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t&>());
|
|
||||||
break;
|
|
||||||
case value_t::array:
|
|
||||||
JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>());
|
|
||||||
break;
|
|
||||||
case value_t::binary:
|
|
||||||
JSONSerializer<other_binary_t>::to_json(*this, val.template get_ref<const other_binary_t&>());
|
|
||||||
break;
|
|
||||||
case value_t::null:
|
|
||||||
*this = nullptr;
|
|
||||||
break;
|
|
||||||
case value_t::discarded:
|
|
||||||
m_data.m_type = value_t::discarded;
|
|
||||||
break;
|
|
||||||
default: // LCOV_EXCL_LINE
|
|
||||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
|
||||||
}
|
}
|
||||||
JSON_ASSERT(m_data.m_type == val.type());
|
JSON_ASSERT(m_data.m_type == val.type());
|
||||||
|
|
||||||
@@ -5035,7 +5219,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||||
return format == input_format_t::json
|
return format == input_format_t::json
|
||||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict);
|
: detail::binary_reader<basic_json, decltype(ia), SAX, true>(std::move(ia), format).sax_parse(sax, strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief generate SAX events (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
/// @brief generate SAX events (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
||||||
@@ -5052,7 +5236,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||||
return format == input_format_t::json
|
return format == input_format_t::json
|
||||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict);
|
: detail::binary_reader<basic_json, decltype(ia), SAX, true>(std::move(ia), format).sax_parse(sax, strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief generate SAX events
|
/// @brief generate SAX events
|
||||||
@@ -5074,7 +5258,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict);
|
: detail::binary_reader<basic_json, decltype(ia), SAX, true>(std::move(ia), format).sax_parse(sax, strict);
|
||||||
}
|
}
|
||||||
#ifndef JSON_NO_IO
|
#ifndef JSON_NO_IO
|
||||||
/// @brief deserialize from stream
|
/// @brief deserialize from stream
|
||||||
@@ -5372,7 +5556,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(&sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved]
|
||||||
{
|
{
|
||||||
result = value_t::discarded;
|
result = value_t::discarded;
|
||||||
}
|
}
|
||||||
@@ -5392,7 +5576,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(&sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved]
|
||||||
{
|
{
|
||||||
result = value_t::discarded;
|
result = value_t::discarded;
|
||||||
}
|
}
|
||||||
@@ -5421,7 +5605,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
auto ia = i.get();
|
auto ia = i.get();
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(&sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved]
|
||||||
{
|
{
|
||||||
result = value_t::discarded;
|
result = value_t::discarded;
|
||||||
}
|
}
|
||||||
@@ -5439,7 +5623,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
{
|
{
|
||||||
result = value_t::discarded;
|
result = value_t::discarded;
|
||||||
}
|
}
|
||||||
@@ -5458,7 +5642,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
{
|
{
|
||||||
result = value_t::discarded;
|
result = value_t::discarded;
|
||||||
}
|
}
|
||||||
@@ -5485,7 +5669,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
auto ia = i.get();
|
auto ia = i.get();
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
{
|
{
|
||||||
result = value_t::discarded;
|
result = value_t::discarded;
|
||||||
}
|
}
|
||||||
@@ -5503,7 +5687,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
{
|
{
|
||||||
result = value_t::discarded;
|
result = value_t::discarded;
|
||||||
}
|
}
|
||||||
@@ -5522,7 +5706,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
{
|
{
|
||||||
result = value_t::discarded;
|
result = value_t::discarded;
|
||||||
}
|
}
|
||||||
@@ -5549,7 +5733,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
auto ia = i.get();
|
auto ia = i.get();
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
{
|
{
|
||||||
result = value_t::discarded;
|
result = value_t::discarded;
|
||||||
}
|
}
|
||||||
@@ -5567,7 +5751,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bjdata).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
{
|
{
|
||||||
result = value_t::discarded;
|
result = value_t::discarded;
|
||||||
}
|
}
|
||||||
@@ -5586,7 +5770,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bjdata).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
{
|
{
|
||||||
result = value_t::discarded;
|
result = value_t::discarded;
|
||||||
}
|
}
|
||||||
@@ -5604,7 +5788,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bon8).sax_parse(input_format_t::bon8, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bon8).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
{
|
{
|
||||||
result = value_t::discarded;
|
result = value_t::discarded;
|
||||||
}
|
}
|
||||||
@@ -5623,7 +5807,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bon8).sax_parse(input_format_t::bon8, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bon8).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
{
|
{
|
||||||
result = value_t::discarded;
|
result = value_t::discarded;
|
||||||
}
|
}
|
||||||
@@ -5641,7 +5825,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
{
|
{
|
||||||
result = value_t::discarded;
|
result = value_t::discarded;
|
||||||
}
|
}
|
||||||
@@ -5660,7 +5844,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
{
|
{
|
||||||
result = value_t::discarded;
|
result = value_t::discarded;
|
||||||
}
|
}
|
||||||
@@ -5687,7 +5871,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
auto ia = i.get();
|
auto ia = i.get();
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(&sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
{
|
{
|
||||||
result = value_t::discarded;
|
result = value_t::discarded;
|
||||||
}
|
}
|
||||||
|
|||||||
+3867
-2013
File diff suppressed because it is too large
Load Diff
@@ -47,6 +47,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
|
||||||
|
|
||||||
|
|||||||
@@ -279,10 +279,6 @@ if(json_32bit_test_only)
|
|||||||
elseif(NOT json_32bit_test)
|
elseif(NOT json_32bit_test)
|
||||||
list(FILTER files EXCLUDE REGEX src/unit-32bit.cpp)
|
list(FILTER files EXCLUDE REGEX src/unit-32bit.cpp)
|
||||||
endif()
|
endif()
|
||||||
if(NOT JSON_MultipleHeaders)
|
|
||||||
# the internal headers of json_view are not part of a single header yet
|
|
||||||
list(FILTER files EXCLUDE REGEX src/unit-json_view_builder.cpp)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
foreach(file ${files})
|
foreach(file ${files})
|
||||||
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
|
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
|
||||||
|
|||||||
@@ -1,599 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | 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
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array> // array
|
|
||||||
#include <cstdint> // uint32_t, uint64_t
|
|
||||||
|
|
||||||
// Number tokens that are hard to round correctly, with the IEEE-754 binary64
|
|
||||||
// and binary32 bits of their correctly rounded values (ties to even; infinity
|
|
||||||
// for an overflow, a signed zero for an underflow).
|
|
||||||
//
|
|
||||||
// For doubles and floats around 0, the smallest normal number, 1, 2^24, 2^53,
|
|
||||||
// 0.1, and the largest finite number, and for random ones, the exact midpoint
|
|
||||||
// m to the next number gives: m, m with one unit more and less in the last
|
|
||||||
// digit, m with "01" and "0...01" appended, m with trailing zeros, and m cut
|
|
||||||
// after 17 to 30 digits (rounded down and up, so that the rounding is decided
|
|
||||||
// after the 19th digit), in fixed and exponent notation, 30% of them negative.
|
|
||||||
// Tokens longer than 80 characters are left out, except for four of 700 digits
|
|
||||||
// and more. Zeros, underflow, overflow, huge exponents, and integers beyond 64
|
|
||||||
// bits complete the set. Of the 508 tokens, 134 (as double) and 150 (as
|
|
||||||
// float) need the exact comparison with the midpoint (detail::digit_comparison()).
|
|
||||||
//
|
|
||||||
// The expected bits were computed with exact rational arithmetic in Python
|
|
||||||
// (fractions.Fraction) and cross-checked with Python's float(); strtod_l and
|
|
||||||
// strtof_l of Apple's libc and of glibc agree. Generated by
|
|
||||||
// compact_hard_cases.py 5 (with hard_cases.py), see the pull request that
|
|
||||||
// added this file.
|
|
||||||
|
|
||||||
namespace float_hard_cases
|
|
||||||
{
|
|
||||||
|
|
||||||
struct hard_case
|
|
||||||
{
|
|
||||||
const char* token;
|
|
||||||
std::uint64_t bits64;
|
|
||||||
std::uint32_t bits32;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline const std::array<hard_case, 508>& cases()
|
|
||||||
{
|
|
||||||
static const std::array<hard_case, 508> table =
|
|
||||||
{
|
|
||||||
{
|
|
||||||
{"-2.4703282292062327e-324", 0x8000000000000000u, 0x80000000u},
|
|
||||||
{"24703282292062328e-340", 0x0000000000000001u, 0x00000000u},
|
|
||||||
{"247032822920623272e-341", 0x0000000000000000u, 0x00000000u},
|
|
||||||
{"-0.2470328229206232721e-323", 0x8000000000000001u, 0x80000000u},
|
|
||||||
{"-0.24703282292062327208e-323", 0x8000000000000000u, 0x80000000u},
|
|
||||||
{"-2.4703282292062327209e-324", 0x8000000000000001u, 0x80000000u},
|
|
||||||
{"2.47032822920623272088e-324", 0x0000000000000000u, 0x00000000u},
|
|
||||||
{"247032822920623272089e-344", 0x0000000000000001u, 0x00000000u},
|
|
||||||
{"-247032822920623272088284396434e-353", 0x8000000000000000u, 0x80000000u},
|
|
||||||
{"0.247032822920623272088284396435e-323", 0x0000000000000001u, 0x00000000u},
|
|
||||||
{"-74109846876186981e-340", 0x8000000000000001u, 0x80000000u},
|
|
||||||
{"0.74109846876186982e-323", 0x0000000000000002u, 0x00000000u},
|
|
||||||
{"-0.7410984687618698162e-323", 0x8000000000000001u, 0x80000000u},
|
|
||||||
{"-7.410984687618698163e-324", 0x8000000000000002u, 0x80000000u},
|
|
||||||
{"7.4109846876186981626e-324", 0x0000000000000001u, 0x00000000u},
|
|
||||||
{"-74109846876186981627e-343", 0x8000000000000002u, 0x80000000u},
|
|
||||||
{"-741098468761869816264e-344", 0x8000000000000001u, 0x80000000u},
|
|
||||||
{"0.741098468761869816265e-323", 0x0000000000000002u, 0x00000000u},
|
|
||||||
{"0.741098468761869816264853189302e-323", 0x0000000000000001u, 0x00000000u},
|
|
||||||
{"-7.41098468761869816264853189303e-324", 0x8000000000000002u, 0x80000000u},
|
|
||||||
{"0.22250738585072006e-307", 0x000FFFFFFFFFFFFEu, 0x00000000u},
|
|
||||||
{"2.2250738585072007e-308", 0x000FFFFFFFFFFFFFu, 0x00000000u},
|
|
||||||
{"2.225073858507200641e-308", 0x000FFFFFFFFFFFFEu, 0x00000000u},
|
|
||||||
{"-2225073858507200642e-326", 0x800FFFFFFFFFFFFFu, 0x80000000u},
|
|
||||||
{"22250738585072006419e-327", 0x000FFFFFFFFFFFFEu, 0x00000000u},
|
|
||||||
{"0.2225073858507200642e-307", 0x000FFFFFFFFFFFFFu, 0x00000000u},
|
|
||||||
{"0.222507385850720064199e-307", 0x000FFFFFFFFFFFFEu, 0x00000000u},
|
|
||||||
{"2.225073858507200642e-308", 0x000FFFFFFFFFFFFFu, 0x00000000u},
|
|
||||||
{"-2.22507385850720064199176395546e-308", 0x800FFFFFFFFFFFFEu, 0x80000000u},
|
|
||||||
{"222507385850720064199176395547e-337", 0x000FFFFFFFFFFFFFu, 0x00000000u},
|
|
||||||
{"-2.2250738585072011e-308", 0x800FFFFFFFFFFFFFu, 0x80000000u},
|
|
||||||
{"-22250738585072012e-324", 0x8010000000000000u, 0x80000000u},
|
|
||||||
{"-2225073858507201136e-326", 0x800FFFFFFFFFFFFFu, 0x80000000u},
|
|
||||||
{"0.2225073858507201137e-307", 0x0010000000000000u, 0x00000000u},
|
|
||||||
{"0.2225073858507201136e-307", 0x000FFFFFFFFFFFFFu, 0x00000000u},
|
|
||||||
{"-2.2250738585072011361e-308", 0x8010000000000000u, 0x80000000u},
|
|
||||||
{"2.22507385850720113605e-308", 0x000FFFFFFFFFFFFFu, 0x00000000u},
|
|
||||||
{"222507385850720113606e-328", 0x0010000000000000u, 0x00000000u},
|
|
||||||
{"22250738585072011360574097967e-336", 0x000FFFFFFFFFFFFFu, 0x00000000u},
|
|
||||||
{"0.222507385850720113605740979671e-307", 0x0010000000000000u, 0x00000000u},
|
|
||||||
{"22250738585072016e-324", 0x0010000000000000u, 0x00000000u},
|
|
||||||
{"0.22250738585072017e-307", 0x0010000000000001u, 0x00000000u},
|
|
||||||
{"0.222507385850720163e-307", 0x0010000000000000u, 0x00000000u},
|
|
||||||
{"2.225073858507201631e-308", 0x0010000000000001u, 0x00000000u},
|
|
||||||
{"-2.2250738585072016301e-308", 0x8010000000000000u, 0x80000000u},
|
|
||||||
{"22250738585072016302e-327", 0x0010000000000001u, 0x00000000u},
|
|
||||||
{"-222507385850720163012e-328", 0x8010000000000000u, 0x80000000u},
|
|
||||||
{"0.222507385850720163013e-307", 0x0010000000000001u, 0x00000000u},
|
|
||||||
{"0.222507385850720163012305563795e-307", 0x0010000000000000u, 0x00000000u},
|
|
||||||
{"-2.22507385850720163012305563796e-308", 0x8010000000000001u, 0x80000000u},
|
|
||||||
{"0.17976931348623156E+309", 0x7FEFFFFFFFFFFFFEu, 0x7F800000u},
|
|
||||||
{"1.7976931348623157e308", 0x7FEFFFFFFFFFFFFFu, 0x7F800000u},
|
|
||||||
{"1.797693134862315608e308", 0x7FEFFFFFFFFFFFFEu, 0x7F800000u},
|
|
||||||
{"-1797693134862315609e290", 0xFFEFFFFFFFFFFFFFu, 0xFF800000u},
|
|
||||||
{"-17976931348623156083e289", 0xFFEFFFFFFFFFFFFEu, 0xFF800000u},
|
|
||||||
{"-0.17976931348623156084E+309", 0xFFEFFFFFFFFFFFFFu, 0xFF800000u},
|
|
||||||
{"0.179769313486231560835E+309", 0x7FEFFFFFFFFFFFFEu, 0x7F800000u},
|
|
||||||
{"-1.79769313486231560836e308", 0xFFEFFFFFFFFFFFFFu, 0xFF800000u},
|
|
||||||
{"1.79769313486231560835325876058e308", 0x7FEFFFFFFFFFFFFEu, 0x7F800000u},
|
|
||||||
{"179769313486231560835325876059e279", 0x7FEFFFFFFFFFFFFFu, 0x7F800000u},
|
|
||||||
{"1.7976931348623158e308", 0x7FEFFFFFFFFFFFFFu, 0x7F800000u},
|
|
||||||
{"17976931348623159e292", 0x7FF0000000000000u, 0x7F800000u},
|
|
||||||
{"1797693134862315807e290", 0x7FEFFFFFFFFFFFFFu, 0x7F800000u},
|
|
||||||
{"0.1797693134862315808E+309", 0x7FF0000000000000u, 0x7F800000u},
|
|
||||||
{"0.17976931348623158079E+309", 0x7FEFFFFFFFFFFFFFu, 0x7F800000u},
|
|
||||||
{"-1.797693134862315808e308", 0xFFF0000000000000u, 0xFF800000u},
|
|
||||||
{"1.79769313486231580793e308", 0x7FEFFFFFFFFFFFFFu, 0x7F800000u},
|
|
||||||
{"179769313486231580794e288", 0x7FF0000000000000u, 0x7F800000u},
|
|
||||||
{"179769313486231580793728971405e279", 0x7FEFFFFFFFFFFFFFu, 0x7F800000u},
|
|
||||||
{"-0.179769313486231580793728971406E+309", 0xFFF0000000000000u, 0xFF800000u},
|
|
||||||
{"100000000000000011102230246251565404236316680908203125e-53", 0x3FF0000000000000u, 0x3F800000u},
|
|
||||||
{"-1.00000000000000011102230246251565404236316680908203126", 0xBFF0000000000001u, 0xBF800000u},
|
|
||||||
{"1.00000000000000011102230246251565404236316680908203124e0", 0x3FF0000000000000u, 0x3F800000u},
|
|
||||||
{"10000000000000001110223024625156540423631668090820312501e-55", 0x3FF0000000000001u, 0x3F800000u},
|
|
||||||
{"1.00000000000000011102230246251565404236316680908203125000000000000000000001", 0x3FF0000000000001u, 0x3F800000u},
|
|
||||||
{"10000000000000001e-16", 0x3FF0000000000000u, 0x3F800000u},
|
|
||||||
{"1.0000000000000002", 0x3FF0000000000001u, 0x3F800000u},
|
|
||||||
{"1.000000000000000111", 0x3FF0000000000000u, 0x3F800000u},
|
|
||||||
{"1.000000000000000112e0", 0x3FF0000000000001u, 0x3F800000u},
|
|
||||||
{"1.000000000000000111e0", 0x3FF0000000000000u, 0x3F800000u},
|
|
||||||
{"-10000000000000001111e-19", 0xBFF0000000000001u, 0xBF800000u},
|
|
||||||
{"-100000000000000011102e-20", 0xBFF0000000000000u, 0xBF800000u},
|
|
||||||
{"-1.00000000000000011103", 0xBFF0000000000001u, 0xBF800000u},
|
|
||||||
{"1.00000000000000011102230246251", 0x3FF0000000000000u, 0x3F800000u},
|
|
||||||
{"1.00000000000000011102230246252e0", 0x3FF0000000000001u, 0x3F800000u},
|
|
||||||
{"-0.999999999999999944488848768742172978818416595458984375", 0xBFF0000000000000u, 0xBF800000u},
|
|
||||||
{"-9.99999999999999944488848768742172978818416595458984376e-1", 0xBFF0000000000000u, 0xBF800000u},
|
|
||||||
{"999999999999999944488848768742172978818416595458984374e-54", 0x3FEFFFFFFFFFFFFFu, 0x3F800000u},
|
|
||||||
{"0.99999999999999994448884876874217297881841659545898437501", 0x3FF0000000000000u, 0x3F800000u},
|
|
||||||
{"9.99999999999999944488848768742172978818416595458984375000000000000000000001e-1", 0x3FF0000000000000u, 0x3F800000u},
|
|
||||||
{"-0.99999999999999994", 0xBFEFFFFFFFFFFFFFu, 0xBF800000u},
|
|
||||||
{"9.9999999999999995e-1", 0x3FF0000000000000u, 0x3F800000u},
|
|
||||||
{"9.999999999999999444e-1", 0x3FEFFFFFFFFFFFFFu, 0x3F800000u},
|
|
||||||
{"9999999999999999445e-19", 0x3FF0000000000000u, 0x3F800000u},
|
|
||||||
{"99999999999999994448e-20", 0x3FEFFFFFFFFFFFFFu, 0x3F800000u},
|
|
||||||
{"-0.99999999999999994449", 0xBFF0000000000000u, 0xBF800000u},
|
|
||||||
{"0.999999999999999944488", 0x3FEFFFFFFFFFFFFFu, 0x3F800000u},
|
|
||||||
{"-9.99999999999999944489e-1", 0xBFF0000000000000u, 0xBF800000u},
|
|
||||||
{"9.99999999999999944488848768742e-1", 0x3FEFFFFFFFFFFFFFu, 0x3F800000u},
|
|
||||||
{"999999999999999944488848768743e-30", 0x3FF0000000000000u, 0x3F800000u},
|
|
||||||
{"-9.007199254740993e15", 0xC340000000000000u, 0xDA000000u},
|
|
||||||
{"9007199254740994e0", 0x4340000000000001u, 0x5A000000u},
|
|
||||||
{"9007199254740992", 0x4340000000000000u, 0x5A000000u},
|
|
||||||
{"9.00719925474099301e15", 0x4340000000000001u, 0x5A000000u},
|
|
||||||
{"9007199254740993000000000000000000001e-21", 0x4340000000000001u, 0x5A000000u},
|
|
||||||
{"-9007199254740993.000000000000000000000000000000", 0xC340000000000000u, 0xDA000000u},
|
|
||||||
{"90071992547409915e-1", 0x4340000000000000u, 0x5A000000u},
|
|
||||||
{"-9007199254740991.6", 0xC340000000000000u, 0xDA000000u},
|
|
||||||
{"9.0071992547409914e15", 0x433FFFFFFFFFFFFFu, 0x5A000000u},
|
|
||||||
{"-9007199254740991501e-3", 0xC340000000000000u, 0xDA000000u},
|
|
||||||
{"9007199254740991.5000000000000000000001", 0x4340000000000000u, 0x5A000000u},
|
|
||||||
{"9.0071992547409915000000000000000000000000000000e15", 0x4340000000000000u, 0x5A000000u},
|
|
||||||
{"0.100000000000000012490009027033011079765856266021728515625", 0x3FB999999999999Au, 0x3DCCCCCDu},
|
|
||||||
{"1.00000000000000012490009027033011079765856266021728515626e-1", 0x3FB999999999999Bu, 0x3DCCCCCDu},
|
|
||||||
{"100000000000000012490009027033011079765856266021728515624e-57", 0x3FB999999999999Au, 0x3DCCCCCDu},
|
|
||||||
{"0.10000000000000001249000902703301107976585626602172851562501", 0x3FB999999999999Bu, 0x3DCCCCCDu},
|
|
||||||
{"0.10000000000000001", 0x3FB999999999999Au, 0x3DCCCCCDu},
|
|
||||||
{"1.0000000000000002e-1", 0x3FB999999999999Bu, 0x3DCCCCCDu},
|
|
||||||
{"-1.000000000000000124e-1", 0xBFB999999999999Au, 0xBDCCCCCDu},
|
|
||||||
{"1000000000000000125e-19", 0x3FB999999999999Bu, 0x3DCCCCCDu},
|
|
||||||
{"-10000000000000001249e-20", 0xBFB999999999999Au, 0xBDCCCCCDu},
|
|
||||||
{"0.1000000000000000125", 0x3FB999999999999Bu, 0x3DCCCCCDu},
|
|
||||||
{"0.10000000000000001249", 0x3FB999999999999Au, 0x3DCCCCCDu},
|
|
||||||
{"1.00000000000000012491e-1", 0x3FB999999999999Bu, 0x3DCCCCCDu},
|
|
||||||
{"1.00000000000000012490009027033e-1", 0x3FB999999999999Au, 0x3DCCCCCDu},
|
|
||||||
{"100000000000000012490009027034e-30", 0x3FB999999999999Bu, 0x3DCCCCCDu},
|
|
||||||
{"2.45134755833537796875e14", 0x42EBDE5C4164D83Au, 0x575EF2E2u},
|
|
||||||
{"-245134755833537796876e-6", 0xC2EBDE5C4164D83Au, 0xD75EF2E2u},
|
|
||||||
{"-245134755833537.796874", 0xC2EBDE5C4164D839u, 0xD75EF2E2u},
|
|
||||||
{"2.4513475583353779687501e14", 0x42EBDE5C4164D83Au, 0x575EF2E2u},
|
|
||||||
{"245134755833537796875000000000000000000001e-27", 0x42EBDE5C4164D83Au, 0x575EF2E2u},
|
|
||||||
{"245134755833537.796875000000000000000000000000000000", 0x42EBDE5C4164D83Au, 0x575EF2E2u},
|
|
||||||
{"2.4513475583353779e14", 0x42EBDE5C4164D839u, 0x575EF2E2u},
|
|
||||||
{"2451347558335378e-1", 0x42EBDE5C4164D83Au, 0x575EF2E2u},
|
|
||||||
{"2451347558335377968e-4", 0x42EBDE5C4164D839u, 0x575EF2E2u},
|
|
||||||
{"245134755833537.7969", 0x42EBDE5C4164D83Au, 0x575EF2E2u},
|
|
||||||
{"245134755833537.79687", 0x42EBDE5C4164D839u, 0x575EF2E2u},
|
|
||||||
{"2.4513475583353779688e14", 0x42EBDE5C4164D83Au, 0x575EF2E2u},
|
|
||||||
{"181510327827821147441864013671875e-23", 0x41DB0C11CB91CE38u, 0x4ED8608Eu},
|
|
||||||
{"-1815103278.27821147441864013671876", 0xC1DB0C11CB91CE38u, 0xCED8608Eu},
|
|
||||||
{"1.81510327827821147441864013671874e9", 0x41DB0C11CB91CE37u, 0x4ED8608Eu},
|
|
||||||
{"18151032782782114744186401367187501e-25", 0x41DB0C11CB91CE38u, 0x4ED8608Eu},
|
|
||||||
{"1815103278.27821147441864013671875000000000000000000001", 0x41DB0C11CB91CE38u, 0x4ED8608Eu},
|
|
||||||
{"-1.81510327827821147441864013671875000000000000000000000000000000e9", 0xC1DB0C11CB91CE38u, 0xCED8608Eu},
|
|
||||||
{"18151032782782114e-7", 0x41DB0C11CB91CE37u, 0x4ED8608Eu},
|
|
||||||
{"1815103278.2782115", 0x41DB0C11CB91CE38u, 0x4ED8608Eu},
|
|
||||||
{"1815103278.278211474", 0x41DB0C11CB91CE37u, 0x4ED8608Eu},
|
|
||||||
{"-1.815103278278211475e9", 0xC1DB0C11CB91CE38u, 0xCED8608Eu},
|
|
||||||
{"1.8151032782782114744e9", 0x41DB0C11CB91CE37u, 0x4ED8608Eu},
|
|
||||||
{"-18151032782782114745e-10", 0xC1DB0C11CB91CE38u, 0xCED8608Eu},
|
|
||||||
{"181510327827821147441e-11", 0x41DB0C11CB91CE37u, 0x4ED8608Eu},
|
|
||||||
{"1815103278.27821147442", 0x41DB0C11CB91CE38u, 0x4ED8608Eu},
|
|
||||||
{"1815103278.27821147441864013671", 0x41DB0C11CB91CE37u, 0x4ED8608Eu},
|
|
||||||
{"1.81510327827821147441864013672e9", 0x41DB0C11CB91CE38u, 0x4ED8608Eu},
|
|
||||||
{"3809325632181785344", 0x43CA6EB8BD69FE2Au, 0x5E5375C6u},
|
|
||||||
{"3.809325632181785345e18", 0x43CA6EB8BD69FE2Au, 0x5E5375C6u},
|
|
||||||
{"3809325632181785343e0", 0x43CA6EB8BD69FE29u, 0x5E5375C6u},
|
|
||||||
{"3809325632181785344.01", 0x43CA6EB8BD69FE2Au, 0x5E5375C6u},
|
|
||||||
{"3.809325632181785344000000000000000000001e18", 0x43CA6EB8BD69FE2Au, 0x5E5375C6u},
|
|
||||||
{"3809325632181785344000000000000000000000000000000e-30", 0x43CA6EB8BD69FE2Au, 0x5E5375C6u},
|
|
||||||
{"3809325632181785300", 0x43CA6EB8BD69FE29u, 0x5E5375C6u},
|
|
||||||
{"3.8093256321817854e18", 0x43CA6EB8BD69FE2Au, 0x5E5375C6u},
|
|
||||||
{"4.046966549916366943359375e12", 0x428D7210076CE2F0u, 0x546B9080u},
|
|
||||||
{"4046966549916366943359376e-12", 0x428D7210076CE2F0u, 0x546B9080u},
|
|
||||||
{"4046966549916.366943359374", 0x428D7210076CE2EFu, 0x546B9080u},
|
|
||||||
{"4.04696654991636694335937501e12", 0x428D7210076CE2F0u, 0x546B9080u},
|
|
||||||
{"4046966549916366943359375000000000000000000001e-33", 0x428D7210076CE2F0u, 0x546B9080u},
|
|
||||||
{"-4046966549916.366943359375000000000000000000000000000000", 0xC28D7210076CE2F0u, 0xD46B9080u},
|
|
||||||
{"4.0469665499163669e12", 0x428D7210076CE2EFu, 0x546B9080u},
|
|
||||||
{"4046966549916367e-3", 0x428D7210076CE2F0u, 0x546B9080u},
|
|
||||||
{"4046966549916366943e-6", 0x428D7210076CE2EFu, 0x546B9080u},
|
|
||||||
{"4046966549916.366944", 0x428D7210076CE2F0u, 0x546B9080u},
|
|
||||||
{"4046966549916.3669433", 0x428D7210076CE2EFu, 0x546B9080u},
|
|
||||||
{"4.0469665499163669434e12", 0x428D7210076CE2F0u, 0x546B9080u},
|
|
||||||
{"-4.04696654991636694335e12", 0xC28D7210076CE2EFu, 0xD46B9080u},
|
|
||||||
{"404696654991636694336e-8", 0x428D7210076CE2F0u, 0x546B9080u},
|
|
||||||
{"28093802557000874e154", 0x63529C3B77330BDBu, 0x7F800000u},
|
|
||||||
{"0.28093802557000875E+171", 0x63529C3B77330BDCu, 0x7F800000u},
|
|
||||||
{"0.2809380255700087447E+171", 0x63529C3B77330BDBu, 0x7F800000u},
|
|
||||||
{"2.809380255700087448e170", 0x63529C3B77330BDCu, 0x7F800000u},
|
|
||||||
{"2.8093802557000874472e170", 0x63529C3B77330BDBu, 0x7F800000u},
|
|
||||||
{"28093802557000874473e151", 0x63529C3B77330BDCu, 0x7F800000u},
|
|
||||||
{"280938025570008744728e150", 0x63529C3B77330BDBu, 0x7F800000u},
|
|
||||||
{"-0.280938025570008744729E+171", 0xE3529C3B77330BDCu, 0xFF800000u},
|
|
||||||
{"-0.280938025570008744728403667979E+171", 0xE3529C3B77330BDBu, 0xFF800000u},
|
|
||||||
{"2.8093802557000874472840366798e170", 0x63529C3B77330BDCu, 0x7F800000u},
|
|
||||||
{"0.39523280297734525e-154", 0x1FE0F51BF17FD374u, 0x00000000u},
|
|
||||||
{"-3.9523280297734526e-155", 0x9FE0F51BF17FD375u, 0x80000000u},
|
|
||||||
{"3.952328029773452547e-155", 0x1FE0F51BF17FD374u, 0x00000000u},
|
|
||||||
{"3952328029773452548e-173", 0x1FE0F51BF17FD375u, 0x00000000u},
|
|
||||||
{"-39523280297734525478e-174", 0x9FE0F51BF17FD374u, 0x80000000u},
|
|
||||||
{"0.39523280297734525479e-154", 0x1FE0F51BF17FD375u, 0x00000000u},
|
|
||||||
{"-0.395232802977345254787e-154", 0x9FE0F51BF17FD374u, 0x80000000u},
|
|
||||||
{"-3.95232802977345254788e-155", 0x9FE0F51BF17FD375u, 0x80000000u},
|
|
||||||
{"-3.95232802977345254787245825501e-155", 0x9FE0F51BF17FD374u, 0x80000000u},
|
|
||||||
{"395232802977345254787245825502e-184", 0x1FE0F51BF17FD375u, 0x00000000u},
|
|
||||||
{"-1.0790205420931879e-276", 0x86A3209CA6233255u, 0x80000000u},
|
|
||||||
{"-1079020542093188e-291", 0x86A3209CA6233256u, 0x80000000u},
|
|
||||||
{"-1079020542093187947e-294", 0x86A3209CA6233255u, 0x80000000u},
|
|
||||||
{"0.1079020542093187948e-275", 0x06A3209CA6233256u, 0x00000000u},
|
|
||||||
{"0.1079020542093187947e-275", 0x06A3209CA6233255u, 0x00000000u},
|
|
||||||
{"1.0790205420931879471e-276", 0x06A3209CA6233256u, 0x00000000u},
|
|
||||||
{"1.07902054209318794701e-276", 0x06A3209CA6233255u, 0x00000000u},
|
|
||||||
{"-107902054209318794702e-296", 0x86A3209CA6233256u, 0x80000000u},
|
|
||||||
{"107902054209318794701153285302e-305", 0x06A3209CA6233255u, 0x00000000u},
|
|
||||||
{"-0.107902054209318794701153285303e-275", 0x86A3209CA6233256u, 0x80000000u},
|
|
||||||
{"58530471071351308e-228", 0x1413B446E6A16A3Bu, 0x00000000u},
|
|
||||||
{"-0.58530471071351309e-211", 0x9413B446E6A16A3Cu, 0x80000000u},
|
|
||||||
{"0.5853047107135130893e-211", 0x1413B446E6A16A3Bu, 0x00000000u},
|
|
||||||
{"-5.853047107135130894e-212", 0x9413B446E6A16A3Cu, 0x80000000u},
|
|
||||||
{"5.853047107135130893e-212", 0x1413B446E6A16A3Bu, 0x00000000u},
|
|
||||||
{"58530471071351308931e-231", 0x1413B446E6A16A3Cu, 0x00000000u},
|
|
||||||
{"-585304710713513089304e-232", 0x9413B446E6A16A3Bu, 0x80000000u},
|
|
||||||
{"0.585304710713513089305e-211", 0x1413B446E6A16A3Cu, 0x00000000u},
|
|
||||||
{"-0.585304710713513089304248824438e-211", 0x9413B446E6A16A3Bu, 0x80000000u},
|
|
||||||
{"5.85304710713513089304248824439e-212", 0x1413B446E6A16A3Cu, 0x00000000u},
|
|
||||||
{"0.19334214893983531e-78", 0x2F96ECBF1CFB10F6u, 0x00000000u},
|
|
||||||
{"1.9334214893983532e-79", 0x2F96ECBF1CFB10F7u, 0x00000000u},
|
|
||||||
{"1.933421489398353102e-79", 0x2F96ECBF1CFB10F6u, 0x00000000u},
|
|
||||||
{"1933421489398353103e-97", 0x2F96ECBF1CFB10F7u, 0x00000000u},
|
|
||||||
{"19334214893983531023e-98", 0x2F96ECBF1CFB10F6u, 0x00000000u},
|
|
||||||
{"0.19334214893983531024e-78", 0x2F96ECBF1CFB10F7u, 0x00000000u},
|
|
||||||
{"0.193342148939835310231e-78", 0x2F96ECBF1CFB10F6u, 0x00000000u},
|
|
||||||
{"1.93342148939835310232e-79", 0x2F96ECBF1CFB10F7u, 0x00000000u},
|
|
||||||
{"-1.93342148939835310231359014704e-79", 0xAF96ECBF1CFB10F6u, 0x80000000u},
|
|
||||||
{"193342148939835310231359014705e-108", 0x2F96ECBF1CFB10F7u, 0x00000000u},
|
|
||||||
{"2.9873358928024455e227", 0x6F2938807814E8A2u, 0x7F800000u},
|
|
||||||
{"29873358928024456e211", 0x6F2938807814E8A3u, 0x7F800000u},
|
|
||||||
{"298733589280244551e210", 0x6F2938807814E8A2u, 0x7F800000u},
|
|
||||||
{"0.2987335892802445511E+228", 0x6F2938807814E8A3u, 0x7F800000u},
|
|
||||||
{"0.29873358928024455109E+228", 0x6F2938807814E8A2u, 0x7F800000u},
|
|
||||||
{"2.987335892802445511e227", 0x6F2938807814E8A3u, 0x7F800000u},
|
|
||||||
{"-2.98733589280244551098e227", 0xEF2938807814E8A2u, 0xFF800000u},
|
|
||||||
{"-298733589280244551099e207", 0xEF2938807814E8A3u, 0xFF800000u},
|
|
||||||
{"298733589280244551098081559931e198", 0x6F2938807814E8A2u, 0x7F800000u},
|
|
||||||
{"0.298733589280244551098081559932E+228", 0x6F2938807814E8A3u, 0x7F800000u},
|
|
||||||
{"7.0064923216240853e-46", 0x3690000000000000u, 0x00000000u},
|
|
||||||
{"70064923216240854e-62", 0x3690000000000000u, 0x00000001u},
|
|
||||||
{"7006492321624085354e-64", 0x3690000000000000u, 0x00000000u},
|
|
||||||
{"-0.7006492321624085355e-45", 0xB690000000000000u, 0x80000001u},
|
|
||||||
{"0.70064923216240853546e-45", 0x3690000000000000u, 0x00000000u},
|
|
||||||
{"7.0064923216240853547e-46", 0x3690000000000000u, 0x00000001u},
|
|
||||||
{"7.00649232162408535461e-46", 0x3690000000000000u, 0x00000000u},
|
|
||||||
{"-700649232162408535462e-66", 0xB690000000000000u, 0x80000001u},
|
|
||||||
{"-700649232162408535461864791644e-75", 0xB690000000000000u, 0x80000000u},
|
|
||||||
{"0.700649232162408535461864791645e-45", 0x3690000000000000u, 0x00000001u},
|
|
||||||
{"21019476964872256e-61", 0x36A8000000000000u, 0x00000001u},
|
|
||||||
{"0.21019476964872257e-44", 0x36A8000000000000u, 0x00000002u},
|
|
||||||
{"-0.2101947696487225606e-44", 0xB6A8000000000000u, 0x80000001u},
|
|
||||||
{"-2.101947696487225607e-45", 0xB6A8000000000000u, 0x80000002u},
|
|
||||||
{"2.1019476964872256063e-45", 0x36A8000000000000u, 0x00000001u},
|
|
||||||
{"21019476964872256064e-64", 0x36A8000000000000u, 0x00000002u},
|
|
||||||
{"-210194769648722560638e-65", 0xB6A8000000000000u, 0x80000001u},
|
|
||||||
{"-0.210194769648722560639e-44", 0xB6A8000000000000u, 0x80000002u},
|
|
||||||
{"-0.210194769648722560638559437493e-44", 0xB6A8000000000000u, 0x80000001u},
|
|
||||||
{"2.10194769648722560638559437494e-45", 0x36A8000000000000u, 0x00000002u},
|
|
||||||
{"0.11754941406275178e-37", 0x380FFFFFA0000000u, 0x007FFFFEu},
|
|
||||||
{"1.1754941406275179e-38", 0x380FFFFFA0000000u, 0x007FFFFFu},
|
|
||||||
{"-1.175494140627517859e-38", 0xB80FFFFFA0000000u, 0x807FFFFEu},
|
|
||||||
{"117549414062751786e-55", 0x380FFFFFA0000000u, 0x007FFFFFu},
|
|
||||||
{"-11754941406275178592e-57", 0xB80FFFFFA0000000u, 0x807FFFFEu},
|
|
||||||
{"0.11754941406275178593e-37", 0x380FFFFFA0000000u, 0x007FFFFFu},
|
|
||||||
{"0.117549414062751785924e-37", 0x380FFFFFA0000000u, 0x007FFFFEu},
|
|
||||||
{"1.17549414062751785925e-38", 0x380FFFFFA0000000u, 0x007FFFFFu},
|
|
||||||
{"-1.17549414062751785924617589866e-38", 0xB80FFFFFA0000000u, 0x807FFFFEu},
|
|
||||||
{"117549414062751785924617589867e-67", 0x380FFFFFA0000000u, 0x007FFFFFu},
|
|
||||||
{"1.1754942807573642e-38", 0x380FFFFFDFFFFFFFu, 0x007FFFFFu},
|
|
||||||
{"11754942807573643e-54", 0x380FFFFFE0000000u, 0x00800000u},
|
|
||||||
{"1175494280757364291e-56", 0x380FFFFFE0000000u, 0x007FFFFFu},
|
|
||||||
{"0.1175494280757364292e-37", 0x380FFFFFE0000000u, 0x00800000u},
|
|
||||||
{"0.11754942807573642917e-37", 0x380FFFFFE0000000u, 0x007FFFFFu},
|
|
||||||
{"-1.1754942807573642918e-38", 0xB80FFFFFE0000000u, 0x80800000u},
|
|
||||||
{"-1.17549428075736429172e-38", 0xB80FFFFFE0000000u, 0x807FFFFFu},
|
|
||||||
{"-117549428075736429173e-58", 0xB80FFFFFE0000000u, 0x80800000u},
|
|
||||||
{"117549428075736429172788299103e-67", 0x380FFFFFE0000000u, 0x007FFFFFu},
|
|
||||||
{"0.117549428075736429172788299104e-37", 0x380FFFFFE0000000u, 0x00800000u},
|
|
||||||
{"11754944208872107e-54", 0x3810000010000000u, 0x00800000u},
|
|
||||||
{"-0.11754944208872108e-37", 0xB810000010000000u, 0x80800001u},
|
|
||||||
{"0.1175494420887210724e-37", 0x3810000010000000u, 0x00800000u},
|
|
||||||
{"1.175494420887210725e-38", 0x3810000010000000u, 0x00800001u},
|
|
||||||
{"1.1754944208872107242e-38", 0x3810000010000000u, 0x00800000u},
|
|
||||||
{"-11754944208872107243e-57", 0xB810000010000000u, 0x80800001u},
|
|
||||||
{"-11754944208872107242e-57", 0xB810000010000000u, 0x80800000u},
|
|
||||||
{"0.117549442088721072421e-37", 0x3810000010000000u, 0x00800001u},
|
|
||||||
{"0.11754944208872107242095900834e-37", 0x3810000010000000u, 0x00800000u},
|
|
||||||
{"-1.17549442088721072420959008341e-38", 0xB810000010000000u, 0x80800001u},
|
|
||||||
{"340282336497324057985868971510891282432", 0x47EFFFFFD0000000u, 0x7F7FFFFEu},
|
|
||||||
{"-3.40282336497324057985868971510891282433e38", 0xC7EFFFFFD0000000u, 0xFF7FFFFFu},
|
|
||||||
{"340282336497324057985868971510891282431e0", 0x47EFFFFFD0000000u, 0x7F7FFFFEu},
|
|
||||||
{"340282336497324057985868971510891282432.01", 0x47EFFFFFD0000000u, 0x7F7FFFFFu},
|
|
||||||
{"3.40282336497324057985868971510891282432000000000000000000001e38", 0x47EFFFFFD0000000u, 0x7F7FFFFFu},
|
|
||||||
{"340282336497324057985868971510891282432000000000000000000000000000000e-30", 0x47EFFFFFD0000000u, 0x7F7FFFFEu},
|
|
||||||
{"340282336497324050000000000000000000000", 0x47EFFFFFD0000000u, 0x7F7FFFFEu},
|
|
||||||
{"3.4028233649732406e38", 0x47EFFFFFD0000000u, 0x7F7FFFFFu},
|
|
||||||
{"3.402823364973240579e38", 0x47EFFFFFD0000000u, 0x7F7FFFFEu},
|
|
||||||
{"340282336497324058e21", 0x47EFFFFFD0000000u, 0x7F7FFFFFu},
|
|
||||||
{"34028233649732405798e19", 0x47EFFFFFD0000000u, 0x7F7FFFFEu},
|
|
||||||
{"340282336497324057990000000000000000000", 0x47EFFFFFD0000000u, 0x7F7FFFFFu},
|
|
||||||
{"340282336497324057985000000000000000000", 0x47EFFFFFD0000000u, 0x7F7FFFFEu},
|
|
||||||
{"3.40282336497324057986e38", 0x47EFFFFFD0000000u, 0x7F7FFFFFu},
|
|
||||||
{"3.4028233649732405798586897151e38", 0x47EFFFFFD0000000u, 0x7F7FFFFEu},
|
|
||||||
{"340282336497324057985868971511e9", 0x47EFFFFFD0000000u, 0x7F7FFFFFu},
|
|
||||||
{"3.40282356779733661637539395458142568448e38", 0x47EFFFFFF0000000u, 0x7F800000u},
|
|
||||||
{"-340282356779733661637539395458142568449e0", 0xC7EFFFFFF0000000u, 0xFF800000u},
|
|
||||||
{"340282356779733661637539395458142568447", 0x47EFFFFFF0000000u, 0x7F7FFFFFu},
|
|
||||||
{"3.4028235677973366163753939545814256844801e38", 0x47EFFFFFF0000000u, 0x7F800000u},
|
|
||||||
{"340282356779733661637539395458142568448000000000000000000001e-21", 0x47EFFFFFF0000000u, 0x7F800000u},
|
|
||||||
{"340282356779733661637539395458142568448.000000000000000000000000000000", 0x47EFFFFFF0000000u, 0x7F800000u},
|
|
||||||
{"3.4028235677973366e38", 0x47EFFFFFF0000000u, 0x7F7FFFFFu},
|
|
||||||
{"-34028235677973367e22", 0xC7EFFFFFF0000000u, 0xFF800000u},
|
|
||||||
{"3402823567797336616e20", 0x47EFFFFFF0000000u, 0x7F7FFFFFu},
|
|
||||||
{"340282356779733661700000000000000000000", 0x47EFFFFFF0000000u, 0x7F800000u},
|
|
||||||
{"340282356779733661630000000000000000000", 0x47EFFFFFF0000000u, 0x7F7FFFFFu},
|
|
||||||
{"3.4028235677973366164e38", 0x47EFFFFFF0000000u, 0x7F800000u},
|
|
||||||
{"3.40282356779733661637e38", 0x47EFFFFFF0000000u, 0x7F7FFFFFu},
|
|
||||||
{"340282356779733661638e18", 0x47EFFFFFF0000000u, 0x7F800000u},
|
|
||||||
{"-340282356779733661637539395458e9", 0xC7EFFFFFF0000000u, 0xFF7FFFFFu},
|
|
||||||
{"340282356779733661637539395459000000000", 0x47EFFFFFF0000000u, 0x7F800000u},
|
|
||||||
{"-1000000059604644775390625e-24", 0xBFF0000010000000u, 0xBF800000u},
|
|
||||||
{"-1.000000059604644775390626", 0xBFF0000010000000u, 0xBF800001u},
|
|
||||||
{"-1.000000059604644775390624e0", 0xBFF0000010000000u, 0xBF800000u},
|
|
||||||
{"100000005960464477539062501e-26", 0x3FF0000010000000u, 0x3F800001u},
|
|
||||||
{"1.000000059604644775390625000000000000000000001", 0x3FF0000010000000u, 0x3F800001u},
|
|
||||||
{"1.000000059604644775390625000000000000000000000000000000e0", 0x3FF0000010000000u, 0x3F800000u},
|
|
||||||
{"-10000000596046447e-16", 0xBFF0000010000000u, 0xBF800000u},
|
|
||||||
{"1.0000000596046448", 0x3FF0000010000000u, 0x3F800001u},
|
|
||||||
{"1.000000059604644775", 0x3FF0000010000000u, 0x3F800000u},
|
|
||||||
{"-1.000000059604644776e0", 0xBFF0000010000000u, 0xBF800001u},
|
|
||||||
{"-1.0000000596046447753e0", 0xBFF0000010000000u, 0xBF800000u},
|
|
||||||
{"10000000596046447754e-19", 0x3FF0000010000000u, 0x3F800001u},
|
|
||||||
{"100000005960464477539e-20", 0x3FF0000010000000u, 0x3F800000u},
|
|
||||||
{"-1.0000000596046447754", 0xBFF0000010000000u, 0xBF800001u},
|
|
||||||
{"0.9999999701976776123046875", 0x3FEFFFFFF0000000u, 0x3F800000u},
|
|
||||||
{"9.999999701976776123046876e-1", 0x3FEFFFFFF0000000u, 0x3F800000u},
|
|
||||||
{"9999999701976776123046874e-25", 0x3FEFFFFFF0000000u, 0x3F7FFFFFu},
|
|
||||||
{"0.999999970197677612304687501", 0x3FEFFFFFF0000000u, 0x3F800000u},
|
|
||||||
{"-9.999999701976776123046875000000000000000000001e-1", 0xBFEFFFFFF0000000u, 0xBF800000u},
|
|
||||||
{"-9999999701976776123046875000000000000000000000000000000e-55", 0xBFEFFFFFF0000000u, 0xBF800000u},
|
|
||||||
{"0.99999997019767761", 0x3FEFFFFFF0000000u, 0x3F7FFFFFu},
|
|
||||||
{"-9.9999997019767762e-1", 0xBFEFFFFFF0000000u, 0xBF800000u},
|
|
||||||
{"-9.999999701976776123e-1", 0xBFEFFFFFF0000000u, 0xBF7FFFFFu},
|
|
||||||
{"9999999701976776124e-19", 0x3FEFFFFFF0000000u, 0x3F800000u},
|
|
||||||
{"9999999701976776123e-19", 0x3FEFFFFFF0000000u, 0x3F7FFFFFu},
|
|
||||||
{"0.99999997019767761231", 0x3FEFFFFFF0000000u, 0x3F800000u},
|
|
||||||
{"-0.999999970197677612304", 0xBFEFFFFFF0000000u, 0xBF7FFFFFu},
|
|
||||||
{"9.99999970197677612305e-1", 0x3FEFFFFFF0000000u, 0x3F800000u},
|
|
||||||
{"-1.6777217e7", 0xC170000010000000u, 0xCB800000u},
|
|
||||||
{"16777218e0", 0x4170000020000000u, 0x4B800001u},
|
|
||||||
{"16777216", 0x4170000000000000u, 0x4B800000u},
|
|
||||||
{"-1.677721701e7", 0xC17000001028F5C3u, 0xCB800001u},
|
|
||||||
{"-16777217000000000000000000001e-21", 0xC170000010000000u, 0xCB800001u},
|
|
||||||
{"16777217.000000000000000000000000000000", 0x4170000010000000u, 0x4B800000u},
|
|
||||||
{"167772155e-1", 0x416FFFFFF0000000u, 0x4B800000u},
|
|
||||||
{"16777215.6", 0x416FFFFFF3333333u, 0x4B800000u},
|
|
||||||
{"1.67772154e7", 0x416FFFFFECCCCCCDu, 0x4B7FFFFFu},
|
|
||||||
{"16777215501e-3", 0x416FFFFFF0083127u, 0x4B800000u},
|
|
||||||
{"-16777215.5000000000000000000001", 0xC16FFFFFF0000000u, 0xCB800000u},
|
|
||||||
{"-1.67772155000000000000000000000000000000e7", 0xC16FFFFFF0000000u, 0xCB800000u},
|
|
||||||
{"0.1000000052154064178466796875", 0x3FB99999B0000000u, 0x3DCCCCCEu},
|
|
||||||
{"1.000000052154064178466796876e-1", 0x3FB99999B0000000u, 0x3DCCCCCEu},
|
|
||||||
{"-1000000052154064178466796874e-28", 0xBFB99999B0000000u, 0xBDCCCCCDu},
|
|
||||||
{"0.100000005215406417846679687501", 0x3FB99999B0000000u, 0x3DCCCCCEu},
|
|
||||||
{"1.000000052154064178466796875000000000000000000001e-1", 0x3FB99999B0000000u, 0x3DCCCCCEu},
|
|
||||||
{"-1000000052154064178466796875000000000000000000000000000000e-58", 0xBFB99999B0000000u, 0xBDCCCCCEu},
|
|
||||||
{"-0.10000000521540641", 0xBFB99999AFFFFFFFu, 0xBDCCCCCDu},
|
|
||||||
{"-1.0000000521540642e-1", 0xBFB99999B0000000u, 0xBDCCCCCEu},
|
|
||||||
{"1.000000052154064178e-1", 0x3FB99999B0000000u, 0x3DCCCCCDu},
|
|
||||||
{"-1000000052154064179e-19", 0xBFB99999B0000000u, 0xBDCCCCCEu},
|
|
||||||
{"10000000521540641784e-20", 0x3FB99999B0000000u, 0x3DCCCCCDu},
|
|
||||||
{"0.10000000521540641785", 0x3FB99999B0000000u, 0x3DCCCCCEu},
|
|
||||||
{"0.100000005215406417846", 0x3FB99999B0000000u, 0x3DCCCCCDu},
|
|
||||||
{"1.00000005215406417847e-1", 0x3FB99999B0000000u, 0x3DCCCCCEu},
|
|
||||||
{"5.429001220703125e3", 0x40B5350050000000u, 0x45A9A802u},
|
|
||||||
{"-5429001220703126e-12", 0xC0B5350050000001u, 0xC5A9A803u},
|
|
||||||
{"-5429.001220703124", 0xC0B535004FFFFFFFu, 0xC5A9A802u},
|
|
||||||
{"5.42900122070312501e3", 0x40B5350050000000u, 0x45A9A803u},
|
|
||||||
{"5429001220703125000000000000000000001e-33", 0x40B5350050000000u, 0x45A9A803u},
|
|
||||||
{"-5429.001220703125000000000000000000000000000000", 0xC0B5350050000000u, 0xC5A9A802u},
|
|
||||||
{"503719056e0", 0x41BE062490000000u, 0x4DF03124u},
|
|
||||||
{"503719057", 0x41BE062491000000u, 0x4DF03125u},
|
|
||||||
{"5.03719055e8", 0x41BE06248F000000u, 0x4DF03124u},
|
|
||||||
{"50371905601e-2", 0x41BE062490028F5Cu, 0x4DF03125u},
|
|
||||||
{"503719056.000000000000000000001", 0x41BE062490000000u, 0x4DF03125u},
|
|
||||||
{"5.03719056000000000000000000000000000000e8", 0x41BE062490000000u, 0x4DF03124u},
|
|
||||||
{"-92331620", 0xC196037990000000u, 0xCCB01BCCu},
|
|
||||||
{"9.233163e7", 0x41960379B8000000u, 0x4CB01BCEu},
|
|
||||||
{"9233161e1", 0x4196037968000000u, 0x4CB01BCBu},
|
|
||||||
{"92331620.1", 0x4196037990666666u, 0x4CB01BCDu},
|
|
||||||
{"9.233162000000000000000000001e7", 0x4196037990000000u, 0x4CB01BCDu},
|
|
||||||
{"9233162000000000000000000000000000000e-29", 0x4196037990000000u, 0x4CB01BCCu},
|
|
||||||
{"3.002458625e6", 0x4146E82D50000000u, 0x4A37416Au},
|
|
||||||
{"3002458626e-3", 0x4146E82D5020C49Cu, 0x4A37416Bu},
|
|
||||||
{"3002458.624", 0x4146E82D4FDF3B64u, 0x4A37416Au},
|
|
||||||
{"-3.00245862501e6", 0xC146E82D500053E3u, 0xCA37416Bu},
|
|
||||||
{"3002458625000000000000000000001e-24", 0x4146E82D50000000u, 0x4A37416Bu},
|
|
||||||
{"3002458.625000000000000000000000000000000", 0x4146E82D50000000u, 0x4A37416Au},
|
|
||||||
{"-1095485584696182596504479582065262592e1", 0xC7A07BA830000000u, 0xFD03DD42u},
|
|
||||||
{"10954855846961825965044795820652625930", 0x47A07BA830000000u, 0x7D03DD42u},
|
|
||||||
{"1.095485584696182596504479582065262591e37", 0x47A07BA830000000u, 0x7D03DD41u},
|
|
||||||
{"109548558469618259650447958206526259201e-1", 0x47A07BA830000000u, 0x7D03DD42u},
|
|
||||||
{"-10954855846961825965044795820652625920.00000000000000000001", 0xC7A07BA830000000u, 0xFD03DD42u},
|
|
||||||
{"1.095485584696182596504479582065262592000000000000000000000000000000e37", 0x47A07BA830000000u, 0x7D03DD42u},
|
|
||||||
{"10954855846961825e21", 0x47A07BA830000000u, 0x7D03DD41u},
|
|
||||||
{"10954855846961826000000000000000000000", 0x47A07BA830000000u, 0x7D03DD42u},
|
|
||||||
{"10954855846961825960000000000000000000", 0x47A07BA830000000u, 0x7D03DD41u},
|
|
||||||
{"1.095485584696182597e37", 0x47A07BA830000000u, 0x7D03DD42u},
|
|
||||||
{"1.0954855846961825965e37", 0x47A07BA830000000u, 0x7D03DD41u},
|
|
||||||
{"-10954855846961825966e18", 0xC7A07BA830000000u, 0xFD03DD42u},
|
|
||||||
{"10954855846961825965e18", 0x47A07BA830000000u, 0x7D03DD41u},
|
|
||||||
{"10954855846961825965100000000000000000", 0x47A07BA830000000u, 0x7D03DD42u},
|
|
||||||
{"10954855846961825965044795820600000000", 0x47A07BA830000000u, 0x7D03DD41u},
|
|
||||||
{"-1.09548558469618259650447958207e37", 0xC7A07BA830000000u, 0xFD03DD42u},
|
|
||||||
{"1.6449216019182103706535606608388384863861375606575165875256061553955078126e-21", 0x3B9F125A50000000u, 0x1CF892D3u},
|
|
||||||
{"-16449216019182103706535606608388384863861375606575165875256061553955078124e-94", 0xBB9F125A50000000u, 0x9CF892D2u},
|
|
||||||
{"-0.0000000000000000000016449216019182103", 0xBB9F125A50000000u, 0x9CF892D2u},
|
|
||||||
{"-1.6449216019182104e-21", 0xBB9F125A50000000u, 0x9CF892D3u},
|
|
||||||
{"-1.64492160191821037e-21", 0xBB9F125A50000000u, 0x9CF892D2u},
|
|
||||||
{"-1644921601918210371e-39", 0xBB9F125A50000000u, 0x9CF892D3u},
|
|
||||||
{"-16449216019182103706e-40", 0xBB9F125A50000000u, 0x9CF892D2u},
|
|
||||||
{"0.0000000000000000000016449216019182103707", 0x3B9F125A50000000u, 0x1CF892D3u},
|
|
||||||
{"0.00000000000000000000164492160191821037065", 0x3B9F125A50000000u, 0x1CF892D2u},
|
|
||||||
{"1.64492160191821037066e-21", 0x3B9F125A50000000u, 0x1CF892D3u},
|
|
||||||
{"1.64492160191821037065356066083e-21", 0x3B9F125A50000000u, 0x1CF892D2u},
|
|
||||||
{"164492160191821037065356066084e-50", 0x3B9F125A50000000u, 0x1CF892D3u},
|
|
||||||
{"6.565061509609222412109375e-1", 0x3FE5021930000000u, 0x3F2810CAu},
|
|
||||||
{"6565061509609222412109376e-25", 0x3FE5021930000000u, 0x3F2810CAu},
|
|
||||||
{"0.6565061509609222412109374", 0x3FE5021930000000u, 0x3F2810C9u},
|
|
||||||
{"6.56506150960922241210937501e-1", 0x3FE5021930000000u, 0x3F2810CAu},
|
|
||||||
{"6565061509609222412109375000000000000000000001e-46", 0x3FE5021930000000u, 0x3F2810CAu},
|
|
||||||
{"-0.6565061509609222412109375000000000000000000000000000000", 0xBFE5021930000000u, 0xBF2810CAu},
|
|
||||||
{"6.5650615096092224e-1", 0x3FE5021930000000u, 0x3F2810C9u},
|
|
||||||
{"-65650615096092225e-17", 0xBFE5021930000000u, 0xBF2810CAu},
|
|
||||||
{"6565061509609222412e-19", 0x3FE5021930000000u, 0x3F2810C9u},
|
|
||||||
{"0.6565061509609222413", 0x3FE5021930000000u, 0x3F2810CAu},
|
|
||||||
{"0.65650615096092224121", 0x3FE5021930000000u, 0x3F2810C9u},
|
|
||||||
{"6.5650615096092224122e-1", 0x3FE5021930000000u, 0x3F2810CAu},
|
|
||||||
{"-6.5650615096092224121e-1", 0xBFE5021930000000u, 0xBF2810C9u},
|
|
||||||
{"656506150960922241211e-21", 0x3FE5021930000000u, 0x3F2810CAu},
|
|
||||||
{"18014627239033005156980393746124491372029297053813934326171875e-77", 0x3CA9F63970000000u, 0x254FB1CCu},
|
|
||||||
{"0.00000000000000018014627239033005156980393746124491372029297053813934326171876", 0x3CA9F63970000000u, 0x254FB1CCu},
|
|
||||||
{"-1.8014627239033005156980393746124491372029297053813934326171874e-16", 0xBCA9F63970000000u, 0xA54FB1CBu},
|
|
||||||
{"1801462723903300515698039374612449137202929705381393432617187501e-79", 0x3CA9F63970000000u, 0x254FB1CCu},
|
|
||||||
{"18014627239033005e-32", 0x3CA9F63970000000u, 0x254FB1CBu},
|
|
||||||
{"0.00000000000000018014627239033006", 0x3CA9F63970000000u, 0x254FB1CCu},
|
|
||||||
{"0.0000000000000001801462723903300515", 0x3CA9F63970000000u, 0x254FB1CBu},
|
|
||||||
{"-1.801462723903300516e-16", 0xBCA9F63970000000u, 0xA54FB1CCu},
|
|
||||||
{"-1.8014627239033005156e-16", 0xBCA9F63970000000u, 0xA54FB1CBu},
|
|
||||||
{"18014627239033005157e-35", 0x3CA9F63970000000u, 0x254FB1CCu},
|
|
||||||
{"180146272390330051569e-36", 0x3CA9F63970000000u, 0x254FB1CBu},
|
|
||||||
{"-0.00000000000000018014627239033005157", 0xBCA9F63970000000u, 0xA54FB1CCu},
|
|
||||||
{"0.000000000000000180146272390330051569803937461", 0x3CA9F63970000000u, 0x254FB1CBu},
|
|
||||||
{"1.80146272390330051569803937462e-16", 0x3CA9F63970000000u, 0x254FB1CCu},
|
|
||||||
{"0.05534819327294826507568359375", 0x3FAC569930000000u, 0x3D62B4CAu},
|
|
||||||
{"-5.534819327294826507568359376e-2", 0xBFAC569930000000u, 0xBD62B4CAu},
|
|
||||||
{"5534819327294826507568359374e-29", 0x3FAC569930000000u, 0x3D62B4C9u},
|
|
||||||
{"-0.0553481932729482650756835937501", 0xBFAC569930000000u, 0xBD62B4CAu},
|
|
||||||
{"5.534819327294826507568359375000000000000000000001e-2", 0x3FAC569930000000u, 0x3D62B4CAu},
|
|
||||||
{"5534819327294826507568359375000000000000000000000000000000e-59", 0x3FAC569930000000u, 0x3D62B4CAu},
|
|
||||||
{"0.055348193272948265", 0x3FAC569930000000u, 0x3D62B4C9u},
|
|
||||||
{"5.5348193272948266e-2", 0x3FAC569930000000u, 0x3D62B4CAu},
|
|
||||||
{"5.534819327294826507e-2", 0x3FAC569930000000u, 0x3D62B4C9u},
|
|
||||||
{"5534819327294826508e-20", 0x3FAC569930000000u, 0x3D62B4CAu},
|
|
||||||
{"55348193272948265075e-21", 0x3FAC569930000000u, 0x3D62B4C9u},
|
|
||||||
{"-0.055348193272948265076", 0xBFAC569930000000u, 0xBD62B4CAu},
|
|
||||||
{"0.0553481932729482650756", 0x3FAC569930000000u, 0x3D62B4C9u},
|
|
||||||
{"-5.53481932729482650757e-2", 0xBFAC569930000000u, 0xBD62B4CAu},
|
|
||||||
{"5.179692133247783258005389047985340416e36", 0x478F2C9450000000u, 0x7C7964A2u},
|
|
||||||
{"5179692133247783258005389047985340417e0", 0x478F2C9450000000u, 0x7C7964A3u},
|
|
||||||
{"5179692133247783258005389047985340415", 0x478F2C9450000000u, 0x7C7964A2u},
|
|
||||||
{"-5.17969213324778325800538904798534041601e36", 0xC78F2C9450000000u, 0xFC7964A3u},
|
|
||||||
{"-5179692133247783258005389047985340416000000000000000000001e-21", 0xC78F2C9450000000u, 0xFC7964A3u},
|
|
||||||
{"-5179692133247783258005389047985340416.000000000000000000000000000000", 0xC78F2C9450000000u, 0xFC7964A2u},
|
|
||||||
{"5.1796921332477832e36", 0x478F2C9450000000u, 0x7C7964A2u},
|
|
||||||
{"51796921332477833e20", 0x478F2C9450000000u, 0x7C7964A3u},
|
|
||||||
{"-5179692133247783258e18", 0xC78F2C9450000000u, 0xFC7964A2u},
|
|
||||||
{"5179692133247783259000000000000000000", 0x478F2C9450000000u, 0x7C7964A3u},
|
|
||||||
{"5179692133247783258000000000000000000", 0x478F2C9450000000u, 0x7C7964A2u},
|
|
||||||
{"5.1796921332477832581e36", 0x478F2C9450000000u, 0x7C7964A3u},
|
|
||||||
{"-5.179692133247783258e36", 0xC78F2C9450000000u, 0xFC7964A2u},
|
|
||||||
{"-517969213324778325801e16", 0xC78F2C9450000000u, 0xFC7964A3u},
|
|
||||||
{"517969213324778325800538904798e7", 0x478F2C9450000000u, 0x7C7964A2u},
|
|
||||||
{"5179692133247783258005389047990000000", 0x478F2C9450000000u, 0x7C7964A3u},
|
|
||||||
{
|
|
||||||
"0.22250738585072011360574097967091319759348195463516456480234261097248222220210769455165295239081350"
|
|
||||||
"8791414915891303962110687008643869459464552765720740782062174337998814106326732925355228688137214901"
|
|
||||||
"2981122451451889849057222307285255133155755015914397476397983411801999323962548289017107081850690630"
|
|
||||||
"6666559949382757725720157630626906633326475653000092458883164330377797918696120494973903778297049050"
|
|
||||||
"5108060994073026293712895895000358379996720725430436028407889577179615094551674824347103070260914462"
|
|
||||||
"1572289880258182545180325707018860872113128079512233426288368622321503775666622503982534335974568884"
|
|
||||||
"4239002654981983854879482922068947216898310996983658468140228542433306603398508864458040010349339704"
|
|
||||||
"2756718644338377048603786162277173854562306587467901408672332763671875e-307", 0x0010000000000000u, 0x00000000u
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"2.22507385850720113605740979670913197593481954635164564802342610972482222202107694551652952390813508"
|
|
||||||
"7914149158913039621106870086438694594645527657207407820621743379988141063267329253552286881372149012"
|
|
||||||
"9811224514518898490572223072852551331557550159143974763979834118019993239625482890171070818506906306"
|
|
||||||
"6665599493827577257201576306269066333264756530000924588831643303777979186961204949739037782970490505"
|
|
||||||
"1080609940730262937128958950003583799967207254304360284078895771796150945516748243471030702609144621"
|
|
||||||
"5722898802581825451803257070188608721131280795122334262883686223215037756666225039825343359745688844"
|
|
||||||
"2390026549819838548794829220689472168983109969836584681402285424333066033985088644580400103493397042"
|
|
||||||
"756718644338377048603786162277173854562306587467901408672332763671875000000000000000000001e-308", 0x0010000000000000u, 0x00000000u
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"0.11754942807573642917278829910357665133228589927589904276829631184250030649651730385585324256680905"
|
|
||||||
"8189392089843750000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
|
||||||
"00000000000000000000000000000000000000000000000000000000000000000e-37", 0x380FFFFFE0000000u, 0x00800000u
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"1175494280757364291727882991035766513322858992758990427682963118425003064965173038558532425668090581"
|
|
||||||
"8939208984375000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
|
||||||
"00000000000001e-751", 0x380FFFFFE0000000u, 0x00800000u
|
|
||||||
},
|
|
||||||
{"0", 0x0000000000000000u, 0x00000000u},
|
|
||||||
{"-0", 0x8000000000000000u, 0x80000000u},
|
|
||||||
{"0.0", 0x0000000000000000u, 0x00000000u},
|
|
||||||
{"-0.0", 0x8000000000000000u, 0x80000000u},
|
|
||||||
{"0e999999999999999999999", 0x0000000000000000u, 0x00000000u},
|
|
||||||
{"-0.000e-99999", 0x8000000000000000u, 0x80000000u},
|
|
||||||
{"1e-400", 0x0000000000000000u, 0x00000000u},
|
|
||||||
{"-1e-400", 0x8000000000000000u, 0x80000000u},
|
|
||||||
{"1e400", 0x7FF0000000000000u, 0x7F800000u},
|
|
||||||
{"-1e400", 0xFFF0000000000000u, 0xFF800000u},
|
|
||||||
{"1e-50", 0x358DEE7A4AD4B81Fu, 0x00000000u},
|
|
||||||
{"-1e-50", 0xB58DEE7A4AD4B81Fu, 0x80000000u},
|
|
||||||
{"1e39", 0x48078287F49C4A1Du, 0x7F800000u},
|
|
||||||
{"-1e39", 0xC8078287F49C4A1Du, 0xFF800000u},
|
|
||||||
{"1e99999999999999999999999999", 0x7FF0000000000000u, 0x7F800000u},
|
|
||||||
{"1e-99999999999999999999999999", 0x0000000000000000u, 0x00000000u},
|
|
||||||
{"1e0000000000000000000000000000000000000000308", 0x7FE1CCF385EBC8A0u, 0x7F800000u},
|
|
||||||
{"123456789012345678901234567890e-30", 0x3FBF9ADD3746F65Fu, 0x3DFCD6EAu},
|
|
||||||
{"18446744073709551615", 0x43F0000000000000u, 0x5F800000u},
|
|
||||||
{"18446744073709551616", 0x43F0000000000000u, 0x5F800000u},
|
|
||||||
{"-9223372036854775808", 0xC3E0000000000000u, 0xDF000000u},
|
|
||||||
{"-9223372036854775809", 0xC3E0000000000000u, 0xDF000000u},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return table;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace float_hard_cases
|
|
||||||
@@ -45,6 +45,10 @@ dumps is stable under exactly the same values that break operator==.
|
|||||||
The unit tests run the same checks on a fixed corpus (see the "BJData round-trip
|
The unit tests run the same checks on a fixed corpus (see the "BJData round-trip
|
||||||
invariants" test case), so keep both in sync.
|
invariants" test case), so keep both in sync.
|
||||||
|
|
||||||
|
Furthermore, it reads data with a SAX parser that recovers from every error
|
||||||
|
and checks that the events are balanced, that reading ends, and that it
|
||||||
|
reports an error exactly when from_bjdata() fails (see #3989).
|
||||||
|
|
||||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||||
drivers.
|
drivers.
|
||||||
*/
|
*/
|
||||||
@@ -59,6 +63,8 @@ drivers.
|
|||||||
#error "the fuzzer drivers must be built without NDEBUG"
|
#error "the fuzzer drivers must be built without NDEBUG"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
// value-stable comparison for the round-trip checks below; see the note
|
// value-stable comparison for the round-trip checks below; see the note
|
||||||
@@ -71,11 +77,15 @@ static bool is_value_stable(const json& lhs, const json& rhs)
|
|||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
|
// step 0: recover from all errors, reading from memory and from a stream
|
||||||
|
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::bjdata).errors == 0;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// step 1: parse input
|
// step 1: parse input
|
||||||
std::vector<uint8_t> const vec1(data, data + size);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
json const j1 = json::from_bjdata(vec1);
|
json const j1 = json::from_bjdata(vec1);
|
||||||
|
assert(recovered_without_errors);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -109,6 +119,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::parse_error&)
|
catch (const json::parse_error&)
|
||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
|
assert(!recovered_without_errors);
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -117,6 +128,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::out_of_range&)
|
catch (const json::out_of_range&)
|
||||||
{
|
{
|
||||||
// out of range errors may happen if provided sizes are excessive
|
// out of range errors may happen if provided sizes are excessive
|
||||||
|
assert(!recovered_without_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// return 0 - non-zero return values are reserved for future use
|
// return 0 - non-zero return values are reserved for future use
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ It also checks that reading the data from a stream, which reads strings byte by
|
|||||||
byte, gives the same value or error as reading it from contiguous memory, which
|
byte, gives the same value or error as reading it from contiguous memory, which
|
||||||
copies strings in bulk.
|
copies strings in bulk.
|
||||||
|
|
||||||
|
Furthermore, it reads data with a SAX parser that recovers from every error
|
||||||
|
and checks that the events are balanced, that reading ends, and that it
|
||||||
|
reports an error exactly when from_bon8() fails (see #3989).
|
||||||
|
|
||||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||||
drivers.
|
drivers.
|
||||||
*/
|
*/
|
||||||
@@ -33,6 +37,8 @@ drivers.
|
|||||||
#error "the fuzzer drivers must be built without NDEBUG"
|
#error "the fuzzer drivers must be built without NDEBUG"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -56,6 +62,9 @@ std::string read_bon8(InputType&& input)
|
|||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
|
// step 0: recover from all errors, reading from memory and from a stream
|
||||||
|
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::bon8).errors == 0;
|
||||||
|
|
||||||
// contiguous and stream input must be read alike
|
// contiguous and stream input must be read alike
|
||||||
{
|
{
|
||||||
std::istringstream stream(std::string(reinterpret_cast<const char*>(data), size));
|
std::istringstream stream(std::string(reinterpret_cast<const char*>(data), size));
|
||||||
@@ -67,6 +76,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
// step 1: parse input
|
// step 1: parse input
|
||||||
std::vector<uint8_t> const vec1(data, data + size);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
json const j1 = json::from_bon8(vec1);
|
json const j1 = json::from_bon8(vec1);
|
||||||
|
assert(recovered_without_errors);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -88,6 +98,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::parse_error&)
|
catch (const json::parse_error&)
|
||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
|
assert(!recovered_without_errors);
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -96,6 +107,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::out_of_range&)
|
catch (const json::out_of_range&)
|
||||||
{
|
{
|
||||||
// out of range errors may happen if provided sizes are excessive
|
// out of range errors may happen if provided sizes are excessive
|
||||||
|
assert(!recovered_without_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// return 0 - non-zero return values are reserved for future use
|
// return 0 - non-zero return values are reserved for future use
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ array data, it performs the following steps:
|
|||||||
- j2 = from_bson(vec)
|
- j2 = from_bson(vec)
|
||||||
- assert(j1 == j2)
|
- assert(j1 == j2)
|
||||||
|
|
||||||
|
Furthermore, it reads data with a SAX parser that recovers from every error
|
||||||
|
and checks that the events are balanced, that reading ends, and that it
|
||||||
|
reports an error exactly when from_bson() fails (see #3989).
|
||||||
|
|
||||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||||
drivers.
|
drivers.
|
||||||
*/
|
*/
|
||||||
@@ -29,16 +33,22 @@ drivers.
|
|||||||
#error "the fuzzer drivers must be built without NDEBUG"
|
#error "the fuzzer drivers must be built without NDEBUG"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
|
// step 0: recover from all errors, reading from memory and from a stream
|
||||||
|
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::bson).errors == 0;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// step 1: parse input
|
// step 1: parse input
|
||||||
std::vector<uint8_t> const vec1(data, data + size);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
json const j1 = json::from_bson(vec1);
|
json const j1 = json::from_bson(vec1);
|
||||||
|
assert(recovered_without_errors);
|
||||||
|
|
||||||
if (j1.is_discarded())
|
if (j1.is_discarded())
|
||||||
{
|
{
|
||||||
@@ -65,6 +75,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::parse_error&)
|
catch (const json::parse_error&)
|
||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
|
assert(!recovered_without_errors);
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -73,6 +84,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::out_of_range&)
|
catch (const json::out_of_range&)
|
||||||
{
|
{
|
||||||
// out of range errors can occur during parsing, too
|
// out of range errors can occur during parsing, too
|
||||||
|
assert(!recovered_without_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// return 0 - non-zero return values are reserved for future use
|
// return 0 - non-zero return values are reserved for future use
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ array data, it performs the following steps:
|
|||||||
- j2 = from_cbor(vec)
|
- j2 = from_cbor(vec)
|
||||||
- assert(j1 == j2)
|
- assert(j1 == j2)
|
||||||
|
|
||||||
|
Furthermore, it reads data with a SAX parser that recovers from every error
|
||||||
|
and checks that the events are balanced, that reading ends, and that it
|
||||||
|
reports an error exactly when from_cbor() fails (see #3989).
|
||||||
|
|
||||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||||
drivers.
|
drivers.
|
||||||
*/
|
*/
|
||||||
@@ -29,16 +33,22 @@ drivers.
|
|||||||
#error "the fuzzer drivers must be built without NDEBUG"
|
#error "the fuzzer drivers must be built without NDEBUG"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
|
// step 0: recover from all errors, reading from memory and from a stream
|
||||||
|
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::cbor).errors == 0;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// step 1: parse input
|
// step 1: parse input
|
||||||
std::vector<uint8_t> const vec1(data, data + size);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
json const j1 = json::from_cbor(vec1);
|
json const j1 = json::from_cbor(vec1);
|
||||||
|
assert(recovered_without_errors);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -60,6 +70,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::parse_error&)
|
catch (const json::parse_error&)
|
||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
|
assert(!recovered_without_errors);
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -68,6 +79,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::out_of_range&)
|
catch (const json::out_of_range&)
|
||||||
{
|
{
|
||||||
// out of range errors can occur during parsing, too
|
// out of range errors can occur during parsing, too
|
||||||
|
assert(!recovered_without_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// return 0 - non-zero return values are reserved for future use
|
// return 0 - non-zero return values are reserved for future use
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ array data, it performs the following steps:
|
|||||||
- s2 = serialize(j2)
|
- s2 = serialize(j2)
|
||||||
- assert(s1 == s2)
|
- assert(s1 == s2)
|
||||||
|
|
||||||
|
Furthermore, it parses data with a SAX parser that recovers from every error
|
||||||
|
and checks that the events are balanced, that parsing ends, and that valid
|
||||||
|
input is parsed without errors (see #3989).
|
||||||
|
|
||||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||||
drivers.
|
drivers.
|
||||||
*/
|
*/
|
||||||
@@ -23,6 +27,7 @@ drivers.
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
// the round-trip checks below are assertions; NDEBUG would compile them away
|
// the round-trip checks below are assertions; NDEBUG would compile them away
|
||||||
@@ -30,11 +35,20 @@ drivers.
|
|||||||
#error "the fuzzer drivers must be built without NDEBUG"
|
#error "the fuzzer drivers must be built without NDEBUG"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
|
// step 0: recover from all errors, reading from memory and from a stream
|
||||||
|
{
|
||||||
|
const auto checker = check_recovering_parse(data, size, json::input_format_t::json);
|
||||||
|
assert(checker.events <= (4 * size) + 4);
|
||||||
|
assert((checker.errors == 0) == json::accept(data, data + size));
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// step 1: parse input
|
// step 1: parse input
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ array data, it performs the following steps:
|
|||||||
- j2 = from_msgpack(vec)
|
- j2 = from_msgpack(vec)
|
||||||
- assert(j1 == j2)
|
- assert(j1 == j2)
|
||||||
|
|
||||||
|
Furthermore, it reads data with a SAX parser that recovers from every error
|
||||||
|
and checks that the events are balanced, that reading ends, and that it
|
||||||
|
reports an error exactly when from_msgpack() fails (see #3989).
|
||||||
|
|
||||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||||
drivers.
|
drivers.
|
||||||
*/
|
*/
|
||||||
@@ -29,16 +33,22 @@ drivers.
|
|||||||
#error "the fuzzer drivers must be built without NDEBUG"
|
#error "the fuzzer drivers must be built without NDEBUG"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
|
// step 0: recover from all errors, reading from memory and from a stream
|
||||||
|
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::msgpack).errors == 0;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// step 1: parse input
|
// step 1: parse input
|
||||||
std::vector<uint8_t> const vec1(data, data + size);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
json const j1 = json::from_msgpack(vec1);
|
json const j1 = json::from_msgpack(vec1);
|
||||||
|
assert(recovered_without_errors);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -60,6 +70,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::parse_error&)
|
catch (const json::parse_error&)
|
||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
|
assert(!recovered_without_errors);
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -68,6 +79,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::out_of_range&)
|
catch (const json::out_of_range&)
|
||||||
{
|
{
|
||||||
// out of range errors may happen if provided sizes are excessive
|
// out of range errors may happen if provided sizes are excessive
|
||||||
|
assert(!recovered_without_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// return 0 - non-zero return values are reserved for future use
|
// return 0 - non-zero return values are reserved for future use
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ array data, it performs the following steps:
|
|||||||
The unit tests run the same checks on a fixed corpus (see the "UBJSON round-trip
|
The unit tests run the same checks on a fixed corpus (see the "UBJSON round-trip
|
||||||
invariants" test case), so keep both in sync.
|
invariants" test case), so keep both in sync.
|
||||||
|
|
||||||
|
Furthermore, it reads data with a SAX parser that recovers from every error
|
||||||
|
and checks that the events are balanced, that reading ends, and that it
|
||||||
|
reports an error exactly when from_ubjson() fails (see #3989).
|
||||||
|
|
||||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||||
drivers.
|
drivers.
|
||||||
*/
|
*/
|
||||||
@@ -38,16 +42,22 @@ drivers.
|
|||||||
#error "the fuzzer drivers must be built without NDEBUG"
|
#error "the fuzzer drivers must be built without NDEBUG"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
|
// step 0: recover from all errors, reading from memory and from a stream
|
||||||
|
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::ubjson).errors == 0;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// step 1: parse input
|
// step 1: parse input
|
||||||
std::vector<uint8_t> const vec1(data, data + size);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
json const j1 = json::from_ubjson(vec1);
|
json const j1 = json::from_ubjson(vec1);
|
||||||
|
assert(recovered_without_errors);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -79,6 +89,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::parse_error&)
|
catch (const json::parse_error&)
|
||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
|
assert(!recovered_without_errors);
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -87,6 +98,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
catch (const json::out_of_range&)
|
catch (const json::out_of_range&)
|
||||||
{
|
{
|
||||||
// out of range errors may happen if provided sizes are excessive
|
// out of range errors may happen if provided sizes are excessive
|
||||||
|
assert(!recovered_without_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// return 0 - non-zero return values are reserved for future use
|
// return 0 - non-zero return values are reserved for future use
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// a SAX parser that recovers from every error and checks that the events are
|
||||||
|
// balanced and that every key is followed by exactly one value
|
||||||
|
class recovering_checker : public nlohmann::json_sax<nlohmann::json>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool null() override
|
||||||
|
{
|
||||||
|
return value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool boolean(bool /*val*/) override
|
||||||
|
{
|
||||||
|
return value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_integer(number_integer_t /*val*/) override
|
||||||
|
{
|
||||||
|
return value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_unsigned(number_unsigned_t /*val*/) override
|
||||||
|
{
|
||||||
|
return value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_float(number_float_t /*val*/, const string_t& /*s*/) override
|
||||||
|
{
|
||||||
|
return value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool string(string_t& /*val*/) override
|
||||||
|
{
|
||||||
|
return value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool binary(binary_t& /*val*/) override
|
||||||
|
{
|
||||||
|
return value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool start_object(std::size_t /*elements*/) override
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
stack.push_back('o');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool key(string_t& /*val*/) override
|
||||||
|
{
|
||||||
|
++events;
|
||||||
|
assert(!stack.empty() && stack.back() == 'o');
|
||||||
|
stack.back() = 'v';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool end_object() override
|
||||||
|
{
|
||||||
|
++events;
|
||||||
|
assert(!stack.empty() && stack.back() == 'o');
|
||||||
|
stack.pop_back();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool start_array(std::size_t /*elements*/) override
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
stack.push_back('a');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool end_array() override
|
||||||
|
{
|
||||||
|
++events;
|
||||||
|
assert(!stack.empty() && stack.back() == 'a');
|
||||||
|
stack.pop_back();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const nlohmann::detail::exception& /*ex*/) override
|
||||||
|
{
|
||||||
|
++errors;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool complete() const
|
||||||
|
{
|
||||||
|
return stack.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t events = 0;
|
||||||
|
std::size_t errors = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool value()
|
||||||
|
{
|
||||||
|
++events;
|
||||||
|
if (!stack.empty())
|
||||||
|
{
|
||||||
|
// an array element, or the value of a key
|
||||||
|
assert(stack.back() != 'o');
|
||||||
|
if (stack.back() == 'v')
|
||||||
|
{
|
||||||
|
stack.back() = 'o';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'a' for an array, 'o' for an object that expects a key, 'v' for an
|
||||||
|
// object that expects the value of a key
|
||||||
|
std::vector<char> stack {}; // NOLINT(readability-redundant-member-init)
|
||||||
|
};
|
||||||
|
|
||||||
|
/// parses @a data with a recovering_checker from memory and from a stream,
|
||||||
|
/// checks that both see the same, that the events are balanced, and that the
|
||||||
|
/// number of errors is bounded, and returns the checker (see #3989)
|
||||||
|
inline recovering_checker check_recovering_parse(const std::uint8_t* data, const std::size_t size, const nlohmann::json::input_format_t format)
|
||||||
|
{
|
||||||
|
recovering_checker checker;
|
||||||
|
const bool ok = nlohmann::json::sax_parse(data, data + size, &checker, format);
|
||||||
|
assert(checker.complete());
|
||||||
|
assert(checker.errors <= size + 1);
|
||||||
|
assert(ok == (checker.errors == 0));
|
||||||
|
|
||||||
|
std::istringstream stream(std::string(reinterpret_cast<const char*>(data), size));
|
||||||
|
recovering_checker stream_checker;
|
||||||
|
assert(nlohmann::json::sax_parse(stream, &stream_checker, format) == ok);
|
||||||
|
assert(stream_checker.complete());
|
||||||
|
assert(stream_checker.events == checker.events);
|
||||||
|
assert(stream_checker.errors == checker.errors);
|
||||||
|
|
||||||
|
return checker;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
@@ -479,6 +479,77 @@ TEST_CASE("deep copy uses the provided allocator")
|
|||||||
CHECK(copy == j);
|
CHECK(copy == j);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// the number of constructions countdown_allocator lets happen, including the
|
||||||
|
// one that fails; 0 means none ever fails
|
||||||
|
std::size_t constructions_until_failure = 0;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct countdown_allocator : std::allocator<T>
|
||||||
|
{
|
||||||
|
using std::allocator<T>::allocator;
|
||||||
|
|
||||||
|
template<class U, class... Args>
|
||||||
|
void construct(U* p, Args&& ... args)
|
||||||
|
{
|
||||||
|
if (constructions_until_failure != 0 && --constructions_until_failure == 0)
|
||||||
|
{
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
|
||||||
|
::new (static_cast<void*>(p)) U(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class U>
|
||||||
|
struct rebind
|
||||||
|
{
|
||||||
|
using other = countdown_allocator<U>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("converting a deeply nested value from another specialization fails cleanly (#5650)")
|
||||||
|
{
|
||||||
|
using countdown_json = nlohmann::basic_json<std::map,
|
||||||
|
std::vector,
|
||||||
|
std::string,
|
||||||
|
bool,
|
||||||
|
std::int64_t,
|
||||||
|
std::uint64_t,
|
||||||
|
double,
|
||||||
|
countdown_allocator>;
|
||||||
|
|
||||||
|
// deeper than the 128 levels the converting constructor descends into, so
|
||||||
|
// that failures land on both sides of the bound - or, built with
|
||||||
|
// JSON_NO_THREAD_LOCAL, all in the iterative conversion
|
||||||
|
json j = {1, "two", {{"three", 3}}};
|
||||||
|
for (std::size_t i = 0; i < 150; ++i)
|
||||||
|
{
|
||||||
|
j = json{{"a", json::array({j, "sibling"})}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail every construction in turn. Each failure has to reach the caller,
|
||||||
|
// and everything built until then has to be destroyed cleanly.
|
||||||
|
std::size_t failures = 0;
|
||||||
|
for (std::size_t n = 1;; ++n)
|
||||||
|
{
|
||||||
|
constructions_until_failure = n;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const countdown_json converted = j;
|
||||||
|
constructions_until_failure = 0;
|
||||||
|
CHECK(converted.dump() == j.dump());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (const std::bad_alloc&)
|
||||||
|
{
|
||||||
|
++failures;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CHECK(failures > 0);
|
||||||
|
}
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
template<class T>
|
template<class T>
|
||||||
|
|||||||
@@ -419,6 +419,46 @@ TEST_CASE("alternative string type")
|
|||||||
CHECK(j2.dump() == R"({"/foo/0":"bar","/foo/1":"baz"})");
|
CHECK(j2.dump() == R"({"/foo/0":"bar","/foo/1":"baz"})");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("error recovery")
|
||||||
|
{
|
||||||
|
// a SAX parser that recovers from every error (see #3989)
|
||||||
|
struct recovering_parser : nlohmann::detail::json_sax_dom_parser<alt_json>
|
||||||
|
{
|
||||||
|
explicit recovering_parser(alt_json& j)
|
||||||
|
: nlohmann::detail::json_sax_dom_parser<alt_json>(j, false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
// sax_parse() calls the SAX parser's own parse_error(), so hiding
|
||||||
|
// the one of the base class is what recovering takes
|
||||||
|
// NOLINTNEXTLINE(bugprone-derived-method-shadowing-base-method)
|
||||||
|
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const nlohmann::detail::exception& /*unused*/)
|
||||||
|
{
|
||||||
|
++errors;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t errors = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
alt_json j;
|
||||||
|
recovering_parser sax(j);
|
||||||
|
// not inside CHECK(): MSVC reads the escape in a stringized raw string
|
||||||
|
const std::string input = R"([1., "a\qb", tru, {"k" 2}])";
|
||||||
|
CHECK(!alt_json::sax_parse(input, &sax));
|
||||||
|
CHECK(sax.errors == 4);
|
||||||
|
CHECK(j.dump() == R"([1,"aqb",null,{"k":2}])");
|
||||||
|
|
||||||
|
// a UBJSON high-precision number, a CBOR key that is not a string
|
||||||
|
alt_json u;
|
||||||
|
recovering_parser ubjson_sax(u);
|
||||||
|
CHECK(!alt_json::sax_parse(std::vector<std::uint8_t> {'[', 'H', 'i', 2, '1', '.', ']'}, &ubjson_sax, alt_json::input_format_t::ubjson));
|
||||||
|
CHECK(u.dump() == "[1]");
|
||||||
|
alt_json c;
|
||||||
|
recovering_parser cbor_sax(c);
|
||||||
|
CHECK(!alt_json::sax_parse(std::vector<std::uint8_t> {0xA2, 0x01, 0x02, 0x61, 'a', 0x03}, &cbor_sax, alt_json::input_format_t::cbor));
|
||||||
|
CHECK(c.dump() == R"({"a":3})");
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("strict enum")
|
SECTION("strict enum")
|
||||||
{
|
{
|
||||||
// regression test for #5667: NLOHMANN_JSON_SERIALIZE_ENUM_STRICT's from_json
|
// regression test for #5667: NLOHMANN_JSON_SERIALIZE_ENUM_STRICT's from_json
|
||||||
|
|||||||
@@ -210,15 +210,42 @@ TEST_CASE_TEMPLATE_INVOKE(value_in_range_of_test, \
|
|||||||
|
|
||||||
TEST_CASE("BJData")
|
TEST_CASE("BJData")
|
||||||
{
|
{
|
||||||
SECTION("binary_reader BJData LUT arrays are sorted")
|
SECTION("binary_reader BJData lookup tables")
|
||||||
{
|
{
|
||||||
std::vector<std::uint8_t> const data;
|
std::vector<std::uint8_t> const data;
|
||||||
auto ia = nlohmann::detail::input_adapter(data);
|
auto ia = nlohmann::detail::input_adapter(data);
|
||||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||||
nlohmann::detail::binary_reader<json, decltype(ia)> const br{std::move(ia), json::input_format_t::bjdata};
|
nlohmann::detail::binary_reader<json, decltype(ia)> const br{std::move(ia), json::input_format_t::bjdata};
|
||||||
|
|
||||||
CHECK(std::is_sorted(br.bjd_optimized_type_markers.begin(), br.bjd_optimized_type_markers.end()));
|
// the excluded optimized-type markers must match binary_writer's
|
||||||
CHECK(std::is_sorted(br.bjd_types_map.begin(), br.bjd_types_map.end()));
|
// is_bjdata_excluded_type_marker(), which encodes the same 8 markers
|
||||||
|
for (const char marker :
|
||||||
|
{'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CHECK(br.is_bjd_excluded_optimized_type(marker));
|
||||||
|
}
|
||||||
|
for (const char marker :
|
||||||
|
{'U', 'i', 'u', 'I', 'm', 'l', 'M', 'L', 'd', 'D', 'C', 'B', 'x'
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CHECK(!br.is_bjd_excluded_optimized_type(marker));
|
||||||
|
}
|
||||||
|
|
||||||
|
// every dtype marker must round-trip to its ND-array type name
|
||||||
|
const std::vector<std::pair<char, std::string>> types
|
||||||
|
{
|
||||||
|
{'B', "byte"}, {'C', "char"}, {'D', "double"}, {'I', "int16"},
|
||||||
|
{'L', "int64"}, {'M', "uint64"}, {'U', "uint8"}, {'d', "single"},
|
||||||
|
{'i', "int8"}, {'l', "int32"}, {'m', "uint32"}, {'u', "uint16"}
|
||||||
|
};
|
||||||
|
for (const auto& type : types)
|
||||||
|
{
|
||||||
|
const char* name = br.bjd_type_name(type.first);
|
||||||
|
REQUIRE(name != nullptr);
|
||||||
|
CHECK(std::string(name) == type.second);
|
||||||
|
}
|
||||||
|
CHECK(br.bjd_type_name('x') == nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("individual values")
|
SECTION("individual values")
|
||||||
|
|||||||
+106
-553
@@ -13,19 +13,15 @@
|
|||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
#include <array> // array
|
#include <array> // array
|
||||||
|
#include <cfloat> // FLT_EVAL_METHOD
|
||||||
#include <cstdint> // uint32_t, uint64_t
|
#include <cstdint> // uint32_t, uint64_t
|
||||||
#include <cstdio> // snprintf
|
|
||||||
#include <cstdlib> // strtod
|
#include <cstdlib> // strtod
|
||||||
#include <cstring> // memcpy
|
#include <cstring> // memcpy
|
||||||
#include <map> // map
|
|
||||||
#include <random> // mt19937
|
|
||||||
#include <sstream> // stringstream
|
#include <sstream> // stringstream
|
||||||
#include <string> // string
|
#include <string> // string
|
||||||
#include <utility> // pair
|
#include <utility> // pair
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
|
|
||||||
#include "float_hard_cases.hpp"
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// shortcut to scan a string literal
|
// shortcut to scan a string literal
|
||||||
@@ -261,7 +257,7 @@ TEST_CASE("lexer number fast path")
|
|||||||
"123456789012345678901234567890", // huge -> float
|
"123456789012345678901234567890", // huge -> float
|
||||||
"0.30000000000000004", "2.2250738585072014e-308", "1e308",
|
"0.30000000000000004", "2.2250738585072014e-308", "1e308",
|
||||||
// high-precision / wide-exponent values that exercise the
|
// high-precision / wide-exponent values that exercise the
|
||||||
// Eisel-Lemire path beyond the Clinger subset
|
// std::from_chars (Eisel-Lemire) path beyond the Clinger subset
|
||||||
"1.7976931348623157e308", "1.2345678901234567e-250",
|
"1.7976931348623157e308", "1.2345678901234567e-250",
|
||||||
"9007199254740993", "5e-324", "1e-320"
|
"9007199254740993", "5e-324", "1e-320"
|
||||||
};
|
};
|
||||||
@@ -283,18 +279,20 @@ TEST_CASE("lexer number fast path")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("significant digits around Clinger's fast path")
|
SECTION("significant-digit gate for the Clinger fast path")
|
||||||
{
|
{
|
||||||
// Clinger's fast path needs a significand of at most 2^53, which
|
// Clinger's fast path needs a significand below 2^53, so it cannot
|
||||||
// tokens with 17 or more significant digits exceed. The conversion
|
// succeed once the mantissa has 17 or more significant digits (the
|
||||||
// splits the token at the positions the scanners recorded, so leading
|
// significand would be at least 10^16). The lexer skips the attempt
|
||||||
// zeros must not count as digits - "0.1234567890123456" has 16
|
// there. That is only allowed to save work: every value must still come
|
||||||
// significant digits, not 17 - and both scanners must agree.
|
// out bit-exactly, and both scanners must agree. In particular the gate
|
||||||
|
// must not fire for tokens whose leading zeros merely look like extra
|
||||||
|
// digits - "0.1234567890123456" has 16 significant digits, not 17.
|
||||||
const std::vector<std::string> numbers =
|
const std::vector<std::string> numbers =
|
||||||
{
|
{
|
||||||
"1234567890123456", // 16 significant digits
|
"1234567890123456", // 16 significant digits
|
||||||
"12345678901234567", // 17
|
"12345678901234567", // 17 -> attempt skipped
|
||||||
"123456789012345678", // 18
|
"123456789012345678", // 18 -> attempt skipped
|
||||||
"0.1234567890123456", // 16: the leading "0" is not significant
|
"0.1234567890123456", // 16: the leading "0" is not significant
|
||||||
"0.12345678901234567", // 17
|
"0.12345678901234567", // 17
|
||||||
"0.00000000000000001", // 1, in a long token
|
"0.00000000000000001", // 1, in a long token
|
||||||
@@ -665,323 +663,46 @@ TEST_CASE("lexer string fast path")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("lexer escape fast path")
|
TEST_CASE("parse_float_fast declines what it cannot convert exactly")
|
||||||
{
|
{
|
||||||
// json::accept() never throws, so this section stays covered without
|
// The lexer only hands well-formed numbers to parse_float_fast, so the
|
||||||
// exceptions; it pins which of the cases below are valid/invalid and
|
// malformed ones below can only be passed to it directly. Declining is
|
||||||
// checks the contiguous and streaming paths agree on that classification.
|
// always safe: the caller then falls back to a slower, exact conversion.
|
||||||
SECTION("accept() parity")
|
const auto fast = [](const std::string & s, double & out)
|
||||||
{
|
{
|
||||||
const std::vector<std::pair<std::string, bool>> cases =
|
return nlohmann::detail::parse_float_fast(s.data(), s.data() + s.size(), out);
|
||||||
{
|
|
||||||
{"\\u0041", true}, {"\\u00e4", true}, {"\\u00E4", true},
|
|
||||||
{"\\uD83D\\uDE00", true},
|
|
||||||
{"\\u12", false}, {"\\u12G4", false}, {"\\uXYZW", false},
|
|
||||||
{"\\uD800", false}, {"\\uD800A", false}, {"\\uD800\\u0041", false},
|
|
||||||
{"\\uDC00", false}, {"\\u", false}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& c : cases)
|
|
||||||
{
|
|
||||||
for (const std::size_t offset :
|
|
||||||
{
|
|
||||||
std::size_t{0}, std::size_t{9}
|
|
||||||
})
|
|
||||||
{
|
|
||||||
const std::string doc = "[\"" + std::string(offset, 'a') + c.first + "\"]";
|
|
||||||
CAPTURE(doc);
|
|
||||||
CHECK(json::accept(doc) == c.second);
|
|
||||||
std::stringstream ss(doc);
|
|
||||||
CHECK(json::accept(ss) == c.second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
// the full outcome of parsing @a doc: the parsed value, or the exact
|
|
||||||
// error message, so a mismatch in either is caught
|
|
||||||
const auto outcome = [](const std::string & doc, bool streaming) -> std::string
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (streaming)
|
|
||||||
{
|
|
||||||
std::stringstream ss(doc);
|
|
||||||
const json j = json::parse(ss);
|
|
||||||
return j.dump();
|
|
||||||
}
|
|
||||||
const json j = json::parse(doc);
|
|
||||||
return j.dump();
|
|
||||||
}
|
|
||||||
catch (const json::exception& e)
|
|
||||||
{
|
|
||||||
return {e.what()};
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
double out = 0;
|
||||||
|
|
||||||
SECTION("contiguous vs streaming parity")
|
#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0
|
||||||
{
|
// without true double precision, the fast path declines everything
|
||||||
const std::vector<std::string> escapes =
|
CHECK_FALSE(fast("1.5", out));
|
||||||
{
|
#else
|
||||||
"\\u0041", // "A"
|
CHECK(fast("1.5", out));
|
||||||
"\\u00e4", // "ä" (lowercase hex)
|
CHECK(out == 1.5);
|
||||||
"\\u00E4", // "ä" (uppercase hex)
|
CHECK(fast("+2.5e1", out));
|
||||||
"\\uD83D\\uDE00", // valid surrogate pair (an emoji)
|
CHECK(out == 25.0);
|
||||||
"\\u12", // truncated: only 2 hex digits before the closing quote
|
CHECK(fast("-25E-1", out));
|
||||||
"\\u12G4", // invalid hex digit at the 3rd position
|
CHECK(out == -2.5);
|
||||||
"\\uXYZW", // all 4 bytes invalid
|
CHECK(fast("1e", out));
|
||||||
"\\uD800", // lone high surrogate, string ends right after
|
CHECK(out == 1.0);
|
||||||
"\\uD800A", // high surrogate not followed by another \u escape
|
|
||||||
"\\uD800\\u0041", // high surrogate followed by \u, but not a low surrogate
|
|
||||||
"\\uDC00", // lone low surrogate
|
|
||||||
"\\u", // '\u' with nothing after (closing quote right away)
|
|
||||||
};
|
|
||||||
|
|
||||||
// once at the start of the string and once past the first 8-byte SWAR
|
|
||||||
// word of the outer string_bulk_run, so the escape is reached both
|
|
||||||
// right after the opening quote and mid-run
|
|
||||||
for (const auto& escape : escapes)
|
|
||||||
{
|
|
||||||
for (const std::size_t offset :
|
|
||||||
{
|
|
||||||
std::size_t{0}, std::size_t{9}
|
|
||||||
})
|
|
||||||
{
|
|
||||||
const std::string doc = "[\"" + std::string(offset, 'a') + escape + "\"]";
|
|
||||||
CAPTURE(doc);
|
|
||||||
CHECK(outcome(doc, false) == outcome(doc, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
// the escape is the last thing before end of input: no closing
|
|
||||||
// quote at all
|
|
||||||
const std::string truncated_doc = "[\"" + escape;
|
|
||||||
CAPTURE(truncated_doc);
|
|
||||||
CHECK(outcome(truncated_doc, false) == outcome(truncated_doc, true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("truncated \\u escape at every distance from the end of input")
|
|
||||||
{
|
|
||||||
// ia.bulk_remaining() must correctly report fewer than 4 bytes for
|
|
||||||
// every possible count of trailing hex-looking bytes (0, 1, 2, or 3)
|
|
||||||
// before end of input, so the fast path declines and the byte path
|
|
||||||
// alone reports the "must be followed by 4 hex digits" error, at the
|
|
||||||
// same position, in every case
|
|
||||||
for (const std::string& tail :
|
|
||||||
{
|
|
||||||
std::string{}, std::string("1"), std::string("12"), std::string("123")
|
|
||||||
})
|
|
||||||
{
|
|
||||||
const std::string doc = "[\"\\u" + tail;
|
|
||||||
CAPTURE(doc);
|
|
||||||
CHECK(outcome(doc, false) == outcome(doc, true));
|
|
||||||
CHECK(outcome(doc, false).find("must be followed by 4 hex digits") != std::string::npos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("invalid hex digit at every position of the 4")
|
|
||||||
{
|
|
||||||
// the fast path must decline for *any* invalid byte among the 4, not
|
|
||||||
// just the first, and the byte path must then stop at exactly that
|
|
||||||
// position - same as it always has
|
|
||||||
for (std::size_t bad_pos = 0; bad_pos < 4; ++bad_pos)
|
|
||||||
{
|
|
||||||
std::string digits = "1234";
|
|
||||||
digits[bad_pos] = 'g'; // not a hex digit
|
|
||||||
const std::string doc = "[\"\\u" + digits + "\"]";
|
|
||||||
CAPTURE(doc);
|
|
||||||
CHECK(outcome(doc, false) == outcome(doc, true));
|
|
||||||
CHECK(outcome(doc, false).find("must be followed by 4 hex digits") != std::string::npos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("random escapes")
|
|
||||||
{
|
|
||||||
// A seeded PRNG builds the 4 bytes following `\u` from a mix of hex
|
|
||||||
// digits and non-hex bytes, at varying distances from the start of
|
|
||||||
// the string, to compare the two scanners on many more shapes than
|
|
||||||
// are practical to enumerate by hand.
|
|
||||||
std::mt19937 gen(7654321); // NOLINT(cert-msc32-c,cert-msc51-cpp)
|
|
||||||
const std::string hex_alphabet = "0123456789AaBbCcDdEeFf";
|
|
||||||
std::uniform_int_distribution<std::size_t> pick_hex(0, hex_alphabet.size() - 1);
|
|
||||||
std::uniform_int_distribution<int> pick_byte(1, 255); // never NUL
|
|
||||||
std::uniform_int_distribution<int> pick_is_hex(0, 4); // 4-in-5 chance of a hex digit
|
|
||||||
std::uniform_int_distribution<std::size_t> pick_offset(0, 12);
|
|
||||||
|
|
||||||
std::vector<std::string> mismatches;
|
|
||||||
for (int iter = 0; iter < 3000; ++iter)
|
|
||||||
{
|
|
||||||
std::string digits;
|
|
||||||
for (int i = 0; i < 4; ++i)
|
|
||||||
{
|
|
||||||
if (pick_is_hex(gen) != 0)
|
|
||||||
{
|
|
||||||
digits += hex_alphabet[pick_hex(gen)];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
char c = static_cast<char>(pick_byte(gen));
|
|
||||||
if (c == '"' || c == '\\')
|
|
||||||
{
|
|
||||||
// keep the string well-formed apart from the escape
|
|
||||||
// itself, so any mismatch is attributable to the \u
|
|
||||||
// handling and not to an unrelated quote/escape
|
|
||||||
c = 'z';
|
|
||||||
}
|
|
||||||
digits += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const std::string doc = "[\"" + std::string(pick_offset(gen), 'a') + "\\u" + digits + "\"]";
|
|
||||||
if (outcome(doc, false) != outcome(doc, true))
|
|
||||||
{
|
|
||||||
mismatches.push_back(doc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CAPTURE(mismatches);
|
|
||||||
CHECK(mismatches.empty());
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
namespace
|
// not a number
|
||||||
{
|
CHECK_FALSE(fast("", out));
|
||||||
// the index of the decimal point (or npos) and of the end of the mantissa of a
|
CHECK_FALSE(fast("-", out));
|
||||||
// number token, which the lexer records while scanning it
|
CHECK_FALSE(fast(".", out));
|
||||||
std::pair<std::size_t, std::size_t> float_token_layout(const std::string& s)
|
CHECK_FALSE(fast("1.2.3", out));
|
||||||
{
|
CHECK_FALSE(fast("1x", out));
|
||||||
std::size_t dot = std::string::npos;
|
CHECK_FALSE(fast("1e+", out));
|
||||||
std::size_t mantissa_end = s.size();
|
CHECK_FALSE(fast("1e1x", out));
|
||||||
for (std::size_t i = 0; i < s.size(); ++i)
|
|
||||||
{
|
|
||||||
if (s[i] == '.')
|
|
||||||
{
|
|
||||||
dot = i;
|
|
||||||
}
|
|
||||||
else if (s[i] == 'e' || s[i] == 'E')
|
|
||||||
{
|
|
||||||
mantissa_end = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {dot, mantissa_end};
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename FloatType>
|
// numbers that are not represented exactly on the fast path
|
||||||
FloatType parse_native(const std::string& s)
|
CHECK_FALSE(fast("12345678901234567890", out));
|
||||||
{
|
CHECK_FALSE(fast("1e10000", out));
|
||||||
const auto layout = float_token_layout(s);
|
CHECK_FALSE(fast("9007199254740993", out));
|
||||||
return nlohmann::detail::parse_float_native<FloatType>(s.data(), s.data() + s.size(), layout.first, layout.second);
|
CHECK_FALSE(fast("1e23", out));
|
||||||
}
|
CHECK_FALSE(fast("1e-23", out));
|
||||||
|
|
||||||
std::uint64_t bits_of(double d)
|
|
||||||
{
|
|
||||||
std::uint64_t b = 0;
|
|
||||||
std::memcpy(&b, &d, sizeof(b));
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::uint32_t bits_of(float f)
|
|
||||||
{
|
|
||||||
std::uint32_t b = 0;
|
|
||||||
std::memcpy(&b, &f, sizeof(b));
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::uint64_t native_bits64(const std::string& s)
|
|
||||||
{
|
|
||||||
return bits_of(parse_native<double>(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::uint32_t native_bits32(const std::string& s)
|
|
||||||
{
|
|
||||||
return bits_of(parse_native<float>(s));
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("parse_float_native rounds correctly")
|
|
||||||
{
|
|
||||||
SECTION("double")
|
|
||||||
{
|
|
||||||
CHECK(native_bits64("1.5") == 0x3FF8000000000000u);
|
|
||||||
CHECK(native_bits64("0.1") == 0x3FB999999999999Au);
|
|
||||||
CHECK(native_bits64("-0.0") == 0x8000000000000000u);
|
|
||||||
CHECK(native_bits64("0e999999999999999999999") == 0u);
|
|
||||||
// 2^53 + 1 is exactly between two doubles: ties to even, unless more digits follow
|
|
||||||
CHECK(native_bits64("9007199254740993") == 0x4340000000000000u);
|
|
||||||
CHECK(native_bits64("9007199254740993.0000000000000000001") == 0x4340000000000001u);
|
|
||||||
CHECK(native_bits64("9007199254740992.9999999999999999999") == 0x4340000000000000u);
|
|
||||||
// 1 + 2^-53 exactly (a tie), and one unit in the 55th digit around it
|
|
||||||
CHECK(native_bits64("1.00000000000000011102230246251565404236316680908203125") == 0x3FF0000000000000u);
|
|
||||||
CHECK(native_bits64("1.00000000000000011102230246251565404236316680908203126") == 0x3FF0000000000001u);
|
|
||||||
CHECK(native_bits64("1.00000000000000011102230246251565404236316680908203124") == 0x3FF0000000000000u);
|
|
||||||
// subnormal and overflow boundaries
|
|
||||||
CHECK(native_bits64("2.4703282292062327e-324") == 0u);
|
|
||||||
CHECK(native_bits64("2.4703282292062328e-324") == 1u);
|
|
||||||
CHECK(native_bits64("2.2250738585072011e-308") == 0x000FFFFFFFFFFFFFu);
|
|
||||||
CHECK(native_bits64("2.2250738585072012e-308") == 0x0010000000000000u);
|
|
||||||
CHECK(native_bits64("1.7976931348623157e308") == 0x7FEFFFFFFFFFFFFFu);
|
|
||||||
CHECK(native_bits64("1.7976931348623159e308") == 0x7FF0000000000000u);
|
|
||||||
CHECK(native_bits64("-1e400") == 0xFFF0000000000000u);
|
|
||||||
CHECK(native_bits64("-1e-400") == 0x8000000000000000u);
|
|
||||||
// exponents and zeros far beyond the range cancel out
|
|
||||||
CHECK(native_bits64("0." + std::string(1000, '0') + "1e1001") == 0x3FF0000000000000u);
|
|
||||||
CHECK(native_bits64("1" + std::string(1000, '0') + "e-1000") == 0x3FF0000000000000u);
|
|
||||||
CHECK(native_bits64("1e-99999999999999999999999") == 0u);
|
|
||||||
CHECK(native_bits64("1E+99999999999999999999999") == 0x7FF0000000000000u);
|
|
||||||
// more digits than any midpoint has (769): only whether a nonzero digit follows matters
|
|
||||||
const std::string tie = "1.00000000000000011102230246251565404236316680908203125";
|
|
||||||
CHECK(native_bits64(tie + std::string(800, '0')) == 0x3FF0000000000000u);
|
|
||||||
CHECK(native_bits64(tie + std::string(800, '0') + "1") == 0x3FF0000000000001u);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("float")
|
|
||||||
{
|
|
||||||
CHECK(native_bits32("1.5") == 0x3FC00000u);
|
|
||||||
CHECK(native_bits32("0.1") == 0x3DCCCCCDu);
|
|
||||||
CHECK(native_bits32("-0.0") == 0x80000000u);
|
|
||||||
// 2^24 + 1 is exactly between two floats
|
|
||||||
CHECK(native_bits32("16777217") == 0x4B800000u);
|
|
||||||
CHECK(native_bits32("16777217.000000000000000000001") == 0x4B800001u);
|
|
||||||
CHECK(native_bits32("16777218.999999999999999999999") == 0x4B800001u);
|
|
||||||
CHECK(native_bits32("16777219") == 0x4B800002u);
|
|
||||||
// subnormal and overflow boundaries
|
|
||||||
CHECK(native_bits32("3.4028235677973366e38") == 0x7F7FFFFFu);
|
|
||||||
CHECK(native_bits32("3.4028235677973367e38") == 0x7F800000u);
|
|
||||||
CHECK(native_bits32("7.006492321624085e-46") == 0u);
|
|
||||||
CHECK(native_bits32("7.006492321624086e-46") == 1u);
|
|
||||||
CHECK(native_bits32("1.1754942e-38") == 0x007FFFFFu);
|
|
||||||
CHECK(native_bits32("-1.17549435e-38") == 0x80800000u);
|
|
||||||
CHECK(native_bits32("1e39") == 0x7F800000u);
|
|
||||||
CHECK(native_bits32("-1e-50") == 0x80000000u);
|
|
||||||
// not rounded through double: its double would round to another float
|
|
||||||
CHECK(native_bits32("1.00000005960464477539062500000000001") == 0x3F800001u);
|
|
||||||
CHECK(native_bits32("9007199254740993") == 0x5A000000u);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("the conversion shared with other parsers")
|
|
||||||
{
|
|
||||||
// convert_float() gives the lexer's results, for every type
|
|
||||||
const std::vector<std::string> tokens =
|
|
||||||
{
|
|
||||||
"0", "-0.0", "1.5", "0.1", "1e-400", "-2.5E+3", "123456789012345678901234567890",
|
|
||||||
"9007199254740993.0000000000000000001", "4.9406564584124654e-324"
|
|
||||||
};
|
|
||||||
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
|
||||||
using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>;
|
|
||||||
for (const auto& t : tokens)
|
|
||||||
{
|
|
||||||
CAPTURE(t);
|
|
||||||
const auto layout = float_token_layout(t);
|
|
||||||
const char* const first = t.data();
|
|
||||||
const char* const last = first + t.size();
|
|
||||||
const auto d = nlohmann::detail::convert_float<double>(first, last, layout.first, layout.second);
|
|
||||||
const auto f = nlohmann::detail::convert_float<float>(first, last, layout.first, layout.second);
|
|
||||||
const auto ld = nlohmann::detail::convert_float<long double>(first, last, layout.first, layout.second);
|
|
||||||
CHECK(bits_of(d) == bits_of(json::parse(t).get<double>()));
|
|
||||||
CHECK(bits_of(f) == bits_of(float_json::parse(t).get<float>()));
|
|
||||||
CHECK(ld == long_double_json::parse(t).get<long double>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -1085,6 +806,40 @@ std::size_t big_bit_length(const big_uint& a)
|
|||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::uint64_t bits_of(double d)
|
||||||
|
{
|
||||||
|
std::uint64_t b = 0;
|
||||||
|
std::memcpy(&b, &d, sizeof(b));
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool eisel_lemire(const std::string& s, double& out)
|
||||||
|
{
|
||||||
|
return nlohmann::detail::parse_float_eisel_lemire(s.data(), s.data() + s.size(), out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// significant digits of a token, without trailing zeros
|
||||||
|
std::size_t significant_digits(const std::string& s)
|
||||||
|
{
|
||||||
|
std::string digits;
|
||||||
|
for (const char c : s)
|
||||||
|
{
|
||||||
|
if (c == 'e' || c == 'E')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (c >= '0' && c <= '9' && !(digits.empty() && c == '0'))
|
||||||
|
{
|
||||||
|
digits += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!digits.empty() && digits.back() == '0')
|
||||||
|
{
|
||||||
|
digits.pop_back();
|
||||||
|
}
|
||||||
|
return digits.size();
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("Eisel-Lemire float conversion")
|
TEST_CASE("Eisel-Lemire float conversion")
|
||||||
@@ -1482,33 +1237,26 @@ TEST_CASE("Eisel-Lemire float conversion")
|
|||||||
for (const auto& c : known)
|
for (const auto& c : known)
|
||||||
{
|
{
|
||||||
CAPTURE(c.first);
|
CAPTURE(c.first);
|
||||||
CHECK(native_bits64(c.first) == c.second);
|
double out = 0;
|
||||||
|
if (eisel_lemire(c.first, out))
|
||||||
|
{
|
||||||
|
CHECK(bits_of(out) == c.second);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// only tokens with more than 19 significant digits are left to
|
||||||
|
// strtod: those whose value lies too close to a tie
|
||||||
|
CHECK(significant_digits(c.first) > 19);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("binary32")
|
|
||||||
{
|
|
||||||
using binary32 = nlohmann::detail::ieee_binary_format<24>;
|
|
||||||
CHECK(nlohmann::detail::eisel_lemire<binary32>(0, 1) == 0x3F800000u);
|
|
||||||
CHECK(nlohmann::detail::eisel_lemire<binary32>(-1, 1) == 0x3DCCCCCDu);
|
|
||||||
CHECK(nlohmann::detail::eisel_lemire<binary32>(-1, 15) == 0x3FC00000u);
|
|
||||||
CHECK(nlohmann::detail::eisel_lemire<binary32>(0, 16777217) == 0x4B800000u); // tie, to even
|
|
||||||
CHECK(nlohmann::detail::eisel_lemire<binary32>(0, 16777219) == 0x4B800002u); // tie, to even
|
|
||||||
CHECK(nlohmann::detail::eisel_lemire<binary32>(-45, 1) == 0x00000001u);
|
|
||||||
CHECK(nlohmann::detail::eisel_lemire<binary32>(-46, 7) == 0x00000000u);
|
|
||||||
CHECK(nlohmann::detail::eisel_lemire<binary32>(-46, 8) == 0x00000001u);
|
|
||||||
CHECK(nlohmann::detail::eisel_lemire<binary32>(-65, 9999999999999999999u) == 0x00000000u);
|
|
||||||
CHECK(nlohmann::detail::eisel_lemire<binary32>(20, 3402823466385288598u) == 0x7F7FFFFFu);
|
|
||||||
CHECK(nlohmann::detail::eisel_lemire<binary32>(20, 3402823669209384635u) == 0x7F800000u);
|
|
||||||
CHECK(nlohmann::detail::eisel_lemire<binary32>(39, 1) == 0x7F800000u);
|
|
||||||
CHECK(nlohmann::detail::eisel_lemire<binary32>(-5, 0) == 0x00000000u);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("round trip")
|
SECTION("round trip")
|
||||||
{
|
{
|
||||||
// every double written by to_chars and read back, and its 17-digit
|
// every double written by to_chars and read back, also with trailing
|
||||||
// form with trailing digits that make the token longer than 19 digits
|
// digits that make the token longer than 19 digits
|
||||||
std::uint64_t state = 5295;
|
std::uint64_t state = 5295;
|
||||||
|
std::size_t declined = 0;
|
||||||
for (int i = 0; i < 200000; ++i)
|
for (int i = 0; i < 200000; ++i)
|
||||||
{
|
{
|
||||||
state ^= state << 13u;
|
state ^= state << 13u;
|
||||||
@@ -1530,51 +1278,30 @@ TEST_CASE("Eisel-Lemire float conversion")
|
|||||||
const char* end = nlohmann::detail::to_chars(buffer.data(), buffer.data() + buffer.size(), d);
|
const char* end = nlohmann::detail::to_chars(buffer.data(), buffer.data() + buffer.size(), d);
|
||||||
const std::string token(buffer.data(), static_cast<std::size_t>(end - buffer.data()));
|
const std::string token(buffer.data(), static_cast<std::size_t>(end - buffer.data()));
|
||||||
CAPTURE(token);
|
CAPTURE(token);
|
||||||
CHECK(native_bits64(token) == b);
|
double out = 0;
|
||||||
|
REQUIRE(eisel_lemire(token, out));
|
||||||
|
CHECK(bits_of(out) == b);
|
||||||
|
|
||||||
// insert digits before the exponent of the 17-digit form: that
|
// insert digits before the exponent: the value moves by far less
|
||||||
// form lies strictly inside the rounding interval of the double
|
// than the distance to the rounding boundary, so it must not change
|
||||||
// (the shortest one may lie on its boundary), and the digits move
|
std::string longer = token;
|
||||||
// it by far less than the distance to the boundary, so the value
|
|
||||||
// must not change
|
|
||||||
std::array<char, 64> digits17{};
|
|
||||||
static_cast<void>(std::snprintf(digits17.data(), digits17.size(), "%.17g", d)); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
|
||||||
std::string longer = digits17.data();
|
|
||||||
const std::size_t e = longer.find('e');
|
const std::size_t e = longer.find('e');
|
||||||
const std::size_t dot = longer.find('.');
|
const std::size_t dot = longer.find('.');
|
||||||
const std::string extra = dot == std::string::npos ? ".000000000000000000001" : "000000000000000000001";
|
const std::string extra = dot == std::string::npos ? ".000000000000000000001" : "000000000000000000001";
|
||||||
longer.insert(e == std::string::npos ? longer.size() : e, extra);
|
longer.insert(e == std::string::npos ? longer.size() : e, extra);
|
||||||
CAPTURE(longer);
|
CAPTURE(longer);
|
||||||
CHECK(native_bits64(longer) == b);
|
if (eisel_lemire(longer, out))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("round trip, binary32")
|
|
||||||
{
|
|
||||||
std::uint32_t state = 5295;
|
|
||||||
for (int i = 0; i < 100000; ++i)
|
|
||||||
{
|
|
||||||
state ^= state << 13u;
|
|
||||||
state ^= state >> 17u;
|
|
||||||
state ^= state << 5u;
|
|
||||||
std::uint32_t b = state;
|
|
||||||
if ((b & 0x7F800000u) == 0x7F800000u)
|
|
||||||
{
|
{
|
||||||
continue; // infinity or NaN
|
CHECK(bits_of(out) == b);
|
||||||
}
|
}
|
||||||
if (i % 4 == 0)
|
else
|
||||||
{
|
{
|
||||||
b &= 0x807FFFFFu; // subnormals
|
// w and w + 1 round differently: only when the value is very
|
||||||
|
// close to a rounding boundary
|
||||||
|
++declined;
|
||||||
}
|
}
|
||||||
float f = 0;
|
|
||||||
std::memcpy(&f, &b, sizeof(f));
|
|
||||||
|
|
||||||
std::array<char, 64> buffer{};
|
|
||||||
const char* end = nlohmann::detail::to_chars(buffer.data(), buffer.data() + buffer.size(), f);
|
|
||||||
const std::string token(buffer.data(), static_cast<std::size_t>(end - buffer.data()));
|
|
||||||
CAPTURE(token);
|
|
||||||
CHECK(native_bits32(token) == b);
|
|
||||||
}
|
}
|
||||||
|
CHECK(declined < 1000); // 107 of the 200,000
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("used by the lexer")
|
SECTION("used by the lexer")
|
||||||
@@ -1588,177 +1315,3 @@ TEST_CASE("Eisel-Lemire float conversion")
|
|||||||
"[json.exception.out_of_range.406] number overflow parsing '1.7976931348623159e308'", json::out_of_range&);
|
"[json.exception.out_of_range.406] number overflow parsing '1.7976931348623159e308'", json::out_of_range&);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
|
||||||
|
|
||||||
// the bits of the float that parse() gives for a token, via both scanners;
|
|
||||||
// the value must be the same for both
|
|
||||||
template<typename Json, typename Bits>
|
|
||||||
void check_parse(const std::string& token, Bits expected, Bits infinity)
|
|
||||||
{
|
|
||||||
std::stringstream stream(token);
|
|
||||||
if ((expected & ~(Bits{1} << (8 * sizeof(Bits) - 1))) == infinity)
|
|
||||||
{
|
|
||||||
Json _;
|
|
||||||
CHECK_THROWS_WITH_AS(_ = Json::parse(token), ("[json.exception.out_of_range.406] number overflow parsing '" + token + "'").c_str(), typename Json::out_of_range&);
|
|
||||||
CHECK_THROWS_WITH_AS(_ = Json::parse(stream), ("[json.exception.out_of_range.406] number overflow parsing '" + token + "'").c_str(), typename Json::out_of_range&);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const Json contiguous = Json::parse(token);
|
|
||||||
const Json streamed = Json::parse(stream);
|
|
||||||
if (contiguous.is_number_float()) // not an integer that fits
|
|
||||||
{
|
|
||||||
CHECK(bits_of(contiguous.template get<typename Json::number_float_t>()) == expected);
|
|
||||||
CHECK(bits_of(streamed.template get<typename Json::number_float_t>()) == expected);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CHECK(streamed.is_number_integer());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("float conversion of hard cases")
|
|
||||||
{
|
|
||||||
// see float_hard_cases.hpp
|
|
||||||
for (const auto& c : float_hard_cases::cases())
|
|
||||||
{
|
|
||||||
const std::string token = c.token;
|
|
||||||
CAPTURE(token);
|
|
||||||
CHECK(native_bits64(token) == c.bits64);
|
|
||||||
CHECK(native_bits32(token) == c.bits32);
|
|
||||||
check_parse<json>(token, c.bits64, std::uint64_t{0x7FF0000000000000u});
|
|
||||||
check_parse<float_json>(token, c.bits32, std::uint32_t{0x7F800000u});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("float overflow and underflow in the parser")
|
|
||||||
{
|
|
||||||
SECTION("double")
|
|
||||||
{
|
|
||||||
check_parse<json>("1.7976931348623157e308", std::uint64_t{0x7FEFFFFFFFFFFFFFu}, std::uint64_t{0x7FF0000000000000u});
|
|
||||||
check_parse<json>("1.7976931348623159e308", std::uint64_t{0x7FF0000000000000u}, std::uint64_t{0x7FF0000000000000u});
|
|
||||||
check_parse<json>("-1e309", std::uint64_t{0xFFF0000000000000u}, std::uint64_t{0x7FF0000000000000u});
|
|
||||||
check_parse<json>("1" + std::string(400, '0'), std::uint64_t{0x7FF0000000000000u}, std::uint64_t{0x7FF0000000000000u});
|
|
||||||
check_parse<json>("1e99999999999999999999", std::uint64_t{0x7FF0000000000000u}, std::uint64_t{0x7FF0000000000000u});
|
|
||||||
// an underflow gives a zero with the sign of the token
|
|
||||||
check_parse<json>("1e-400", std::uint64_t{0}, std::uint64_t{0x7FF0000000000000u});
|
|
||||||
check_parse<json>("-1e-400", std::uint64_t{0x8000000000000000u}, std::uint64_t{0x7FF0000000000000u});
|
|
||||||
check_parse<json>("-2.4703282292062327e-324", std::uint64_t{0x8000000000000000u}, std::uint64_t{0x7FF0000000000000u});
|
|
||||||
check_parse<json>("0." + std::string(400, '0') + "1", std::uint64_t{0}, std::uint64_t{0x7FF0000000000000u});
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("float")
|
|
||||||
{
|
|
||||||
check_parse<float_json>("3.4028234e38", std::uint32_t{0x7F7FFFFFu}, std::uint32_t{0x7F800000u});
|
|
||||||
check_parse<float_json>("3.4028236e38", std::uint32_t{0x7F800000u}, std::uint32_t{0x7F800000u});
|
|
||||||
check_parse<float_json>("-1e39", std::uint32_t{0xFF800000u}, std::uint32_t{0x7F800000u});
|
|
||||||
check_parse<float_json>("1e-46", std::uint32_t{0}, std::uint32_t{0x7F800000u});
|
|
||||||
check_parse<float_json>("-1e-46", std::uint32_t{0x80000000u}, std::uint32_t{0x7F800000u});
|
|
||||||
check_parse<float_json>("-7.006492321624085e-46", std::uint32_t{0x80000000u}, std::uint32_t{0x7F800000u});
|
|
||||||
check_parse<float_json>("-7.006492321624086e-46", std::uint32_t{0x80000001u}, std::uint32_t{0x7F800000u});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("string scanning kernels")
|
|
||||||
{
|
|
||||||
// the word-at-a-time kernels must stop exactly where a byte-by-byte scan
|
|
||||||
// stops, for any content, length, and alignment
|
|
||||||
const auto reference_special = [](const unsigned char* data, std::size_t n)
|
|
||||||
{
|
|
||||||
std::size_t i = 0;
|
|
||||||
while (i < n && !nlohmann::detail::is_string_special(data[i]))
|
|
||||||
{
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
return i;
|
|
||||||
};
|
|
||||||
const auto reference_copyable = [](const unsigned char* data, std::size_t n)
|
|
||||||
{
|
|
||||||
std::size_t i = 0;
|
|
||||||
while (i < n && nlohmann::detail::is_ascii_copyable(data[i]))
|
|
||||||
{
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
return i;
|
|
||||||
};
|
|
||||||
const auto reference_bulk_run = [](const unsigned char* data, std::size_t n)
|
|
||||||
{
|
|
||||||
std::size_t i = 0;
|
|
||||||
while (i < n)
|
|
||||||
{
|
|
||||||
if (data[i] < 0x80u)
|
|
||||||
{
|
|
||||||
if (nlohmann::detail::is_string_special(data[i]))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++i;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const std::size_t seq = nlohmann::detail::validate_one_utf8(data + i, n - i);
|
|
||||||
if (seq == 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
i += seq;
|
|
||||||
}
|
|
||||||
return i;
|
|
||||||
};
|
|
||||||
|
|
||||||
// pieces: ordinary ASCII, stops, DEL, well-formed sequences of every
|
|
||||||
// length, and ill-formed or truncated ones
|
|
||||||
const std::vector<std::string> pieces =
|
|
||||||
{
|
|
||||||
"a", "Z", " ", "~", "0123456789", "\"", "\\", std::string(1, '\0'), "\n", "\x1F", "\x7F",
|
|
||||||
"\xC3\xA4", "\xE2\x82\xAC", "\xE6\x97\xA5\xE6\x9C\xAC", "\xF0\x9F\x98\x80", "\xED\x9F\xBF",
|
|
||||||
"\x80", "\xC0\x80", "\xC3", "\xE2\x82", "\xED\xA0\x80", "\xF4\x90\x80\x80", "\xFF",
|
|
||||||
};
|
|
||||||
std::uint64_t state = 5295;
|
|
||||||
const auto next = [&state]()
|
|
||||||
{
|
|
||||||
state ^= state << 13u;
|
|
||||||
state ^= state >> 7u;
|
|
||||||
state ^= state << 17u;
|
|
||||||
return state;
|
|
||||||
};
|
|
||||||
// the upper half as a 32-bit value: converts to std::size_t implicitly on
|
|
||||||
// every platform (a cast of std::uint64_t is useless where both are the
|
|
||||||
// same type, and required where std::size_t is 32 bits wide)
|
|
||||||
const auto next_small = [&next]()
|
|
||||||
{
|
|
||||||
return static_cast<std::uint32_t>(next() >> 32u);
|
|
||||||
};
|
|
||||||
for (int round = 0; round < 100000; ++round)
|
|
||||||
{
|
|
||||||
// mostly ordinary text, so that runs span several words
|
|
||||||
std::string text(next_small() % 8u, '.');
|
|
||||||
const std::size_t count = next_small() % 12u;
|
|
||||||
for (std::size_t k = 0; k < count; ++k)
|
|
||||||
{
|
|
||||||
const std::size_t p = (next() % 4 == 0) ? next_small() % pieces.size() : 0;
|
|
||||||
text += pieces[p];
|
|
||||||
text += std::string(next_small() % 10u, 'x');
|
|
||||||
}
|
|
||||||
const auto* data = reinterpret_cast<const unsigned char*>(text.data()); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
||||||
for (std::size_t offset = 0; offset < 3 && offset <= text.size(); ++offset)
|
|
||||||
{
|
|
||||||
const std::size_t n = text.size() - offset;
|
|
||||||
CAPTURE(text);
|
|
||||||
CAPTURE(offset);
|
|
||||||
CHECK(nlohmann::detail::find_string_special(data + offset, n) == reference_special(data + offset, n));
|
|
||||||
CHECK(nlohmann::detail::find_ascii_copyable_run(data + offset, n) == reference_copyable(data + offset, n));
|
|
||||||
CHECK(nlohmann::detail::scalar_string_bulk_run(data + offset, n) == reference_bulk_run(data + offset, n));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// the trailing-zero count, whichever implementation the compiler gets
|
|
||||||
for (int k = 0; k < 64; ++k)
|
|
||||||
{
|
|
||||||
const std::uint64_t bit = std::uint64_t{1} << k;
|
|
||||||
CHECK(nlohmann::detail::count_trailing_zeros(bit) == k);
|
|
||||||
CHECK(nlohmann::detail::count_trailing_zeros(bit | (bit << 1u) | 0x8000000000000000u) == k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -143,11 +143,13 @@ class SaxEventLogger
|
|||||||
{
|
{
|
||||||
errored = true;
|
errored = true;
|
||||||
events.push_back("parse_error(" + std::to_string(position) + ")");
|
events.push_back("parse_error(" + std::to_string(position) + ")");
|
||||||
return false;
|
return recover;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
|
std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
|
||||||
bool errored = false;
|
bool errored = false;
|
||||||
|
/// whether parse_error() asks the parser to recover from the error (see #3989)
|
||||||
|
bool recover = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SaxCountdown : public nlohmann::json::json_sax_t
|
class SaxCountdown : public nlohmann::json::json_sax_t
|
||||||
@@ -2937,3 +2939,583 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
/// builds a value like json::parse(), but asks the parser to recover from
|
||||||
|
/// errors (see #3989), and checks that the events it receives are balanced
|
||||||
|
class RecoveringDomParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit RecoveringDomParser(json& j, std::size_t max_errors_ = static_cast<std::size_t>(-1))
|
||||||
|
: dom(j, false)
|
||||||
|
, max_errors(max_errors_)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool null()
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.null();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool boolean(bool val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.boolean(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_integer(json::number_integer_t val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.number_integer(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_unsigned(json::number_unsigned_t val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.number_unsigned(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_float(json::number_float_t val, const std::string& s)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.number_float(val, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool string(std::string& val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.string(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool binary(json::binary_t& val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.binary(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool start_object(std::size_t elements)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
stack.push_back('o');
|
||||||
|
return dom.start_object(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool key(std::string& val)
|
||||||
|
{
|
||||||
|
++events;
|
||||||
|
if (stack.empty() || stack.back() != 'o')
|
||||||
|
{
|
||||||
|
well_formed = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stack.back() = 'v';
|
||||||
|
return dom.key(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool end_object()
|
||||||
|
{
|
||||||
|
++events;
|
||||||
|
if (stack.empty() || stack.back() != 'o')
|
||||||
|
{
|
||||||
|
well_formed = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stack.pop_back();
|
||||||
|
return dom.end_object();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool start_array(std::size_t elements)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
stack.push_back('a');
|
||||||
|
return dom.start_array(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool end_array()
|
||||||
|
{
|
||||||
|
++events;
|
||||||
|
if (stack.empty() || stack.back() != 'a')
|
||||||
|
{
|
||||||
|
well_formed = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stack.pop_back();
|
||||||
|
return dom.end_array();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& ex)
|
||||||
|
{
|
||||||
|
errors.emplace_back(ex.what());
|
||||||
|
return errors.size() < max_errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// whether the events were balanced and every key was followed by a value
|
||||||
|
bool balanced() const
|
||||||
|
{
|
||||||
|
return well_formed && stack.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// builds the value
|
||||||
|
nlohmann::detail::json_sax_dom_parser<json> dom;
|
||||||
|
std::vector<std::string> errors {}; // NOLINT(readability-redundant-member-init)
|
||||||
|
std::size_t events = 0;
|
||||||
|
/// the open containers: 'a' for an array, 'o' for an object that expects
|
||||||
|
/// a key, 'v' for an object that expects the value of a key
|
||||||
|
std::vector<char> stack {}; // NOLINT(readability-redundant-member-init)
|
||||||
|
bool well_formed = true;
|
||||||
|
std::size_t max_errors;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// a value is passed: it is an array element, or the value of a key
|
||||||
|
void value()
|
||||||
|
{
|
||||||
|
++events;
|
||||||
|
if (!stack.empty())
|
||||||
|
{
|
||||||
|
if (stack.back() == 'v')
|
||||||
|
{
|
||||||
|
stack.back() = 'o';
|
||||||
|
}
|
||||||
|
else if (stack.back() == 'o')
|
||||||
|
{
|
||||||
|
// a value without a key
|
||||||
|
well_formed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RecoveryResult
|
||||||
|
{
|
||||||
|
json value;
|
||||||
|
std::vector<std::string> errors;
|
||||||
|
std::size_t events;
|
||||||
|
bool ok;
|
||||||
|
bool balanced;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename InputType>
|
||||||
|
RecoveryResult parse_recovering(InputType&& input, const bool strict = true,
|
||||||
|
const bool ignore_comments = false, const bool ignore_trailing_commas = false)
|
||||||
|
{
|
||||||
|
json j;
|
||||||
|
RecoveringDomParser sax(j);
|
||||||
|
const bool ok = json::sax_parse(std::forward<InputType>(input), &sax, json::input_format_t::json,
|
||||||
|
strict, ignore_comments, ignore_trailing_commas);
|
||||||
|
return {j, sax.errors, sax.events, ok, sax.balanced()};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// stops after a number of events, but recovers from errors
|
||||||
|
class RecoveringCountdown : public SaxCountdown
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using SaxCountdown::SaxCountdown;
|
||||||
|
|
||||||
|
bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// a repaired input: the value it is repaired to, and the number of errors
|
||||||
|
struct Repair
|
||||||
|
{
|
||||||
|
const char* input;
|
||||||
|
const char* expected;
|
||||||
|
std::size_t errors;
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("parser error recovery (#3989)")
|
||||||
|
{
|
||||||
|
SECTION("repairs")
|
||||||
|
{
|
||||||
|
const std::vector<Repair> repairs =
|
||||||
|
{
|
||||||
|
// a missing separator is inserted
|
||||||
|
{"[1 2]", "[1,2]", 1},
|
||||||
|
{R"({"a":1 "b":2})", R"({"a":1,"b":2})", 1},
|
||||||
|
{R"({"a" 1})", R"({"a":1})", 1},
|
||||||
|
{"[1 tru 2]", "[1,null,2]", 2},
|
||||||
|
{R"({"a" "b": 1})", R"({"a":"b"})", 2},
|
||||||
|
|
||||||
|
// a missing value is null in an object; in an array, a ',' stands
|
||||||
|
// for null, while an array that ends there just ends
|
||||||
|
{R"({"a":})", R"({"a":null})", 1},
|
||||||
|
{R"({"a"})", R"({"a":null})", 1},
|
||||||
|
{R"({"a","b":1})", R"({"a":null,"b":1})", 1},
|
||||||
|
{"[1,,2]", "[1,null,2]", 1},
|
||||||
|
{"[,1]", "[null,1]", 1},
|
||||||
|
{"[1,]", "[1]", 1},
|
||||||
|
{"[1,2,3,]", "[1,2,3]", 1},
|
||||||
|
{R"({"a":1,})", R"({"a":1})", 1},
|
||||||
|
|
||||||
|
// a broken string keeps what can be read
|
||||||
|
{R"(["a\qb"])", R"(["aqb"])", 1},
|
||||||
|
{R"({"na\me":1})", R"({"name":1})", 1},
|
||||||
|
{"[\"\xFF\"]", R"(["\uFFFD"])", 1},
|
||||||
|
{"[\"a\xC3(\"]", R"(["a\uFFFD("])", 1},
|
||||||
|
{"[\"\xE2\x82\"]", R"(["\uFFFD"])", 1},
|
||||||
|
{"[\"\xC3\\\\\", 1]", R"(["\uFFFD\\",1])", 1},
|
||||||
|
{R"(["\u12"])", R"(["\uFFFD"])", 1},
|
||||||
|
{R"(["\u12G4"])", R"(["\uFFFDG4"])", 1},
|
||||||
|
{R"(["\uDC00x"])", R"(["\uFFFDx"])", 1},
|
||||||
|
{R"(["\uD800x"])", R"(["\uFFFDx"])", 1},
|
||||||
|
{R"(["\uD800\u0041"])", R"(["\uFFFDA"])", 1},
|
||||||
|
{R"(["\uD800\uD800\uDC00"])", R"(["\uFFFD\uD800\uDC00"])", 1},
|
||||||
|
{R"(["\uD800\uD800\uD800x"])", R"(["\uFFFD\uFFFD\uFFFDx"])", 1},
|
||||||
|
{
|
||||||
|
R"(["\uD800\"x", 1])", R"(["\uFFFD\"x",1])", 1
|
||||||
|
},
|
||||||
|
{R"(["\uD800\q"])", R"(["\uFFFDq"])", 1},
|
||||||
|
{"[\"a\tb\"]", R"(["a\tb"])", 1},
|
||||||
|
{R"(["a\qb\u0041\x"])", R"(["aqbAx"])", 1},
|
||||||
|
|
||||||
|
// a broken number keeps its longest valid prefix
|
||||||
|
{"[1.]", "[1]", 1},
|
||||||
|
{"[-2.]", "[-2]", 1},
|
||||||
|
{"[1.5e]", "[1.5]", 1},
|
||||||
|
{"[1e+]", "[1]", 1},
|
||||||
|
{"[1.x2, 3]", "[1,3]", 1},
|
||||||
|
|
||||||
|
// what cannot be read at all is null
|
||||||
|
{"[1,NaN,3]", "[1,null,3]", 1},
|
||||||
|
{"[tru]", "[null]", 1},
|
||||||
|
{"[-]", "[null]", 1},
|
||||||
|
{R"({"a":Infinity})", R"({"a":null})", 1},
|
||||||
|
|
||||||
|
// a stray token is dropped
|
||||||
|
{"[:1]", "[1]", 1},
|
||||||
|
{R"(["a":1])", R"(["a",1])", 1},
|
||||||
|
{R"({"a"::1})", R"({"a":1})", 1},
|
||||||
|
|
||||||
|
// a member that cannot be read is skipped
|
||||||
|
{R"({1:2,"b":3})", R"({"b":3})", 1},
|
||||||
|
{R"({"a":1 2})", R"({"a":1})", 1},
|
||||||
|
{R"({,"a":1})", R"({"a":1})", 1},
|
||||||
|
{R"({"a":1,,"b":2})", R"({"a":1,"b":2})", 1},
|
||||||
|
{"{a:1}", "{}", 1},
|
||||||
|
{R"({"a":1 [1,{"b":2}], "c":3})", R"({"a":1,"c":3})", 1},
|
||||||
|
{R"([{1}, "a"])", R"([{},"a"])", 1},
|
||||||
|
|
||||||
|
// a wrong closing bracket closes the innermost container
|
||||||
|
{R"({"a":[1,2}, "b":3})", R"({"a":[1,2],"b":3})", 1},
|
||||||
|
{R"([{"a":1], 2])", R"([{"a":1},2])", 1},
|
||||||
|
{"{]", "{}", 1},
|
||||||
|
{"[}", "[]", 1},
|
||||||
|
|
||||||
|
// the end of the input closes all containers
|
||||||
|
{R"({"a":[1,2)", R"({"a":[1,2]})", 1},
|
||||||
|
{"[", "[]", 1},
|
||||||
|
{"{", "{}", 1},
|
||||||
|
{R"({"a")", R"({"a":null})", 1},
|
||||||
|
{R"({"a":)", R"({"a":null})", 1},
|
||||||
|
{"[1,", "[1]", 1},
|
||||||
|
{"[[[1", "[[[1]]]", 1},
|
||||||
|
{
|
||||||
|
R"(["abc)", R"(["abc"])", 2
|
||||||
|
},
|
||||||
|
{"[1,tr", "[1,null]", 2},
|
||||||
|
{"\"abc", "\"abc\"", 1},
|
||||||
|
{"[\"ab\ncd\"]", R"(["ab",null,"]"])", 4},
|
||||||
|
|
||||||
|
// what comes before the top-level value is skipped
|
||||||
|
{")]}'\n{\"a\":1}", R"({"a":1})", 1},
|
||||||
|
{R"(data: {"a":1})", R"({"a":1})", 1},
|
||||||
|
{"\xEF\xBB[1]", "[1]", 1},
|
||||||
|
|
||||||
|
// what comes after it is an error that ends parsing
|
||||||
|
{R"({"a":1}})", R"({"a":1})", 1},
|
||||||
|
{"[1}]", "[1]", 2},
|
||||||
|
{"[1] [2]", "[1]", 1},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& repair : repairs)
|
||||||
|
{
|
||||||
|
CAPTURE(repair.input);
|
||||||
|
const auto result = parse_recovering(std::string(repair.input));
|
||||||
|
CHECK(!result.ok);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(result.value == json::parse(repair.expected));
|
||||||
|
CHECK(result.errors.size() == repair.errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("number overflow")
|
||||||
|
{
|
||||||
|
const auto result = parse_recovering(std::string("[1e999,-1e999]"));
|
||||||
|
CHECK(!result.ok);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(result.errors.size() == 2);
|
||||||
|
CHECK(result.errors[0] == "[json.exception.out_of_range.406] number overflow parsing '1e999'");
|
||||||
|
REQUIRE(result.value.size() == 2);
|
||||||
|
CHECK(result.value[0].is_number_float());
|
||||||
|
CHECK(result.value[0].get<double>() == std::numeric_limits<double>::infinity());
|
||||||
|
CHECK(result.value[1].get<double>() == -std::numeric_limits<double>::infinity());
|
||||||
|
|
||||||
|
// the SAX parser gets the number's text
|
||||||
|
SaxEventLogger logger;
|
||||||
|
logger.recover = true;
|
||||||
|
CHECK(!json::sax_parse("1e999", &logger));
|
||||||
|
CHECK(logger.events == std::vector<std::string>({"parse_error(5)", "number_float(1e999)"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("nothing to recover")
|
||||||
|
{
|
||||||
|
for (const std::string s :
|
||||||
|
{
|
||||||
|
"", " ", "]", "tru", "NaN", ",:", "/* comment"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(s);
|
||||||
|
const auto result = parse_recovering(s, true, true);
|
||||||
|
CHECK(!result.ok);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(result.events == 0);
|
||||||
|
CHECK(result.value == nullptr);
|
||||||
|
CHECK(result.errors.size() == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("error messages")
|
||||||
|
{
|
||||||
|
// the first error is reported as without recovery
|
||||||
|
for (const std::string s :
|
||||||
|
{
|
||||||
|
"[1 2]", R"({"a":1 "b":2})", R"({"a" 1})", R"({"a":})", "[1,]", "[1.]",
|
||||||
|
R"(["a\qb"])", "[1e999]", "{1:2}", R"({"a":[1,2}})", "[1,", "[1] [2]", "{a:1}"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(s);
|
||||||
|
const auto result = parse_recovering(s);
|
||||||
|
REQUIRE(!result.errors.empty());
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_STD_STR(_ = json::parse(s), result.errors.front());
|
||||||
|
}
|
||||||
|
|
||||||
|
// the token of an error begins where the previous error was
|
||||||
|
const auto result = parse_recovering(std::string("[tru, fals, nul]"));
|
||||||
|
CHECK(result.errors == std::vector<std::string>(
|
||||||
|
{
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid literal; last read: '[tru,'",
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 11: syntax error while parsing value - invalid literal; last read: ', fals,'",
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 16: syntax error while parsing value - invalid literal; last read: ', nul]'"
|
||||||
|
}));
|
||||||
|
CHECK(result.value == json::parse("[null,null,null]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("events")
|
||||||
|
{
|
||||||
|
// see #4522
|
||||||
|
SaxEventLogger logger;
|
||||||
|
logger.recover = true;
|
||||||
|
CHECK(!json::sax_parse(R"([{1}, "a"])", &logger));
|
||||||
|
CHECK(logger.events == std::vector<std::string>(
|
||||||
|
{
|
||||||
|
"start_array()", "start_object()", "parse_error(3)", "end_object()", "string(a)", "end_array()"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("options")
|
||||||
|
{
|
||||||
|
SECTION("strict")
|
||||||
|
{
|
||||||
|
const auto result = parse_recovering(std::string("[1 2] [3]"), false);
|
||||||
|
CHECK(!result.ok);
|
||||||
|
CHECK(result.value == json::parse("[1,2]"));
|
||||||
|
CHECK(result.errors.size() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ignore_trailing_commas")
|
||||||
|
{
|
||||||
|
for (const std::string s :
|
||||||
|
{
|
||||||
|
"[1,]", R"({"a":1,})", "[[1,],]"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(s);
|
||||||
|
const auto result = parse_recovering(s, true, false, true);
|
||||||
|
CHECK(result.ok);
|
||||||
|
CHECK(result.errors.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = parse_recovering(std::string("[1,,]"), true, false, true);
|
||||||
|
CHECK(result.value == json::parse("[1,null]"));
|
||||||
|
CHECK(result.errors.size() == 1);
|
||||||
|
|
||||||
|
result = parse_recovering(std::string(R"({"a":1,,})"), true, false, true);
|
||||||
|
CHECK(result.value == json::parse(R"({"a":1})"));
|
||||||
|
CHECK(result.errors.size() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ignore_comments")
|
||||||
|
{
|
||||||
|
auto result = parse_recovering(std::string("[1 /* one */ 2]"), true, true);
|
||||||
|
CHECK(result.value == json::parse("[1,2]"));
|
||||||
|
CHECK(result.errors.size() == 1);
|
||||||
|
|
||||||
|
// a comment that is not closed runs to the end of the input, which
|
||||||
|
// is not reported again
|
||||||
|
result = parse_recovering(std::string("[1, 2 /* unterminated"), true, true);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(result.value == json::parse("[1,2]"));
|
||||||
|
CHECK(result.errors.size() == 1);
|
||||||
|
|
||||||
|
// a '/' that does not begin a comment is garbage
|
||||||
|
result = parse_recovering(std::string("[1, /x, 2]"), true, true);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(result.value == json::parse("[1,null,2]"));
|
||||||
|
CHECK(result.errors.size() == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("null bytes")
|
||||||
|
{
|
||||||
|
// a null byte ends the input, unless JSON_STRICT_NUL_HANDLING is set
|
||||||
|
const auto result = parse_recovering(std::string("[1,\0x", 5));
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(!result.ok);
|
||||||
|
#ifdef JSON_TEST_STRICT_NUL_HANDLING_ENABLED
|
||||||
|
CHECK(result.value == json::parse("[1,null]"));
|
||||||
|
#else
|
||||||
|
CHECK(result.value == json::parse("[1]"));
|
||||||
|
CHECK(result.errors.size() == 1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const auto in_string = parse_recovering(std::string("[\"a\0b\"]", 7));
|
||||||
|
CHECK(in_string.balanced);
|
||||||
|
#ifdef JSON_TEST_STRICT_NUL_HANDLING_ENABLED
|
||||||
|
CHECK(in_string.value == json::array({std::string("a\0b", 3)}));
|
||||||
|
#else
|
||||||
|
CHECK(in_string.value == json::parse(R"(["a"])"));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("the SAX parser stops recovering")
|
||||||
|
{
|
||||||
|
json j;
|
||||||
|
RecoveringDomParser sax(j, 2);
|
||||||
|
CHECK(!json::sax_parse("[1 2 3 4 5]", &sax));
|
||||||
|
CHECK(sax.errors.size() == 2);
|
||||||
|
|
||||||
|
// an error at a delimiter that an invalid token consumed is reported
|
||||||
|
// to the SAX parser, too
|
||||||
|
json j2;
|
||||||
|
RecoveringDomParser sax2(j2, 2);
|
||||||
|
CHECK(!json::sax_parse("[tru}, 1]", &sax2));
|
||||||
|
CHECK(sax2.errors.size() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("an event stops parsing during a repair")
|
||||||
|
{
|
||||||
|
// start_object() and key() are passed, then null() for the missing
|
||||||
|
// value returns false
|
||||||
|
RecoveringCountdown countdown(2);
|
||||||
|
CHECK(!json::sax_parse(R"({"a":})", &countdown));
|
||||||
|
|
||||||
|
// the end of the input: end_array() for the second array returns false
|
||||||
|
RecoveringCountdown countdown2(4);
|
||||||
|
CHECK(!json::sax_parse("[[1", &countdown2));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("input adapters")
|
||||||
|
{
|
||||||
|
// the lexer reads contiguous and streaming input differently, and it
|
||||||
|
// puts back a character that ended an invalid token
|
||||||
|
for (const std::string s :
|
||||||
|
{
|
||||||
|
"[1 2]", "[tru}, 1]", R"({"a" "b\q", "c":[1.x, 2}})", "[\"\xFF\xC3(\", -, 1e+]", "{a:1,\"b\":2", ")]}' [1]"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(s);
|
||||||
|
const auto reference = parse_recovering(s);
|
||||||
|
CHECK(reference.balanced);
|
||||||
|
|
||||||
|
const auto from_c_string = parse_recovering(s.c_str());
|
||||||
|
CHECK(from_c_string.value == reference.value);
|
||||||
|
CHECK(from_c_string.errors == reference.errors);
|
||||||
|
|
||||||
|
const std::list<char> l(s.begin(), s.end());
|
||||||
|
json j;
|
||||||
|
RecoveringDomParser sax(j);
|
||||||
|
CHECK(!json::sax_parse(l.begin(), l.end(), &sax));
|
||||||
|
CHECK(j == reference.value);
|
||||||
|
CHECK(sax.errors == reference.errors);
|
||||||
|
|
||||||
|
std::istringstream ss(s);
|
||||||
|
const auto from_stream = parse_recovering(ss);
|
||||||
|
CHECK(from_stream.value == reference.value);
|
||||||
|
CHECK(from_stream.errors == reference.errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("long runs of errors")
|
||||||
|
{
|
||||||
|
// no error may copy all the input read before it
|
||||||
|
const auto closing = parse_recovering("[" + std::string(100000, '}'));
|
||||||
|
CHECK(closing.balanced);
|
||||||
|
CHECK(closing.value == json::array());
|
||||||
|
|
||||||
|
const auto garbage = parse_recovering("[" + std::string(100000, 'x') + "]");
|
||||||
|
CHECK(garbage.balanced);
|
||||||
|
CHECK(garbage.errors.size() == 1);
|
||||||
|
|
||||||
|
const auto commas = parse_recovering("{" + std::string(100000, ',') + "}");
|
||||||
|
CHECK(commas.balanced);
|
||||||
|
CHECK(commas.value == json::object());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("mutations of valid input")
|
||||||
|
{
|
||||||
|
// whatever the input, the events are balanced, every error is reported
|
||||||
|
// at most once, and valid input is parsed as usual
|
||||||
|
const std::vector<std::string> documents =
|
||||||
|
{
|
||||||
|
R"({"name": "value", "list": [1, -2.5, true, null, {"x": [[]]}], "e": "\u00e9"})",
|
||||||
|
R"([{"a": [1, 2, {"b": "c"}]}, [], {}, "\ud83d\ude00", 1e10])",
|
||||||
|
"{\"\xC3\xA9\": \"\xF0\x9F\x98\x80\"}",
|
||||||
|
R"( {"k" : [ "v" , 0 ] } )",
|
||||||
|
};
|
||||||
|
// each character that can be inserted, including a null byte
|
||||||
|
const std::string insertions("[]{},:\"x\\\0\xFF", 11);
|
||||||
|
|
||||||
|
std::vector<std::string> inputs;
|
||||||
|
for (const auto& doc : documents)
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i <= doc.size(); ++i)
|
||||||
|
{
|
||||||
|
inputs.push_back(doc.substr(0, i));
|
||||||
|
if (i < doc.size())
|
||||||
|
{
|
||||||
|
inputs.push_back(doc.substr(0, i) + doc.substr(i + 1));
|
||||||
|
}
|
||||||
|
for (const char c : insertions)
|
||||||
|
{
|
||||||
|
inputs.push_back(doc.substr(0, i) + c + doc.substr(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& s : inputs)
|
||||||
|
{
|
||||||
|
CAPTURE(s);
|
||||||
|
const auto result = parse_recovering(s);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(result.errors.size() <= s.size() + 1);
|
||||||
|
CHECK(result.events <= (4 * s.size()) + 4);
|
||||||
|
if (json::accept(s))
|
||||||
|
{
|
||||||
|
CHECK(result.ok);
|
||||||
|
CHECK(result.errors.empty());
|
||||||
|
CHECK(result.value == json::parse(s));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK(!result.ok);
|
||||||
|
CHECK(!result.errors.empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -141,6 +141,58 @@ TEST_CASE("Better diagnostics with positions")
|
|||||||
check_objects(300);
|
check_objects(300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("converting keeps the positions of nested values (#5650)")
|
||||||
|
{
|
||||||
|
// Values nested deeper than the converting constructor's descent bound
|
||||||
|
// are converted without the call stack, on a path that has to carry the
|
||||||
|
// positions of every value over itself. Objects and arrays take turns,
|
||||||
|
// and the innermost value is null, which used to lose its positions.
|
||||||
|
const auto check_conversion = [](std::size_t depth)
|
||||||
|
{
|
||||||
|
CAPTURE(depth)
|
||||||
|
|
||||||
|
std::string text;
|
||||||
|
std::string closing;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
text += (i % 2 == 0) ? "[12, " : R"({"b":1, "a":)";
|
||||||
|
closing += (i % 2 == 0) ? ']' : '}';
|
||||||
|
}
|
||||||
|
text += "null";
|
||||||
|
text.append(closing.rbegin(), closing.rend());
|
||||||
|
|
||||||
|
const json original = json::parse(text);
|
||||||
|
const nlohmann::ordered_json converted = original;
|
||||||
|
|
||||||
|
const json* o = &original;
|
||||||
|
const nlohmann::ordered_json* c = &converted;
|
||||||
|
for (std::size_t level = 0; level <= depth; ++level)
|
||||||
|
{
|
||||||
|
CAPTURE(level)
|
||||||
|
REQUIRE(c->start_pos() == o->start_pos());
|
||||||
|
REQUIRE(c->end_pos() == o->end_pos());
|
||||||
|
|
||||||
|
if (level < depth)
|
||||||
|
{
|
||||||
|
// the number beside the value nested next
|
||||||
|
const json& o_number = o->is_object() ? o->at("b") : o->at(0);
|
||||||
|
const nlohmann::ordered_json& c_number = c->is_object() ? c->at("b") : c->at(0);
|
||||||
|
REQUIRE(c_number.start_pos() == o_number.start_pos());
|
||||||
|
REQUIRE(c_number.end_pos() == o_number.end_pos());
|
||||||
|
|
||||||
|
o = o->is_object() ? &o->at("a") : &o->at(1);
|
||||||
|
c = c->is_object() ? &c->at("a") : &c->at(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
check_conversion(1);
|
||||||
|
check_conversion(127);
|
||||||
|
check_conversion(128);
|
||||||
|
check_conversion(129);
|
||||||
|
check_conversion(300);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("JSON patch add to primitive parent (#4292)")
|
SECTION("JSON patch add to primitive parent (#4292)")
|
||||||
{
|
{
|
||||||
// the JSON Patch "add" target /foo/bar/baz has a string parent
|
// the JSON Patch "add" target /foo/bar/baz has a string parent
|
||||||
|
|||||||
@@ -341,6 +341,36 @@ TEST_CASE("Regression tests for extended diagnostics")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("Regression test for issue #5650 - converting keeps the parents of nested values")
|
||||||
|
{
|
||||||
|
// A value nested deeper than the converting constructor's descent bound
|
||||||
|
// is converted without the call stack. Every container that path creates
|
||||||
|
// has to have the parents of its children set, or the JSON Pointer in the
|
||||||
|
// diagnostic is cut short. Objects and arrays take turns.
|
||||||
|
const std::size_t pairs = 150;
|
||||||
|
|
||||||
|
json j = "not a number";
|
||||||
|
std::string pointer;
|
||||||
|
for (std::size_t i = 0; i < pairs; ++i)
|
||||||
|
{
|
||||||
|
j = json{{"a", json::array({j})}};
|
||||||
|
pointer += "/a/0";
|
||||||
|
}
|
||||||
|
|
||||||
|
const nlohmann::ordered_json converted = j;
|
||||||
|
|
||||||
|
const nlohmann::ordered_json* inner = &converted;
|
||||||
|
for (std::size_t i = 0; i < pairs; ++i)
|
||||||
|
{
|
||||||
|
inner = &inner->at("a").at(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string const expected = "[json.exception.type_error.302] (" + pointer + ") type must be number, but is string";
|
||||||
|
int i = 0;
|
||||||
|
CHECK_THROWS_WITH_AS(i = inner->get<int>(), expected.c_str(), nlohmann::ordered_json::type_error);
|
||||||
|
CHECK(i == 0);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("Regression test for issue #5668 - wrong path for std::map/unordered_map with non-string keys")
|
SECTION("Regression test for issue #5668 - wrong path for std::map/unordered_map with non-string keys")
|
||||||
{
|
{
|
||||||
// a map with non-string keys is read from an array of [key, value] arrays;
|
// a map with non-string keys is read from an array of [key, value] arrays;
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
// This file tests the opt-in JSON_DISABLE_TUPLE_REFERENCE_CONVERSION, so it
|
||||||
|
// defines the macro itself rather than relying on a -D flag, and runs in every
|
||||||
|
// build.
|
||||||
|
#ifdef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||||
|
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 1
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
using nlohmann::ordered_json;
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
// clang before 4 and GCC before 5 cannot create a std::tuple of basic_json
|
||||||
|
// references at all, with or without JSON_DISABLE_TUPLE_REFERENCE_CONVERSION:
|
||||||
|
// the tuple constructors make them instantiate basic_json's conversion operator
|
||||||
|
// for libstdc++'s internal tuple bases, which fails hard
|
||||||
|
#if (defined(__clang__) && __clang_major__ < 4) || (!defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5)
|
||||||
|
#define SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TEST_CASE("JSON_DISABLE_TUPLE_REFERENCE_CONVERSION")
|
||||||
|
{
|
||||||
|
SECTION("json is not constructible from a one-element tuple of a json reference")
|
||||||
|
{
|
||||||
|
CHECK_FALSE(std::is_constructible<json, std::tuple<json&>>::value);
|
||||||
|
CHECK_FALSE(std::is_constructible<json, std::tuple<const json&>>::value);
|
||||||
|
CHECK_FALSE(std::is_constructible < json, std::tuple < json && >>::value);
|
||||||
|
CHECK_FALSE(std::is_constructible<json, const std::tuple<json&>&>::value);
|
||||||
|
CHECK_FALSE(std::is_constructible<ordered_json, std::tuple<ordered_json&>>::value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
||||||
|
SECTION("issue #2226 - tuple<const json&> from tuple<json&> keeps the reference")
|
||||||
|
{
|
||||||
|
json j = true;
|
||||||
|
const std::tuple<const json&> tup(std::forward_as_tuple(j));
|
||||||
|
CHECK(&std::get<0>(tup) == &j);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("tuple<json> from tuple<json&> copies the element")
|
||||||
|
{
|
||||||
|
const json j = {{"key", "value"}};
|
||||||
|
const std::tuple<json> t1(std::forward_as_tuple(j));
|
||||||
|
CHECK(std::get<0>(t1) == j);
|
||||||
|
|
||||||
|
json j2 = "text";
|
||||||
|
const std::tuple<json> t2(std::forward_as_tuple(std::move(j2)));
|
||||||
|
CHECK(std::get<0>(t2) == "text");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SECTION("other tuple conversions are not affected")
|
||||||
|
{
|
||||||
|
const json j = true;
|
||||||
|
|
||||||
|
// one-element tuple holding a json value
|
||||||
|
CHECK(json(std::make_tuple(j)) == json::array({true}));
|
||||||
|
|
||||||
|
// tuples with more than one element, even when holding references
|
||||||
|
int i = 1;
|
||||||
|
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
||||||
|
CHECK(json(std::forward_as_tuple(i, j)) == json::array({1, true}));
|
||||||
|
CHECK(json(std::forward_as_tuple(j, j)) == json::array({true, true}));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// one-element tuples holding references to other types
|
||||||
|
std::string s = "text";
|
||||||
|
CHECK(json(std::forward_as_tuple(s)) == json::array({"text"}));
|
||||||
|
CHECK(json(std::forward_as_tuple(i)) == json::array({1}));
|
||||||
|
|
||||||
|
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
||||||
|
// a reference to a different basic_json specialization
|
||||||
|
ordered_json oj = true;
|
||||||
|
CHECK(json(std::forward_as_tuple(oj)) == json::array({true}));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,399 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | 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
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
#include <nlohmann/detail/view/builder.hpp>
|
|
||||||
#include <nlohmann/detail/view/string_ref.hpp>
|
|
||||||
using nlohmann::json;
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <fstream>
|
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
|
||||||
#include <random>
|
|
||||||
#include <sstream>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <test_data.hpp>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
using nlohmann::detail::view::document_data;
|
|
||||||
using nlohmann::detail::view::node;
|
|
||||||
|
|
||||||
// the node index of a text; a vector input has no terminating NUL, so that
|
|
||||||
// AddressSanitizer catches any read past the last byte
|
|
||||||
struct built
|
|
||||||
{
|
|
||||||
std::unique_ptr<document_data, document_data::deleter> data{}; // NOLINT(readability-redundant-member-init)
|
|
||||||
std::vector<char> copy{}; // NOLINT(readability-redundant-member-init)
|
|
||||||
bool ok = false;
|
|
||||||
nlohmann::detail::view::parse_failure failure{};
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename FloatType = double>
|
|
||||||
built build(const std::string& text, bool comments, bool trailing_commas, bool sentinel)
|
|
||||||
{
|
|
||||||
built r;
|
|
||||||
r.data.reset(document_data::create(nlohmann::detail::view::estimate_nodes(text.data(), text.size())));
|
|
||||||
const char* src = text.c_str();
|
|
||||||
if (!sentinel)
|
|
||||||
{
|
|
||||||
r.copy.assign(text.begin(), text.end());
|
|
||||||
src = r.copy.data();
|
|
||||||
}
|
|
||||||
r.ok = nlohmann::detail::view::build < FloatType, !nlohmann::detail::abi_config::strict_nul_handling > (*r.data, src, text.size(), comments, trailing_commas, sentinel, r.failure);
|
|
||||||
r.data->src = src;
|
|
||||||
r.data->base[0] = src;
|
|
||||||
r.data->base[1] = r.data->arena.data();
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the value of a subtree, as json::parse would build it
|
|
||||||
json value_of(const document_data& d, const node*& n)
|
|
||||||
{
|
|
||||||
const node& x = *n;
|
|
||||||
++n;
|
|
||||||
switch (static_cast<json::value_t>(x.kind))
|
|
||||||
{
|
|
||||||
case json::value_t::object:
|
|
||||||
{
|
|
||||||
json o = json::object();
|
|
||||||
const node* const end = &x + x.next;
|
|
||||||
while (n != end)
|
|
||||||
{
|
|
||||||
const std::string key(d.str(*n), n->len);
|
|
||||||
++n;
|
|
||||||
o[key] = value_of(d, n);
|
|
||||||
}
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
case json::value_t::array:
|
|
||||||
{
|
|
||||||
json a = json::array();
|
|
||||||
const node* const end = &x + x.next;
|
|
||||||
while (n != end)
|
|
||||||
{
|
|
||||||
a.push_back(value_of(d, n));
|
|
||||||
}
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
case json::value_t::string:
|
|
||||||
return std::string(d.str(x), x.len);
|
|
||||||
case json::value_t::boolean:
|
|
||||||
return (x.flags & nlohmann::detail::view::node_flags::is_true) != 0;
|
|
||||||
case json::value_t::number_integer:
|
|
||||||
return static_cast<std::int64_t>(nlohmann::detail::view::integer_bits(x));
|
|
||||||
case json::value_t::number_unsigned:
|
|
||||||
return nlohmann::detail::view::integer_bits(x);
|
|
||||||
case json::value_t::number_float:
|
|
||||||
return json::parse(std::string(d.src + x.off, x.len)).get<double>();
|
|
||||||
case json::value_t::null:
|
|
||||||
case json::value_t::binary:
|
|
||||||
case json::value_t::discarded:
|
|
||||||
default:
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
json value_of(const built& b)
|
|
||||||
{
|
|
||||||
const node* n = b.data->tape;
|
|
||||||
json v = value_of(*b.data, n);
|
|
||||||
CHECK(n == b.data->tape + b.data->tape_size);
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
// accept/reject and the value must match json::parse, for all options and
|
|
||||||
// with and without a NUL after the text
|
|
||||||
void check_same(const std::string& text)
|
|
||||||
{
|
|
||||||
CAPTURE(text);
|
|
||||||
for (int options = 0; options < 4; ++options)
|
|
||||||
{
|
|
||||||
const bool comments = (options & 1) != 0;
|
|
||||||
const bool trailing_commas = (options & 2) != 0;
|
|
||||||
const bool accepted = json::accept(text, comments, trailing_commas);
|
|
||||||
for (const bool sentinel :
|
|
||||||
{
|
|
||||||
true, false
|
|
||||||
})
|
|
||||||
{
|
|
||||||
const built b = build(text, comments, trailing_commas, sentinel);
|
|
||||||
CHECK(b.ok == accepted);
|
|
||||||
if (b.ok && accepted)
|
|
||||||
{
|
|
||||||
CHECK(value_of(b) == json::parse(text, nullptr, true, comments, trailing_commas));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// a small deterministic generator of documents
|
|
||||||
struct generator
|
|
||||||
{
|
|
||||||
std::mt19937 rng{5295}; // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
|
||||||
|
|
||||||
int r(int n)
|
|
||||||
{
|
|
||||||
return static_cast<int>(rng() % static_cast<unsigned>(n));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ws(std::string& o)
|
|
||||||
{
|
|
||||||
for (int n = r(4) == 0 ? r(12) : r(2); n > 0; --n)
|
|
||||||
{
|
|
||||||
o += " \n\t\r "[r(6)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void str(std::string& o)
|
|
||||||
{
|
|
||||||
static const char* const pieces[] = {"a", "Z", " ", "~", "\\n", "\\\"", "\\\\", "\\/", "\\u00e9", "\\ud83d\\ude00", "\xc3\xa9", "\xe3\x81\x82", "\xf0\x9f\x98\x80", "\x7f", "\\u001f", "long enough text to leave the first 16 bytes"}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
|
||||||
o += '"';
|
|
||||||
for (int n = r(3) == 0 ? r(20) : r(6); n > 0; --n)
|
|
||||||
{
|
|
||||||
o += pieces[r(16)];
|
|
||||||
}
|
|
||||||
o += '"';
|
|
||||||
}
|
|
||||||
|
|
||||||
void num(std::string& o)
|
|
||||||
{
|
|
||||||
static const char* const numbers[] = {"0", "-0", "1", "-1", "12", "123456789", "1234567890123456789", "9223372036854775807", "-9223372036854775808", // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
|
||||||
"9223372036854775808", "18446744073709551615", "18446744073709551616", "-9223372036854775809",
|
|
||||||
"1.5", "-2.25e-3", "1e10", "1E+2", "0.000001", "3.141592653589793238462643", "1e308", "-1e-400", "123.456e7"
|
|
||||||
};
|
|
||||||
o += numbers[r(22)];
|
|
||||||
}
|
|
||||||
|
|
||||||
void value(std::string& o, int depth)
|
|
||||||
{
|
|
||||||
ws(o);
|
|
||||||
const int k = depth > 5 ? 2 + r(6) : r(8);
|
|
||||||
if (k == 0 || k == 1)
|
|
||||||
{
|
|
||||||
const bool object = k == 0;
|
|
||||||
o += object ? '{' : '[';
|
|
||||||
for (int i = r(5); i > 0; --i)
|
|
||||||
{
|
|
||||||
ws(o);
|
|
||||||
if (object)
|
|
||||||
{
|
|
||||||
str(o);
|
|
||||||
ws(o);
|
|
||||||
o += ':';
|
|
||||||
}
|
|
||||||
value(o, depth + 1);
|
|
||||||
o += i > 1 ? "," : "";
|
|
||||||
}
|
|
||||||
ws(o);
|
|
||||||
o += object ? '}' : ']';
|
|
||||||
}
|
|
||||||
else if (k < 4)
|
|
||||||
{
|
|
||||||
str(o);
|
|
||||||
}
|
|
||||||
else if (k < 6)
|
|
||||||
{
|
|
||||||
num(o);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
static const char* const literals[] = {"true", "false", "null"}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
|
||||||
o += literals[r(3)];
|
|
||||||
}
|
|
||||||
ws(o);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("json_view string_ref")
|
|
||||||
{
|
|
||||||
// std::string_view in C++17, a stand-in with the same members before
|
|
||||||
using nlohmann::detail::view::string_ref;
|
|
||||||
const std::string text = "abc";
|
|
||||||
const string_ref r(text);
|
|
||||||
CHECK(r.length() == 3);
|
|
||||||
CHECK(std::string(r.begin(), r.end()) == "abc");
|
|
||||||
CHECK(r[1] == 'b');
|
|
||||||
CHECK(r != string_ref("abd"));
|
|
||||||
CHECK_FALSE(r != string_ref("abcd", 3));
|
|
||||||
std::ostringstream o;
|
|
||||||
o << r;
|
|
||||||
CHECK(o.str() == "abc");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("json_view builder")
|
|
||||||
{
|
|
||||||
SECTION("scalars and containers")
|
|
||||||
{
|
|
||||||
for (const char* text :
|
|
||||||
{
|
|
||||||
"null", "true", "false", "0", "-0", "42", "-42", "1.5", "\"\"", "\"abc\"", "[]", "{}", "[1,2,3]", "{\"a\":1,\"b\":[true,null]}", // NOLINT(modernize-raw-string-literal)
|
|
||||||
" [ 1 , 2 ] ", "{\"a\" : {\"b\" : {}}}", "[[[]]]", "\"\\u00e4\\n\\ud83d\\ude00\"", "{\"a\":1,\"a\":2}", "18446744073709551616", // NOLINT(modernize-raw-string-literal)
|
|
||||||
"-9223372036854775809", "123456789012345678901234567890", "1e400", "-1e400", "1.7976931348623157e308"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
check_same(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
// the midpoint between the largest double and 2^1024 rounds to
|
|
||||||
// infinity (an overflow), one less to the largest double: with more
|
|
||||||
// than 19 digits, Eisel-Lemire cannot decide these, and the overflow
|
|
||||||
// check needs the exact comparison with the midpoint
|
|
||||||
const std::string midpoint = "179769313486231580793728971405303415079934132710037826936173778980444968292764750946649017977587207096330286416692887910946555547851940402630657488671505820681908902000708383676273854845817711531764475730270069855571366959622842914819860834936475292719074168444365510704342711559699508093042880177904174497792";
|
|
||||||
const std::string below = "179769313486231580793728971405303415079934132710037826936173778980444968292764750946649017977587207096330286416692887910946555547851940402630657488671505820681908902000708383676273854845817711531764475730270069855571366959622842914819860834936475292719074168444365510704342711559699508093042880177904174497791";
|
|
||||||
check_same(midpoint);
|
|
||||||
check_same("-" + midpoint);
|
|
||||||
check_same(below);
|
|
||||||
check_same("[" + below + "," + midpoint + "]");
|
|
||||||
|
|
||||||
// the check uses the floating-point type of the document: with float,
|
|
||||||
// the view rejects what parse() rejects (out_of_range.406), and a
|
|
||||||
// double document is not affected
|
|
||||||
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
|
||||||
CHECK_FALSE(float_json::accept("1e39"));
|
|
||||||
CHECK(float_json::accept("3.4028235e38"));
|
|
||||||
CHECK_FALSE(float_json::accept("3.4028236e38"));
|
|
||||||
for (const char* text :
|
|
||||||
{
|
|
||||||
"1e39", "-1e39", "3.4028235e38", "-3.4028235e38", "3.4028236e38", "-3.4028236e38", "3.4028234663852886e38", "1e38",
|
|
||||||
"340282356779733661637539395458142568448", "340282356779733661637539395458142568447.99", "0.00034028236e42",
|
|
||||||
"[1.5e38, 3.5e38]", "{\"a\": 1e-50, \"b\": 1e39}"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(text);
|
|
||||||
const bool float_accepted = float_json::accept(text);
|
|
||||||
for (const bool sentinel :
|
|
||||||
{
|
|
||||||
true, false
|
|
||||||
})
|
|
||||||
{
|
|
||||||
const built f = build<float>(text, false, false, sentinel);
|
|
||||||
CHECK(f.ok == float_accepted);
|
|
||||||
if (!f.ok)
|
|
||||||
{
|
|
||||||
CHECK(f.failure.code == nlohmann::detail::view::error_code::number_overflow);
|
|
||||||
}
|
|
||||||
CHECK(build<double>(text, false, false, sentinel).ok == json::accept(text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("malformed input")
|
|
||||||
{
|
|
||||||
for (const char* text :
|
|
||||||
{
|
|
||||||
"", " ", "[", "]", "{", "}", "[1,]", "{\"a\":1,}", "[1 2]", "{\"a\" 1}", "{1:2}", "tru", "nul", "fals", "truex", "-", "01", "1.", ".5", "1e", "1e+",
|
|
||||||
"\"", "\"abc", "\"\\x\"", "\"\\u12\"", "\"\\u12G4\"", "\"\\ud800\"", "\"\\udc00\"", "\"\\ud800\\u0041\"", "\"\x01\"", "\"\xff\"", "\"\xc3\"", // NOLINT(modernize-raw-string-literal)
|
|
||||||
"\"\xe0\x80\x80\"", "\"\xed\xa0\x80\"", "[1]x", "[1] [2]", "/", "/*", "/* */ 1", "// c\n1", "1 // c", "[1,/*c*/2]", "[1,2,]"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
check_same(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("NUL, BOM, and whitespace")
|
|
||||||
{
|
|
||||||
// a NUL inside a string is a control character, as for json::parse
|
|
||||||
// (where a NUL ends the input, it does so only between values)
|
|
||||||
for (const bool sentinel :
|
|
||||||
{
|
|
||||||
true, false
|
|
||||||
})
|
|
||||||
{
|
|
||||||
const built b = build(std::string("[\"ab\0cd\"]", 9), false, false, sentinel);
|
|
||||||
CHECK(!b.ok);
|
|
||||||
CHECK(b.failure.code == nlohmann::detail::view::error_code::string_control_character);
|
|
||||||
CHECK(b.failure.offset == 4);
|
|
||||||
}
|
|
||||||
check_same(std::string("[1]\0garbage", 11));
|
|
||||||
check_same(std::string("[1\0]", 4));
|
|
||||||
check_same(std::string("[1, // c\0\n2]", 12));
|
|
||||||
check_same(std::string("[1, /* c\0 */ 2]", 15));
|
|
||||||
check_same("\xEF\xBB\xBF[1]");
|
|
||||||
check_same("\xEF\xBB[1]");
|
|
||||||
check_same(" \t\r\n 7 \n");
|
|
||||||
for (const char* text :
|
|
||||||
{"[1]\r", "[1]\n", "[1]\r\n", "[1,\r2]", "[1,\r\n2]", "7\r", "\"x\"\r", "{\"a\":\r\n1}\r", "[\n 1,\n 2\n]", "{\n \"a\": [\n 1\n ]\n}"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
check_same(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("deep nesting")
|
|
||||||
{
|
|
||||||
// the open containers beyond 64 levels live on the heap
|
|
||||||
for (const std::size_t depth :
|
|
||||||
{
|
|
||||||
63u, 64u, 65u, 1000u, 100000u
|
|
||||||
})
|
|
||||||
{
|
|
||||||
const std::string arrays = std::string(depth, '[') + std::string(depth, ']');
|
|
||||||
const built b = build(arrays, false, false, false);
|
|
||||||
REQUIRE(b.ok);
|
|
||||||
CHECK(b.data->tape_size == depth);
|
|
||||||
CHECK(b.data->tape[0].next == depth);
|
|
||||||
std::string objects;
|
|
||||||
for (std::size_t i = 0; i < depth; ++i)
|
|
||||||
{
|
|
||||||
objects += "{\"a\":";
|
|
||||||
}
|
|
||||||
objects += '1' + std::string(depth, '}');
|
|
||||||
const built o = build(objects, false, false, false);
|
|
||||||
REQUIRE(o.ok);
|
|
||||||
CHECK(o.data->tape_size == (2 * depth) + 1);
|
|
||||||
CHECK(!build(std::string(depth, '[') + std::string(depth - 1, ']'), false, false, false).ok);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("generated documents and damaged copies")
|
|
||||||
{
|
|
||||||
generator g;
|
|
||||||
for (int i = 0; i < 3000; ++i)
|
|
||||||
{
|
|
||||||
std::string text;
|
|
||||||
g.value(text, 0);
|
|
||||||
check_same(text);
|
|
||||||
// damage: flip one byte, or cut the text
|
|
||||||
std::string damaged = text;
|
|
||||||
const auto at = static_cast<std::size_t>(g.r(static_cast<int>(damaged.size())));
|
|
||||||
static const char replacements[] = {'x', '"', '\\', ',', ':', ']', '}', '[', '{', '1', '-', '.', 'e', '\0', '\n', '/'}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
|
||||||
damaged[at] = replacements[g.r(16)];
|
|
||||||
check_same(damaged);
|
|
||||||
check_same(text.substr(0, at));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("test files")
|
|
||||||
{
|
|
||||||
for (const char* name :
|
|
||||||
{
|
|
||||||
"/json.org/1.json", "/json.org/2.json", "/json.org/3.json", "/json.org/4.json", "/json.org/5.json",
|
|
||||||
"/json_testsuite/sample.json", "/nativejson-benchmark/canada.json", "/nativejson-benchmark/citm_catalog.json",
|
|
||||||
"/nativejson-benchmark/twitter.json", "/json_tests/pass1.json", "/json_tests/pass2.json", "/json_tests/pass3.json"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(name);
|
|
||||||
std::ifstream f(std::string(TEST_DATA_DIRECTORY) + name, std::ios::binary);
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << f.rdbuf();
|
|
||||||
const std::string text = ss.str();
|
|
||||||
REQUIRE(!text.empty());
|
|
||||||
const built b = build(text, false, false, true);
|
|
||||||
REQUIRE(b.ok);
|
|
||||||
CHECK(value_of(b) == json::parse(text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,6 +13,7 @@ using nlohmann::json;
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
TEST_CASE("tests on very large JSONs")
|
TEST_CASE("tests on very large JSONs")
|
||||||
{
|
{
|
||||||
@@ -53,6 +54,24 @@ const json* innermost_value(const json& j, std::size_t& depth)
|
|||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The text of a value nested depth levels deep around the number 0. Level i is
|
||||||
|
// an array if pattern[i % pattern.size()] is '[', and otherwise an object with
|
||||||
|
// the single member "a", which every object type enumerates in the same order.
|
||||||
|
std::string nested_text(std::size_t depth, const std::string& pattern)
|
||||||
|
{
|
||||||
|
std::string text;
|
||||||
|
std::string closing;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
const bool array = pattern[i % pattern.size()] == '[';
|
||||||
|
text += array ? "[" : "{\"a\":";
|
||||||
|
closing += array ? ']' : '}';
|
||||||
|
}
|
||||||
|
text += '0';
|
||||||
|
text.append(closing.rbegin(), closing.rend());
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("tests on deeply nested JSONs")
|
TEST_CASE("tests on deeply nested JSONs")
|
||||||
@@ -224,5 +243,114 @@ TEST_CASE("tests on deeply nested JSONs")
|
|||||||
CHECK(*innermost_value(j, unused) == 0);
|
CHECK(*innermost_value(j, unused) == 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("issue #5650 - stack overflow converting between specializations")
|
||||||
|
{
|
||||||
|
const std::vector<std::string> patterns = {"[", "{", "[{"};
|
||||||
|
|
||||||
|
SECTION("json to ordered_json")
|
||||||
|
{
|
||||||
|
for (const auto& pattern : patterns)
|
||||||
|
{
|
||||||
|
CAPTURE(pattern);
|
||||||
|
const std::string text = nested_text(depth, pattern);
|
||||||
|
const json j = json::parse(text);
|
||||||
|
|
||||||
|
const nlohmann::ordered_json converted = j;
|
||||||
|
CHECK(converted.dump() == text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ordered_json to json")
|
||||||
|
{
|
||||||
|
for (const auto& pattern : patterns)
|
||||||
|
{
|
||||||
|
CAPTURE(pattern);
|
||||||
|
const std::string text = nested_text(depth, pattern);
|
||||||
|
const nlohmann::ordered_json o = nlohmann::ordered_json::parse(text);
|
||||||
|
|
||||||
|
const json converted = o;
|
||||||
|
CHECK(converted.dump() == text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("get<ordered_json>()")
|
||||||
|
{
|
||||||
|
for (const auto& pattern : patterns)
|
||||||
|
{
|
||||||
|
CAPTURE(pattern);
|
||||||
|
const std::string text = nested_text(depth, pattern);
|
||||||
|
const json j = json::parse(text);
|
||||||
|
|
||||||
|
CHECK(j.get<nlohmann::ordered_json>().dump() == text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("depths around the bound of the recursive descent")
|
||||||
|
{
|
||||||
|
for (std::size_t d = 1; d <= 300; ++d)
|
||||||
|
{
|
||||||
|
CAPTURE(d);
|
||||||
|
for (const auto& pattern : patterns)
|
||||||
|
{
|
||||||
|
CAPTURE(pattern);
|
||||||
|
const std::string text = nested_text(d, pattern);
|
||||||
|
const json j = json::parse(text);
|
||||||
|
|
||||||
|
const nlohmann::ordered_json converted = j;
|
||||||
|
CHECK(converted.dump() == text);
|
||||||
|
const json back = converted;
|
||||||
|
CHECK(back.dump() == text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("values below the bound are converted as values above it")
|
||||||
|
{
|
||||||
|
// Bury a value below the bound, where it is converted without the
|
||||||
|
// call stack, and compare it with the same value converted on its
|
||||||
|
// own by the containers' range constructors. Its objects have
|
||||||
|
// members that the two object types enumerate in different orders.
|
||||||
|
const auto bury = [](nlohmann::ordered_json value)
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < 200; ++i)
|
||||||
|
{
|
||||||
|
value = nlohmann::ordered_json::array({std::move(value)});
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
const auto dig = [](const json & value)
|
||||||
|
{
|
||||||
|
const json* current = &value;
|
||||||
|
for (std::size_t i = 0; i < 200; ++i)
|
||||||
|
{
|
||||||
|
current = ¤t->at(0);
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
};
|
||||||
|
|
||||||
|
nlohmann::ordered_json value = nlohmann::ordered_json::object();
|
||||||
|
value["z"] = {1, -2, 3U, 4.5, true, nullptr, "six", nlohmann::ordered_json::binary({7, 8}, 9),
|
||||||
|
nlohmann::ordered_json::binary({10}), nlohmann::ordered_json::array(), nlohmann::ordered_json::object()
|
||||||
|
};
|
||||||
|
value["y"] = {{"x", {{"w", 1}, {"v", 2}}}, {"u", {3, {{"t", 4}, {"s", 5}}}}};
|
||||||
|
value["r"] = nlohmann::ordered_json::array({nlohmann::ordered_json(nlohmann::ordered_json::value_t::discarded)});
|
||||||
|
|
||||||
|
const json converted_above = value;
|
||||||
|
const json buried = bury(value);
|
||||||
|
const json& converted_below = *dig(buried);
|
||||||
|
|
||||||
|
CHECK(converted_below.dump() == converted_above.dump());
|
||||||
|
CHECK(converted_below.at("z").at(7).get_binary().subtype() == 9);
|
||||||
|
CHECK_FALSE(converted_below.at("z").at(8).get_binary().has_subtype());
|
||||||
|
CHECK(converted_below.at("r").at(0).is_discarded());
|
||||||
|
|
||||||
|
// a discarded value is never equal to anything, so compare the rest
|
||||||
|
value.erase("r");
|
||||||
|
const json without_discarded_above = value;
|
||||||
|
const json without_discarded_buried = bury(value);
|
||||||
|
CHECK(*dig(without_discarded_buried) == without_discarded_above);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -257,11 +257,10 @@ struct LocaleSwitchingSax final: public nlohmann::json_sax<json>
|
|||||||
|
|
||||||
TEST_CASE("locale changes between lexer construction and number conversion (#5198)")
|
TEST_CASE("locale changes between lexer construction and number conversion (#5198)")
|
||||||
{
|
{
|
||||||
// float and double are converted without the locale. A long double that
|
// The numbers are chosen so that the conversion also takes the strtod
|
||||||
// is not binary64 can take the strtold fallback, which honors the locale
|
// fallback, which honors the locale that is current at conversion time:
|
||||||
// that is current at conversion time. The numbers are chosen so that it
|
// too many significant digits for Clinger's fast path, an underflow that
|
||||||
// does: too many significant digits for Clinger's fast path, an underflow
|
// std::from_chars rejects, and a plain value.
|
||||||
// that std::from_chars rejects, and a plain value.
|
|
||||||
const std::vector<std::string> numbers = {"3.14159265358979323846", "1.5e-400", "12.34", "-0.000123456789012345678"};
|
const std::vector<std::string> numbers = {"3.14159265358979323846", "1.5e-400", "12.34", "-0.000123456789012345678"};
|
||||||
std::string text = "[";
|
std::string text = "[";
|
||||||
for (const auto& n : numbers)
|
for (const auto& n : numbers)
|
||||||
@@ -325,8 +324,7 @@ TEST_CASE("locale changes between lexer construction and number conversion (#519
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// a long double goes through std::strtold unless it is binary64 or
|
// a long double goes through std::strtold unless std::from_chars supports it
|
||||||
// std::from_chars supports it
|
|
||||||
{
|
{
|
||||||
bool switched = false;
|
bool switched = false;
|
||||||
const auto cb = [&](int /*depth*/, long_double_json::parse_event_t event, long_double_json& /*parsed*/) noexcept
|
const auto cb = [&](int /*depth*/, long_double_json::parse_event_t event, long_double_json& /*parsed*/) noexcept
|
||||||
@@ -352,15 +350,8 @@ TEST_CASE("locale with a multi-byte decimal point")
|
|||||||
{
|
{
|
||||||
// Some locales use a decimal point that is not a single character, e.g.
|
// Some locales use a decimal point that is not a single character, e.g.
|
||||||
// U+066B ARABIC DECIMAL SEPARATOR (two bytes in UTF-8). It cannot be
|
// U+066B ARABIC DECIMAL SEPARATOR (two bytes in UTF-8). It cannot be
|
||||||
// substituted in place for '.', so the strtold fallback (only for long
|
// substituted in place for '.', so the strtod fallback stops early. The
|
||||||
// double formats other than binary64) converts a copy of the token with
|
// conversion must still terminate rather than retry forever.
|
||||||
// the whole decimal point instead (#5660). The values must be those of the
|
|
||||||
// "C" locale.
|
|
||||||
using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>;
|
|
||||||
const char* const long_double_numbers = "[3.14159265358979323846, 1.5e-400, -0.000123456789012345678]";
|
|
||||||
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
|
||||||
const long_double_json expected_long_double = long_double_json::parse(long_double_numbers);
|
|
||||||
|
|
||||||
const std::array<const char*, 6> names = {{"ar_EG.UTF-8", "ar_SA.UTF-8", "fa_IR.UTF-8", "ps_AF.UTF-8", "ar_EG", "fa_IR"}};
|
const std::array<const char*, 6> names = {{"ar_EG.UTF-8", "ar_SA.UTF-8", "fa_IR.UTF-8", "ps_AF.UTF-8", "ar_EG", "fa_IR"}};
|
||||||
bool tested = false;
|
bool tested = false;
|
||||||
for (const char* name : names)
|
for (const char* name : names)
|
||||||
@@ -378,20 +369,12 @@ TEST_CASE("locale with a multi-byte decimal point")
|
|||||||
tested = true;
|
tested = true;
|
||||||
|
|
||||||
// too many significant digits for Clinger's fast path, and an underflow
|
// too many significant digits for Clinger's fast path, and an underflow
|
||||||
// that std::from_chars rejects: double does not depend on the locale
|
// that std::from_chars rejects: both reach the strtod fallback
|
||||||
json j;
|
json j;
|
||||||
CHECK_NOTHROW(j = json::parse("[3.14159265358979323846, 1.5e-400, -0.000123456789012345678]"));
|
CHECK_NOTHROW(j = json::parse("[3.14159265358979323846, 1.5e-400, -0.000123456789012345678]"));
|
||||||
CHECK(j.is_array());
|
CHECK(j.is_array());
|
||||||
CHECK(j[0] == 3.14159265358979323846);
|
|
||||||
CHECK(j[1] == 0.0);
|
|
||||||
CHECK(j[2] == -0.000123456789012345678);
|
|
||||||
CHECK(json::accept("3.14159265358979323846"));
|
CHECK(json::accept("3.14159265358979323846"));
|
||||||
|
|
||||||
// a long double that reaches the strtold fallback is not truncated
|
|
||||||
long_double_json ld;
|
|
||||||
CHECK_NOTHROW(ld = long_double_json::parse(long_double_numbers));
|
|
||||||
CHECK(ld == expected_long_double);
|
|
||||||
|
|
||||||
// a value the locale-independent paths convert is not affected
|
// a value the locale-independent paths convert is not affected
|
||||||
CHECK(json::parse("12.5") == 12.5);
|
CHECK(json::parse("12.5") == 12.5);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,19 @@
|
|||||||
// for some reason including this after the json header leads to linker errors with VS 2017...
|
// for some reason including this after the json header leads to linker errors with VS 2017...
|
||||||
#include <locale>
|
#include <locale>
|
||||||
|
|
||||||
|
// skip tests if JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1 (#2226)
|
||||||
|
#if defined(JSON_DISABLE_TUPLE_REFERENCE_CONVERSION) && (JSON_DISABLE_TUPLE_REFERENCE_CONVERSION == 1)
|
||||||
|
#define SKIP_TESTS_FOR_TUPLE_REFERENCE_CONVERSION
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// clang before 4 and GCC before 5 cannot create a std::tuple of basic_json
|
||||||
|
// references at all, with or without JSON_DISABLE_TUPLE_REFERENCE_CONVERSION:
|
||||||
|
// the tuple constructors make them instantiate basic_json's conversion operator
|
||||||
|
// for libstdc++'s internal tuple bases, which fails hard
|
||||||
|
#if (defined(__clang__) && __clang_major__ < 4) || (!defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5)
|
||||||
|
#define SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
||||||
|
#endif
|
||||||
|
|
||||||
#define JSON_TESTS_PRIVATE
|
#define JSON_TESTS_PRIVATE
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
@@ -28,6 +41,7 @@ using ordered_json = nlohmann::ordered_json;
|
|||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <tuple>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@@ -542,6 +556,20 @@ TEST_CASE("regression tests 2")
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef SKIP_TESTS_FOR_TUPLE_REFERENCE_CONVERSION
|
||||||
|
SECTION("issue #2226 - std::tuple dangling reference - implicit conversion")
|
||||||
|
{
|
||||||
|
// by default, a one-element tuple holding a json reference converts to
|
||||||
|
// a one-element array; JSON_DISABLE_TUPLE_REFERENCE_CONVERSION removes
|
||||||
|
// this conversion (see unit-disable-tuple-reference-conversion.cpp)
|
||||||
|
const json j = true;
|
||||||
|
CHECK(std::is_constructible<json, std::tuple<const json&>>::value);
|
||||||
|
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
||||||
|
CHECK(json(std::forward_as_tuple(j)) == json::array({true}));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
SECTION("PR #2181 - regression bug with lvalue")
|
SECTION("PR #2181 - regression bug with lvalue")
|
||||||
{
|
{
|
||||||
// see https://github.com/nlohmann/json/pull/2181#issuecomment-653326060
|
// see https://github.com/nlohmann/json/pull/2181#issuecomment-653326060
|
||||||
@@ -870,4 +898,585 @@ TEST_CASE("regression test - excessive binary container size honors allow_except
|
|||||||
CHECK(json::from_cbor(std::vector<std::uint8_t> {0x9b, 0, 0, 0, 0, 0, 0, 0, 0x02}, true, false).is_discarded());
|
CHECK(json::from_cbor(std::vector<std::uint8_t> {0x9b, 0, 0, 0, 0, 0, 0, 0, 0x02}, true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
/// builds a value from SAX events, asks the parser to recover from its first
|
||||||
|
/// 100 errors, and checks that the events are balanced (see #3989)
|
||||||
|
class RecoveringParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit RecoveringParser(json& j)
|
||||||
|
: dom(j, false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool null()
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.null();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool boolean(bool val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.boolean(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_integer(json::number_integer_t val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.number_integer(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_unsigned(json::number_unsigned_t val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.number_unsigned(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_float(json::number_float_t val, const std::string& s)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.number_float(val, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool string(std::string& val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.string(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool binary(json::binary_t& val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.binary(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool start_object(std::size_t elements)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
stack.push_back('o');
|
||||||
|
return dom.start_object(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool key(std::string& val)
|
||||||
|
{
|
||||||
|
if (stack.empty() || stack.back() != 'o')
|
||||||
|
{
|
||||||
|
well_formed = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stack.back() = 'v';
|
||||||
|
return dom.key(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool end_object()
|
||||||
|
{
|
||||||
|
if (stack.empty() || stack.back() != 'o')
|
||||||
|
{
|
||||||
|
well_formed = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stack.pop_back();
|
||||||
|
return dom.end_object();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool start_array(std::size_t elements)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
stack.push_back('a');
|
||||||
|
return dom.start_array(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool end_array()
|
||||||
|
{
|
||||||
|
if (stack.empty() || stack.back() != 'a')
|
||||||
|
{
|
||||||
|
well_formed = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stack.pop_back();
|
||||||
|
return dom.end_array();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& ex)
|
||||||
|
{
|
||||||
|
messages.emplace_back(ex.what());
|
||||||
|
// a limit, so that a reader that does not stop fails the test
|
||||||
|
// instead of making it hang
|
||||||
|
return ++errors < 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// whether the events were balanced and every key was followed by a value
|
||||||
|
bool balanced() const
|
||||||
|
{
|
||||||
|
return well_formed && stack.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// builds the value
|
||||||
|
nlohmann::detail::json_sax_dom_parser<json> dom;
|
||||||
|
std::size_t errors = 0;
|
||||||
|
std::vector<std::string> messages {}; // NOLINT(readability-redundant-member-init)
|
||||||
|
std::vector<char> stack {}; // NOLINT(readability-redundant-member-init)
|
||||||
|
bool well_formed = true;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void value()
|
||||||
|
{
|
||||||
|
if (!stack.empty())
|
||||||
|
{
|
||||||
|
if (stack.back() == 'v')
|
||||||
|
{
|
||||||
|
stack.back() = 'o';
|
||||||
|
}
|
||||||
|
else if (stack.back() == 'o')
|
||||||
|
{
|
||||||
|
well_formed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BinaryParseResult
|
||||||
|
{
|
||||||
|
json value;
|
||||||
|
std::size_t errors;
|
||||||
|
std::vector<std::string> messages;
|
||||||
|
bool ok;
|
||||||
|
bool balanced;
|
||||||
|
};
|
||||||
|
|
||||||
|
BinaryParseResult parse_binary_recovering(const std::vector<std::uint8_t>& input, const json::input_format_t format)
|
||||||
|
{
|
||||||
|
json j;
|
||||||
|
RecoveringParser sax(j);
|
||||||
|
const bool ok = json::sax_parse(input, &sax, format);
|
||||||
|
return {j, sax.errors, sax.messages, ok, sax.balanced()};
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
/// the message of the exception that reading @a input into a JSON value
|
||||||
|
/// throws, or an empty string if reading succeeds
|
||||||
|
std::string binary_error_message(const std::vector<std::uint8_t>& input, const json::input_format_t format)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
json _;
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case json::input_format_t::cbor:
|
||||||
|
_ = json::from_cbor(input);
|
||||||
|
break;
|
||||||
|
case json::input_format_t::msgpack:
|
||||||
|
_ = json::from_msgpack(input);
|
||||||
|
break;
|
||||||
|
case json::input_format_t::ubjson:
|
||||||
|
_ = json::from_ubjson(input);
|
||||||
|
break;
|
||||||
|
case json::input_format_t::bjdata:
|
||||||
|
_ = json::from_bjdata(input);
|
||||||
|
break;
|
||||||
|
case json::input_format_t::bson:
|
||||||
|
_ = json::from_bson(input);
|
||||||
|
break;
|
||||||
|
case json::input_format_t::bon8:
|
||||||
|
_ = json::from_bon8(input);
|
||||||
|
break;
|
||||||
|
case json::input_format_t::json:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const json::exception& e)
|
||||||
|
{
|
||||||
|
return e.what();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// a BSON element: its type, its name, and its value
|
||||||
|
std::vector<std::uint8_t> bson_element(const std::uint8_t type, const std::string& name, const std::vector<std::uint8_t>& value)
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> result = {type};
|
||||||
|
result.insert(result.end(), name.begin(), name.end());
|
||||||
|
result.push_back(0x00);
|
||||||
|
result.insert(result.end(), value.begin(), value.end());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// a BSON document of the given elements; @a size_offset is added to the
|
||||||
|
/// size it declares
|
||||||
|
std::vector<std::uint8_t> bson_document(const std::vector<std::vector<std::uint8_t>>& elements, const int size_offset = 0)
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> body;
|
||||||
|
for (const auto& element : elements)
|
||||||
|
{
|
||||||
|
body.insert(body.end(), element.begin(), element.end());
|
||||||
|
}
|
||||||
|
const auto size = static_cast<std::uint32_t>(static_cast<int>(body.size()) + 5 + size_offset);
|
||||||
|
std::vector<std::uint8_t> result = {static_cast<std::uint8_t>(size & 0xFFu), static_cast<std::uint8_t>((size >> 8u) & 0xFFu),
|
||||||
|
static_cast<std::uint8_t>((size >> 16u) & 0xFFu), static_cast<std::uint8_t>((size >> 24u) & 0xFFu)
|
||||||
|
};
|
||||||
|
result.insert(result.end(), body.begin(), body.end());
|
||||||
|
result.push_back(0x00);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// a BSON int32 value
|
||||||
|
std::vector<std::uint8_t> bson_int32(const std::int32_t value)
|
||||||
|
{
|
||||||
|
const auto u = static_cast<std::uint32_t>(value);
|
||||||
|
return {static_cast<std::uint8_t>(u & 0xFFu), static_cast<std::uint8_t>((u >> 8u) & 0xFFu),
|
||||||
|
static_cast<std::uint8_t>((u >> 16u) & 0xFFu), static_cast<std::uint8_t>((u >> 24u) & 0xFFu)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// a BSON string value, whose length is @a length_offset off
|
||||||
|
std::vector<std::uint8_t> bson_string(const std::string& value, const std::int32_t length_offset = 0)
|
||||||
|
{
|
||||||
|
auto result = bson_int32(static_cast<std::int32_t>(value.size() + 1) + length_offset);
|
||||||
|
result.insert(result.end(), value.begin(), value.end());
|
||||||
|
result.push_back(0x00);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @a count bytes of value 0xAB
|
||||||
|
std::vector<std::uint8_t> bytes(const std::size_t count)
|
||||||
|
{
|
||||||
|
return std::vector<std::uint8_t>(count, 0xAB);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Parts>
|
||||||
|
std::vector<std::uint8_t> concatenated(const std::vector<std::uint8_t>& first, const Parts& ... rest)
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> result = first;
|
||||||
|
for (const auto& part : std::initializer_list<std::vector<std::uint8_t>> {rest...})
|
||||||
|
{
|
||||||
|
result.insert(result.end(), part.begin(), part.end());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// U+FFFD REPLACEMENT CHARACTER
|
||||||
|
std::string replacement_character()
|
||||||
|
{
|
||||||
|
return "\xEF\xBF\xBD";
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("regression test - #3989 SAX parse_error() returning true")
|
||||||
|
{
|
||||||
|
SECTION("binary formats complete what was read before the input ends")
|
||||||
|
{
|
||||||
|
const json j = {{"a", {1, -2, {{"b", "c"}}, json::array()}}, {"d", {{"e", nullptr}, {"f", true}}}, {"g", 1.5}, {"h", json::binary({1, 2, 3})}};
|
||||||
|
|
||||||
|
const std::vector<std::pair<json::input_format_t, std::vector<std::uint8_t>>> encodings =
|
||||||
|
{
|
||||||
|
{json::input_format_t::cbor, json::to_cbor(j)},
|
||||||
|
{json::input_format_t::msgpack, json::to_msgpack(j)},
|
||||||
|
{json::input_format_t::ubjson, json::to_ubjson(j)},
|
||||||
|
{json::input_format_t::ubjson, json::to_ubjson(j, true, true)},
|
||||||
|
{json::input_format_t::bjdata, json::to_bjdata(j)},
|
||||||
|
{json::input_format_t::bjdata, json::to_bjdata(j, true, true)},
|
||||||
|
{json::input_format_t::bson, json::to_bson(j)},
|
||||||
|
{json::input_format_t::bon8, json::to_bon8(j)},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& encoding : encodings)
|
||||||
|
{
|
||||||
|
const auto format = encoding.first;
|
||||||
|
const auto& bytes = encoding.second;
|
||||||
|
CAPTURE(format);
|
||||||
|
|
||||||
|
// every prefix is truncated input
|
||||||
|
for (std::size_t length = 0; length < bytes.size(); ++length)
|
||||||
|
{
|
||||||
|
CAPTURE(length);
|
||||||
|
const auto result = parse_binary_recovering(std::vector<std::uint8_t>(bytes.begin(), bytes.begin() + static_cast<std::ptrdiff_t>(length)), format);
|
||||||
|
CHECK(!result.ok);
|
||||||
|
CHECK(result.errors == 1);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the complete input is read as usual (binary values do not
|
||||||
|
// round-trip through every format, so compare with a plain parse)
|
||||||
|
json expected;
|
||||||
|
nlohmann::detail::json_sax_dom_parser<json> dom(expected);
|
||||||
|
CHECK(json::sax_parse(bytes, &dom, format));
|
||||||
|
const auto complete = parse_binary_recovering(bytes, format);
|
||||||
|
CHECK(complete.ok);
|
||||||
|
CHECK(complete.errors == 0);
|
||||||
|
CHECK(complete.value == expected);
|
||||||
|
|
||||||
|
// a byte after the value
|
||||||
|
auto trailing_bytes = bytes;
|
||||||
|
trailing_bytes.push_back(0x01);
|
||||||
|
const auto trailing = parse_binary_recovering(trailing_bytes, format);
|
||||||
|
CHECK(!trailing.ok);
|
||||||
|
CHECK(trailing.errors == 1);
|
||||||
|
CHECK(trailing.value == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("containers without an end")
|
||||||
|
{
|
||||||
|
// these made the readers loop, or read on, after the error
|
||||||
|
const auto cbor_array = parse_binary_recovering({0x9F}, json::input_format_t::cbor);
|
||||||
|
CHECK(cbor_array.errors == 1);
|
||||||
|
CHECK(cbor_array.value == json::array());
|
||||||
|
|
||||||
|
const auto cbor_map = parse_binary_recovering({0xBF, 0x61, 'a'}, json::input_format_t::cbor);
|
||||||
|
CHECK(cbor_map.errors == 1);
|
||||||
|
CHECK(cbor_map.value == json({{"a", nullptr}}));
|
||||||
|
|
||||||
|
const auto msgpack_array = parse_binary_recovering({0xDD, 0xFF, 0xFF, 0xFF, 0xFF}, json::input_format_t::msgpack);
|
||||||
|
CHECK(msgpack_array.errors == 1);
|
||||||
|
CHECK(msgpack_array.value == json::array());
|
||||||
|
|
||||||
|
const auto msgpack_map = parse_binary_recovering({0x81, 0xA1, 'a', 0x92, 0x01}, json::input_format_t::msgpack);
|
||||||
|
CHECK(msgpack_map.errors == 1);
|
||||||
|
CHECK(msgpack_map.value == json({{"a", {1}}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("BJData ndarray")
|
||||||
|
{
|
||||||
|
// a 2x3 int8 array with two of its six elements; the annotated array
|
||||||
|
// format opens an object and two arrays of its own
|
||||||
|
const auto result = parse_binary_recovering({'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 1, 2}, json::input_format_t::bjdata);
|
||||||
|
CHECK(result.errors == 1);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(result.value == json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2, 3}}, {"_ArrayData_", {1, 2}}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("binary formats repair items whose end is known")
|
||||||
|
{
|
||||||
|
struct Repair
|
||||||
|
{
|
||||||
|
json::input_format_t format;
|
||||||
|
std::vector<std::uint8_t> input;
|
||||||
|
json expected;
|
||||||
|
std::size_t errors;
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<Repair> repairs =
|
||||||
|
{
|
||||||
|
// CBOR: tags are ignored (here tag 1 and the self-describe tag 55799)
|
||||||
|
{json::input_format_t::cbor, {0x82, 0xC1, 0x05, 0xD9, 0xD9, 0xF7, 0x06}, {5, 6}, 2},
|
||||||
|
// CBOR: undefined and other simple values become null
|
||||||
|
{json::input_format_t::cbor, {0x84, 0xF7, 0xE0, 0xF8, 0x20, 0x01}, {nullptr, nullptr, nullptr, 1}, 3},
|
||||||
|
// CBOR: ill-formed UTF-8 becomes U+FFFD, also in keys
|
||||||
|
{json::input_format_t::cbor, {0xA1, 0x61, 0xFF, 0x62, 0xC3, 0x28}, {{replacement_character(), replacement_character() + "("}}, 2},
|
||||||
|
// CBOR: members whose key is not a string are skipped, whatever their key and value
|
||||||
|
{json::input_format_t::cbor, {0xA4, 0x01, 0x02, 0x82, 0x01, 0x02, 0xA1, 0x61, 'x', 0x9F, 0xFF, 0xC1, 0x01, 0x5F, 0x41, 0x00, 0xFF, 0x61, 'a', 0x03}, {{"a", 3}}, 3},
|
||||||
|
{json::input_format_t::cbor, {0xBF, 0xF5, 0xBF, 0x61, 'x', 0x7F, 0x61, 'y', 0xFF, 0xFF, 0x61, 'a', 0x03, 0xFF}, {{"a", 3}}, 1},
|
||||||
|
// MessagePack: members whose key is not a string are skipped
|
||||||
|
{json::input_format_t::msgpack, {0x84, 0x01, 0x02, 0x81, 0xA1, 'x', 0x01, 0x92, 0x01, 0x02, 0xD4, 0x01, 0x02, 0xC0, 0xA1, 'a', 0x04}, {{"a", 4}}, 3},
|
||||||
|
// MessagePack: ill-formed UTF-8 becomes U+FFFD
|
||||||
|
{json::input_format_t::msgpack, {0x92, 0xA2, 0xC3, 0x28, 0xA3, 0xE2, 0x82, 'x'}, {replacement_character() + "(", replacement_character() + "x"}, 2},
|
||||||
|
// UBJSON: a char that is not ASCII becomes U+FFFD
|
||||||
|
{json::input_format_t::ubjson, {'[', 'C', 0x80, 'C', 'A', ']'}, {replacement_character(), "A"}, 1},
|
||||||
|
// UBJSON: the longest beginning of a high-precision number is kept
|
||||||
|
{json::input_format_t::ubjson, {'[', 'H', 'i', 5, '1', '2', 'a', 'b', 'c', 'H', 'i', 2, '1', '.', 'H', 'i', 3, 'a', 'b', 'c', 'H', 'i', 3, '4', '.', '5', ']'}, {12, 1, nullptr, 4.5}, 3},
|
||||||
|
// BJData, too
|
||||||
|
{json::input_format_t::bjdata, {'[', 'C', 0xFF, 'H', 'i', 2, '-', '1', 'H', 'i', 2, '-', 'x', ']'}, {replacement_character(), -1, nullptr}, 2},
|
||||||
|
// BON8: members whose key is not a string are skipped
|
||||||
|
{json::input_format_t::bon8, {0x89, 0x91, 0x92, 0xC9, 0x40, 0x82, 0x91, 0x92, 0x61, 0x93}, {{"a", 3}}, 2},
|
||||||
|
{json::input_format_t::bon8, {0x8B, 0x91, 0x85, 0x91, 0xFE, 0xFA, 0x8B, 'x', 0x91, 0xFE, 0x61, 0x93, 0xFE}, {{"a", 3}}, 2},
|
||||||
|
// BSON: elements of types the library does not read become null
|
||||||
|
{
|
||||||
|
json::input_format_t::bson, bson_document(
|
||||||
|
{
|
||||||
|
bson_element(0x07, "_id", bytes(12)), // ObjectId
|
||||||
|
bson_element(0x09, "date", bytes(8)), // UTC datetime
|
||||||
|
bson_element(0x13, "decimal", bytes(16)), // 128-bit decimal
|
||||||
|
bson_element(0x0B, "regex", {'a', '+', 0, 'i', 0}), // regular expression
|
||||||
|
bson_element(0x0D, "code", bson_string("f()")), // JavaScript code
|
||||||
|
bson_element(0x0E, "symbol", bson_string("s")), // symbol
|
||||||
|
bson_element(0x0C, "pointer", concatenated(bson_string("c"), bytes(12))), // DBPointer
|
||||||
|
bson_element(0x0F, "scope", concatenated(bson_int32(15), bson_string("g"), bson_document({}))), // code with scope
|
||||||
|
bson_element(0x06, "undefined", {}), // undefined
|
||||||
|
bson_element(0xFF, "min", {}), // min key
|
||||||
|
bson_element(0x7F, "max", {}), // max key
|
||||||
|
bson_element(0x10, "z", bson_int32(7)),
|
||||||
|
}),
|
||||||
|
{{"_id", nullptr}, {"date", nullptr}, {"decimal", nullptr}, {"regex", nullptr}, {"code", nullptr}, {"symbol", nullptr}, {"pointer", nullptr}, {"scope", nullptr}, {"undefined", nullptr}, {"min", nullptr}, {"max", nullptr}, {"z", 7}},
|
||||||
|
11
|
||||||
|
},
|
||||||
|
// BSON: an element of an unknown type becomes null, and the rest of its document is skipped
|
||||||
|
{
|
||||||
|
json::input_format_t::bson, bson_document(
|
||||||
|
{
|
||||||
|
bson_element(0x03, "inner", bson_document({bson_element(0x10, "a", bson_int32(1)), bson_element(0x42, "x", bytes(3)), bson_element(0x10, "b", bson_int32(2))})),
|
||||||
|
bson_element(0x04, "array", bson_document({bson_element(0x10, "0", bson_int32(1)), bson_element(0x42, "1", bytes(3))})),
|
||||||
|
bson_element(0x10, "after", bson_int32(3)),
|
||||||
|
}),
|
||||||
|
{{"inner", {{"a", 1}, {"x", nullptr}}}, {"array", {1, nullptr}}, {"after", 3}},
|
||||||
|
2
|
||||||
|
},
|
||||||
|
// BSON: so does a string or byte array whose length cannot be right
|
||||||
|
{
|
||||||
|
json::input_format_t::bson, bson_document(
|
||||||
|
{
|
||||||
|
bson_element(0x03, "inner", bson_document({bson_element(0x02, "s", bson_string("abc", -10)), bson_element(0x10, "b", bson_int32(2))})),
|
||||||
|
bson_element(0x03, "bin", bson_document({bson_element(0x05, "b", concatenated(bson_int32(-1), bytes(1))), bson_element(0x10, "b", bson_int32(2))})),
|
||||||
|
bson_element(0x10, "after", bson_int32(3)),
|
||||||
|
}),
|
||||||
|
{{"inner", {{"s", nullptr}}}, {"bin", {{"b", nullptr}}}, {"after", 3}},
|
||||||
|
2
|
||||||
|
},
|
||||||
|
// BSON: a string without its terminator, and a document whose size does not match, are kept
|
||||||
|
{
|
||||||
|
json::input_format_t::bson, bson_document(
|
||||||
|
{
|
||||||
|
bson_element(0x02, "s", {2, 0, 0, 0, 'a', 'X'}),
|
||||||
|
bson_element(0x03, "inner", bson_document({bson_element(0x10, "a", bson_int32(1))}, 1)),
|
||||||
|
}),
|
||||||
|
{{"s", "a"}, {"inner", {{"a", 1}}}},
|
||||||
|
2
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& repair : repairs)
|
||||||
|
{
|
||||||
|
CAPTURE(repair.format);
|
||||||
|
CAPTURE(repair.input);
|
||||||
|
const auto result = parse_binary_recovering(repair.input, repair.format);
|
||||||
|
CHECK(!result.ok);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(result.errors == repair.errors);
|
||||||
|
CHECK(result.value == repair.expected);
|
||||||
|
REQUIRE(!result.messages.empty());
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
// the first error is the one reported without recovering; under
|
||||||
|
// JSON_NOEXCEPTION, reading without recovering aborts instead of
|
||||||
|
// throwing, so there is no message to compare with
|
||||||
|
CHECK(result.messages.front() == binary_error_message(repair.input, repair.format));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("binary formats repair numbers that are out of range")
|
||||||
|
{
|
||||||
|
// CBOR: a negative integer below the range of number_integer_t
|
||||||
|
const auto cbor = parse_binary_recovering({0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, json::input_format_t::cbor);
|
||||||
|
CHECK(cbor.errors == 1);
|
||||||
|
CHECK(cbor.value.is_number_float());
|
||||||
|
CHECK(cbor.value.get<double>() == -18446744073709551616.0);
|
||||||
|
|
||||||
|
// UBJSON: a high-precision number too large for number_float_t
|
||||||
|
const auto ubjson = parse_binary_recovering({'H', 'i', 5, '1', 'e', '9', '9', '9'}, json::input_format_t::ubjson);
|
||||||
|
CHECK(ubjson.errors == 1);
|
||||||
|
CHECK(ubjson.value.is_number_float());
|
||||||
|
CHECK(std::isinf(ubjson.value.get<double>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("binary formats stop where the end of an item is not known")
|
||||||
|
{
|
||||||
|
// a byte that begins no item
|
||||||
|
const auto cbor = parse_binary_recovering({0x82, 0x01, 0x1C, 0x02}, json::input_format_t::cbor);
|
||||||
|
CHECK(cbor.errors == 1);
|
||||||
|
CHECK(cbor.value == json({1}));
|
||||||
|
|
||||||
|
// a key that is no item: the unused MessagePack byte, a CBOR break
|
||||||
|
// in a map of known size, and the end of a BON8 container
|
||||||
|
const auto msgpack = parse_binary_recovering({0x82, 0xA1, 'a', 0x01, 0xC1, 0x02}, json::input_format_t::msgpack);
|
||||||
|
CHECK(msgpack.errors == 1);
|
||||||
|
CHECK(msgpack.value == json({{"a", 1}}));
|
||||||
|
const auto cbor_break = parse_binary_recovering({0xA2, 0x61, 'a', 0x01, 0xFF, 0x02}, json::input_format_t::cbor);
|
||||||
|
CHECK(cbor_break.errors == 1);
|
||||||
|
CHECK(cbor_break.value == json({{"a", 1}}));
|
||||||
|
const auto bon8 = parse_binary_recovering({0x88, 0x61, 0x91, 0xFE}, json::input_format_t::bon8);
|
||||||
|
CHECK(bon8.errors == 1);
|
||||||
|
CHECK(bon8.value == json({{"a", 1}}));
|
||||||
|
|
||||||
|
// a skipped member that the input ends in
|
||||||
|
const auto truncated = parse_binary_recovering({0xA2, 0x01, 0x82, 0x01}, json::input_format_t::cbor);
|
||||||
|
CHECK(truncated.errors == 2);
|
||||||
|
CHECK(truncated.balanced);
|
||||||
|
CHECK(truncated.value == json::object());
|
||||||
|
|
||||||
|
// a BSON element of an unknown type in a document whose size cannot be right
|
||||||
|
const auto bson = parse_binary_recovering(bson_document({bson_element(0x10, "a", bson_int32(1)), bson_element(0x42, "x", bytes(3))}, -10), json::input_format_t::bson);
|
||||||
|
CHECK(bson.errors == 1);
|
||||||
|
CHECK(bson.value == json({{"a", 1}, {"x", nullptr}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("changed bytes in binary input")
|
||||||
|
{
|
||||||
|
const json j = {{"a", {1, -2, {{"b", "c"}}, json::array()}}, {"d", {{"e", nullptr}, {"f", true}}}, {"g", 1.5}, {"h", json::binary({1, 2, 3})}, {"i", "\xC3\xA4"}};
|
||||||
|
|
||||||
|
const std::vector<std::pair<json::input_format_t, std::vector<std::uint8_t>>> encodings =
|
||||||
|
{
|
||||||
|
{json::input_format_t::cbor, json::to_cbor(j)},
|
||||||
|
{json::input_format_t::msgpack, json::to_msgpack(j)},
|
||||||
|
{json::input_format_t::ubjson, json::to_ubjson(j)},
|
||||||
|
{json::input_format_t::ubjson, json::to_ubjson(j, true, true)},
|
||||||
|
{json::input_format_t::bjdata, json::to_bjdata(j)},
|
||||||
|
{json::input_format_t::bjdata, json::to_bjdata(j, true, true)},
|
||||||
|
{json::input_format_t::bson, json::to_bson(j)},
|
||||||
|
{json::input_format_t::bon8, json::to_bon8(j)},
|
||||||
|
};
|
||||||
|
const std::vector<std::uint8_t> replacements = {0x00, 0x01, 0x7F, 0x80, 0xC1, 0xD9, 0xE0, 0xF7, 0xFE, 0xFF};
|
||||||
|
|
||||||
|
for (const auto& encoding : encodings)
|
||||||
|
{
|
||||||
|
const auto format = encoding.first;
|
||||||
|
const auto& original = encoding.second;
|
||||||
|
CAPTURE(format);
|
||||||
|
|
||||||
|
std::vector<std::vector<std::uint8_t>> inputs;
|
||||||
|
for (std::size_t position = 0; position < original.size(); ++position)
|
||||||
|
{
|
||||||
|
for (const auto replacement : replacements)
|
||||||
|
{
|
||||||
|
auto changed = original;
|
||||||
|
changed[position] = replacement;
|
||||||
|
inputs.push_back(changed);
|
||||||
|
}
|
||||||
|
auto removed = original;
|
||||||
|
removed.erase(removed.begin() + static_cast<std::ptrdiff_t>(position));
|
||||||
|
inputs.push_back(removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& input : inputs)
|
||||||
|
{
|
||||||
|
CAPTURE(input);
|
||||||
|
const auto result = parse_binary_recovering(input, format);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(result.errors <= input.size() + 1);
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
// an error is reported exactly if reading into a JSON value
|
||||||
|
// fails, and the first one is the same (under JSON_NOEXCEPTION,
|
||||||
|
// that reading aborts instead of throwing)
|
||||||
|
const auto message = binary_error_message(input, format);
|
||||||
|
CHECK(result.ok == message.empty());
|
||||||
|
if (!result.ok && result.errors < 100)
|
||||||
|
{
|
||||||
|
CHECK(result.messages.front() == message);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("JSON text")
|
||||||
|
{
|
||||||
|
// the parser stopped, but reported success
|
||||||
|
json j;
|
||||||
|
RecoveringParser sax(j);
|
||||||
|
CHECK(!json::sax_parse("[1,2,3,]", &sax));
|
||||||
|
CHECK(sax.errors == 1);
|
||||||
|
CHECK(j == json({1, 2, 3}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("the SAX parsers of the library stop")
|
||||||
|
{
|
||||||
|
json _;
|
||||||
|
CHECK(json::from_cbor(std::vector<std::uint8_t> {0x9F}, true, false).is_discarded());
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t> {0x9F}), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
||||||
|
CHECK(json::parse("[1,2,3,]", nullptr, false).is_discarded());
|
||||||
|
CHECK(!json::accept("[1,2,3,]"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
|
|||||||
@@ -3033,3 +3033,224 @@ TEST_CASE("UBJSON optimized array of unsigned integers beyond int64")
|
|||||||
CHECK(json::to_ubjson(j, true, true) == expected);
|
CHECK(json::to_ubjson(j, true, true) == expected);
|
||||||
CHECK(json::from_ubjson(expected) == j);
|
CHECK(json::from_ubjson(expected) == j);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// the bytes that follow the marker of an integer: the value in the width of
|
||||||
|
// the marker (big endian for UBJSON, little endian for BJData), or, for a
|
||||||
|
// high-precision number, the length and the decimal digits
|
||||||
|
std::vector<std::uint8_t> integer_payload(const char marker, const json& value, const bool little_endian)
|
||||||
|
{
|
||||||
|
std::size_t width = 0;
|
||||||
|
switch (marker)
|
||||||
|
{
|
||||||
|
case 'i':
|
||||||
|
case 'U':
|
||||||
|
width = 1;
|
||||||
|
break;
|
||||||
|
case 'I':
|
||||||
|
case 'u':
|
||||||
|
width = 2;
|
||||||
|
break;
|
||||||
|
case 'l':
|
||||||
|
case 'm':
|
||||||
|
width = 4;
|
||||||
|
break;
|
||||||
|
case 'L':
|
||||||
|
case 'M':
|
||||||
|
width = 8;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
const std::string digits = value.dump();
|
||||||
|
std::vector<std::uint8_t> result = {'i', static_cast<std::uint8_t>(digits.size())};
|
||||||
|
for (const char c : digits)
|
||||||
|
{
|
||||||
|
result.push_back(static_cast<std::uint8_t>(c));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::uint64_t bits = value.is_number_unsigned()
|
||||||
|
? value.get<std::uint64_t>()
|
||||||
|
: static_cast<std::uint64_t>(value.get<std::int64_t>());
|
||||||
|
std::vector<std::uint8_t> result(width);
|
||||||
|
for (std::size_t i = 0; i < width; ++i)
|
||||||
|
{
|
||||||
|
result[little_endian ? i : width - 1 - i] = static_cast<std::uint8_t>(bits >> (8 * i));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
json i64(const std::int64_t v)
|
||||||
|
{
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
json u64(const std::uint64_t v)
|
||||||
|
{
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("UBJSON and BJData integer markers at every range edge")
|
||||||
|
{
|
||||||
|
// An optimized container announces the marker of its values after `$` and
|
||||||
|
// then writes every value without a marker, so the marker the writer
|
||||||
|
// announces and the width it writes must match for every value. This
|
||||||
|
// checks both for the values around each edge of the integer types, as
|
||||||
|
// scalars and as the values of optimized arrays and objects.
|
||||||
|
struct integer_case
|
||||||
|
{
|
||||||
|
json value;
|
||||||
|
char ubjson; // expected UBJSON marker
|
||||||
|
char bjdata; // expected BJData marker
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::int64_t int64_min = (std::numeric_limits<std::int64_t>::min)();
|
||||||
|
const std::int64_t int64_max = (std::numeric_limits<std::int64_t>::max)();
|
||||||
|
const std::uint64_t uint64_max = (std::numeric_limits<std::uint64_t>::max)();
|
||||||
|
|
||||||
|
const std::vector<integer_case> cases =
|
||||||
|
{
|
||||||
|
// int8
|
||||||
|
{i64(-129), 'I', 'I'},
|
||||||
|
{i64(-128), 'i', 'i'},
|
||||||
|
{i64(-127), 'i', 'i'},
|
||||||
|
{i64(-1), 'i', 'i'},
|
||||||
|
{i64(0), 'i', 'i'},
|
||||||
|
{u64(0), 'i', 'i'},
|
||||||
|
{i64(126), 'i', 'i'},
|
||||||
|
{i64(127), 'i', 'i'},
|
||||||
|
{u64(127), 'i', 'i'},
|
||||||
|
{i64(128), 'U', 'U'},
|
||||||
|
{u64(128), 'U', 'U'},
|
||||||
|
// uint8
|
||||||
|
{i64(254), 'U', 'U'},
|
||||||
|
{i64(255), 'U', 'U'},
|
||||||
|
{u64(255), 'U', 'U'},
|
||||||
|
{i64(256), 'I', 'I'},
|
||||||
|
{u64(256), 'I', 'I'},
|
||||||
|
// int16
|
||||||
|
{i64(-32769), 'l', 'l'},
|
||||||
|
{i64(-32768), 'I', 'I'},
|
||||||
|
{i64(-32767), 'I', 'I'},
|
||||||
|
{i64(32766), 'I', 'I'},
|
||||||
|
{i64(32767), 'I', 'I'},
|
||||||
|
{u64(32767), 'I', 'I'},
|
||||||
|
{i64(32768), 'l', 'u'},
|
||||||
|
{u64(32768), 'l', 'u'},
|
||||||
|
// uint16 (BJData only)
|
||||||
|
{i64(65534), 'l', 'u'},
|
||||||
|
{i64(65535), 'l', 'u'},
|
||||||
|
{u64(65535), 'l', 'u'},
|
||||||
|
{i64(65536), 'l', 'l'},
|
||||||
|
{u64(65536), 'l', 'l'},
|
||||||
|
// int32
|
||||||
|
{i64(-2147483649LL), 'L', 'L'},
|
||||||
|
{i64(-2147483648LL), 'l', 'l'},
|
||||||
|
{i64(-2147483647LL), 'l', 'l'},
|
||||||
|
{i64(2147483646LL), 'l', 'l'},
|
||||||
|
{i64(2147483647LL), 'l', 'l'},
|
||||||
|
{u64(2147483647ULL), 'l', 'l'},
|
||||||
|
{i64(2147483648LL), 'L', 'm'},
|
||||||
|
{u64(2147483648ULL), 'L', 'm'},
|
||||||
|
// uint32 (BJData only)
|
||||||
|
{i64(4294967294LL), 'L', 'm'},
|
||||||
|
{i64(4294967295LL), 'L', 'm'},
|
||||||
|
{u64(4294967295ULL), 'L', 'm'},
|
||||||
|
{i64(4294967296LL), 'L', 'L'},
|
||||||
|
{u64(4294967296ULL), 'L', 'L'},
|
||||||
|
// int64
|
||||||
|
{i64(int64_min), 'L', 'L'},
|
||||||
|
{i64(int64_min + 1), 'L', 'L'},
|
||||||
|
{i64(int64_max - 1), 'L', 'L'},
|
||||||
|
{i64(int64_max), 'L', 'L'},
|
||||||
|
{u64(static_cast<std::uint64_t>(int64_max)), 'L', 'L'},
|
||||||
|
// uint64 (BJData only; UBJSON writes a high-precision number)
|
||||||
|
{u64(static_cast<std::uint64_t>(int64_max) + 1), 'H', 'M'},
|
||||||
|
{u64(uint64_max - 1), 'H', 'M'},
|
||||||
|
{u64(uint64_max), 'H', 'M'},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& c : cases)
|
||||||
|
{
|
||||||
|
for (const bool bjdata :
|
||||||
|
{
|
||||||
|
false, true
|
||||||
|
})
|
||||||
|
{
|
||||||
|
const char marker = bjdata ? c.bjdata : c.ubjson;
|
||||||
|
const std::vector<std::uint8_t> payload = integer_payload(marker, c.value, bjdata);
|
||||||
|
const auto to_binary = [bjdata](const json & j, const bool use_size, const bool use_type)
|
||||||
|
{
|
||||||
|
return bjdata ? json::to_bjdata(j, use_size, use_type) : json::to_ubjson(j, use_size, use_type);
|
||||||
|
};
|
||||||
|
const auto from_binary = [bjdata](const std::vector<std::uint8_t>& v)
|
||||||
|
{
|
||||||
|
return bjdata ? json::from_bjdata(v) : json::from_ubjson(v);
|
||||||
|
};
|
||||||
|
INFO("value = " << c.value.dump() << (c.value.is_number_unsigned() ? " (unsigned)" : "") << ", format = " << (bjdata ? "BJData" : "UBJSON"));
|
||||||
|
|
||||||
|
// scalar
|
||||||
|
std::vector<std::uint8_t> expected = {static_cast<std::uint8_t>(marker)};
|
||||||
|
expected.insert(expected.end(), payload.begin(), payload.end());
|
||||||
|
for (const bool use_size :
|
||||||
|
{
|
||||||
|
false, true
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CHECK(to_binary(c.value, use_size, false) == expected);
|
||||||
|
}
|
||||||
|
CHECK(from_binary(expected) == c.value);
|
||||||
|
|
||||||
|
const json arr = {c.value, c.value, c.value};
|
||||||
|
|
||||||
|
// array without count or type: every value has its marker
|
||||||
|
expected = {'['};
|
||||||
|
for (int i = 0; i < 3; ++i)
|
||||||
|
{
|
||||||
|
expected.push_back(static_cast<std::uint8_t>(marker));
|
||||||
|
expected.insert(expected.end(), payload.begin(), payload.end());
|
||||||
|
}
|
||||||
|
expected.push_back(']');
|
||||||
|
CHECK(to_binary(arr, false, false) == expected);
|
||||||
|
CHECK(from_binary(expected) == arr);
|
||||||
|
|
||||||
|
// array with count: every value has its marker
|
||||||
|
expected = {'[', '#', 'i', 3};
|
||||||
|
for (int i = 0; i < 3; ++i)
|
||||||
|
{
|
||||||
|
expected.push_back(static_cast<std::uint8_t>(marker));
|
||||||
|
expected.insert(expected.end(), payload.begin(), payload.end());
|
||||||
|
}
|
||||||
|
CHECK(to_binary(arr, true, false) == expected);
|
||||||
|
CHECK(from_binary(expected) == arr);
|
||||||
|
|
||||||
|
// array with type and count: the marker once, then the payloads
|
||||||
|
expected = {'[', '$', static_cast<std::uint8_t>(marker), '#', 'i', 3};
|
||||||
|
for (int i = 0; i < 3; ++i)
|
||||||
|
{
|
||||||
|
expected.insert(expected.end(), payload.begin(), payload.end());
|
||||||
|
}
|
||||||
|
CHECK(to_binary(arr, true, true) == expected);
|
||||||
|
CHECK(from_binary(expected) == arr);
|
||||||
|
|
||||||
|
// object with type and count: the marker once, then key and payload
|
||||||
|
const json obj = {{"a", c.value}, {"b", c.value}};
|
||||||
|
expected = {'{', '$', static_cast<std::uint8_t>(marker), '#', 'i', 2};
|
||||||
|
for (const char key :
|
||||||
|
{'a', 'b'
|
||||||
|
})
|
||||||
|
{
|
||||||
|
expected.push_back('i');
|
||||||
|
expected.push_back(1);
|
||||||
|
expected.push_back(static_cast<std::uint8_t>(key));
|
||||||
|
expected.insert(expected.end(), payload.begin(), payload.end());
|
||||||
|
}
|
||||||
|
CHECK(to_binary(obj, true, true) == expected);
|
||||||
|
CHECK(from_binary(expected) == obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+15
-11
@@ -1,8 +1,8 @@
|
|||||||
# amalgamate.py - Amalgamate C source and header files
|
# amalgamate.py - Amalgamate C source and header files
|
||||||
|
|
||||||
Origin: https://bitbucket.org/erikedlund/amalgamate
|
Origin: https://github.com/edlund/amalgamate (formerly hosted at
|
||||||
|
https://bitbucket.org/erikedlund/amalgamate, which no longer exists; see
|
||||||
Mirror: https://github.com/edlund/amalgamate
|
`CHANGES.md` for the upstream commit this copy is based on)
|
||||||
|
|
||||||
`amalgamate.py` aims to make it easy to use SQLite-style C source and header
|
`amalgamate.py` aims to make it easy to use SQLite-style C source and header
|
||||||
amalgamation in projects.
|
amalgamation in projects.
|
||||||
@@ -41,21 +41,22 @@ results.
|
|||||||
|
|
||||||
## Installing amalgamate.py
|
## Installing amalgamate.py
|
||||||
|
|
||||||
Python v.2.7.0 or higher is required.
|
Python 3 is required.
|
||||||
|
|
||||||
`amalgamate.py` can be tested and installed using the following commands:
|
In this repository, `amalgamate.py` is not installed separately; it is run in
|
||||||
|
place through `make amalgamate`, which calls it once for `json.hpp` and once
|
||||||
./test.sh && sudo -k cp ./amalgamate.py /usr/local/bin/
|
for `json_fwd.hpp` (see the root `Makefile`).
|
||||||
|
|
||||||
## Using amalgamate.py
|
## Using amalgamate.py
|
||||||
|
|
||||||
amalgamate.py [-v] -c path/to/config.json -s path/to/source/dir \
|
amalgamate.py -c path/to/config.json -s path/to/source/dir \
|
||||||
[-p path/to/prologue.(c|h)]
|
[-p path/to/prologue.(c|h)] [--verbose=yes|no]
|
||||||
|
|
||||||
* The `-c, --config` option should specify the path to a JSON config file which
|
* The `-c, --config` option should specify the path to a JSON config file which
|
||||||
lists the source files, include paths and where to write the resulting
|
lists the source files, include paths and where to write the resulting
|
||||||
amalgamation. Have a look at `test/source.c.json` and `test/include.h.json`
|
amalgamation. `config_json.json` and `config_json_fwd.json` in this
|
||||||
to see two examples.
|
directory are the configs used for `json.hpp` and `json_fwd.hpp`; each
|
||||||
|
sets `target`, `sources` and `include_paths`.
|
||||||
|
|
||||||
The optional `external` list names include paths that are kept as `#include`
|
The optional `external` list names include paths that are kept as `#include`
|
||||||
directives instead of being inlined, e.g. `["nlohmann/json.hpp"]` for a header
|
directives instead of being inlined, e.g. `["nlohmann/json.hpp"]` for a header
|
||||||
@@ -68,3 +69,6 @@ Python v.2.7.0 or higher is required.
|
|||||||
* The `-p, --prologue` option should specify the path to a file which will be
|
* The `-p, --prologue` option should specify the path to a file which will be
|
||||||
added to the beginning of the amalgamation. It is optional.
|
added to the beginning of the amalgamation. It is optional.
|
||||||
|
|
||||||
|
* The `-v, --verbose` option takes `yes` or `no` (for example
|
||||||
|
`--verbose=yes`, as used by the Makefile). It is optional.
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,26 @@
|
|||||||
|
|
||||||
Generate the Natvis debugger visualization file for all supported namespace combinations.
|
Generate the Natvis debugger visualization file for all supported namespace combinations.
|
||||||
|
|
||||||
|
The ABI tag list and the library version are parsed from
|
||||||
|
`include/nlohmann/detail/abi_macros.hpp`, so this script must be re-run (via
|
||||||
|
`make natvis`) whenever an `NLOHMANN_JSON_ABI_TAG_*` macro is added to that
|
||||||
|
file or the library version is bumped — otherwise the committed
|
||||||
|
`nlohmann_json.natvis` drifts from the header it visualizes, and
|
||||||
|
`make check-amalgamation` fails.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./generate_natvis.py --version X.Y.Z output_directory/
|
make natvis
|
||||||
```
|
```
|
||||||
|
|
||||||
|
or, equivalently:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
./generate_natvis.py [--version X.Y.Z] [repository_root/]
|
||||||
|
```
|
||||||
|
|
||||||
|
`--version` and the output/repository-root directory both default to values
|
||||||
|
derived from this script's own location, so they only need to be given
|
||||||
|
explicitly when generating a Natvis file for a different checkout or a
|
||||||
|
version other than the one in `abi_macros.hpp`.
|
||||||
|
|||||||
@@ -7,21 +7,75 @@ import os
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
# Directory of the repository, assuming this script stays at
|
||||||
|
# tools/generate_natvis/generate_natvis.py. Used only as the default value
|
||||||
|
# for the "output" argument below.
|
||||||
|
REPO_ROOT = os.path.normpath(os.path.join(sys.path[0], '..', '..'))
|
||||||
|
|
||||||
|
|
||||||
def semver(v):
|
def semver(v):
|
||||||
if not re.fullmatch(r'\d+\.\d+\.\d+', v):
|
if not re.fullmatch(r'\d+\.\d+\.\d+', v):
|
||||||
raise ValueError
|
raise ValueError
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
def abi_info(repo_root):
|
||||||
|
"""Derive the ABI tag list (in NLOHMANN_JSON_ABI_TAGS_CONCAT order) and the
|
||||||
|
library version from <repo_root>/include/nlohmann/detail/abi_macros.hpp,
|
||||||
|
so this script cannot drift from the header it visualizes."""
|
||||||
|
abi_macros_hpp = os.path.join(repo_root, 'include', 'nlohmann', 'detail', 'abi_macros.hpp')
|
||||||
|
with open(abi_macros_hpp) as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# find the NLOHMANN_JSON_ABI_TAGS_CONCAT(...) invocation that lists the
|
||||||
|
# NLOHMANN_JSON_ABI_TAG_* identifiers in order (not its own #define, which
|
||||||
|
# only names its formal parameters a, b, c, ...)
|
||||||
|
tag_idents = None
|
||||||
|
for args in re.findall(r'NLOHMANN_JSON_ABI_TAGS_CONCAT\(\s*(.*?)\)', content, re.S):
|
||||||
|
idents = re.findall(r'NLOHMANN_JSON_ABI_TAG_\w+', args)
|
||||||
|
if idents:
|
||||||
|
tag_idents = idents
|
||||||
|
break
|
||||||
|
if not tag_idents:
|
||||||
|
raise ValueError(f'could not find NLOHMANN_JSON_ABI_TAGS_CONCAT(...) in {abi_macros_hpp}')
|
||||||
|
|
||||||
|
abi_tags = []
|
||||||
|
for ident in tag_idents:
|
||||||
|
# each tag is #define'd to its suffix (e.g. _diag) when the matching
|
||||||
|
# JSON_* option is enabled, and to nothing in the #else branch; only
|
||||||
|
# the non-empty definition matches here
|
||||||
|
match = re.search(r'#define\s+' + re.escape(ident) + r'\s+(_\w+)\s*\n', content)
|
||||||
|
if not match:
|
||||||
|
raise ValueError(f'could not find a non-empty #define for {ident} in {abi_macros_hpp}')
|
||||||
|
abi_tags.append(match.group(1))
|
||||||
|
|
||||||
|
version = {}
|
||||||
|
for part in ('MAJOR', 'MINOR', 'PATCH'):
|
||||||
|
match = re.search(r'#define\s+NLOHMANN_JSON_VERSION_' + part + r'\s+(\d+)', content)
|
||||||
|
if not match:
|
||||||
|
raise ValueError(f'could not find NLOHMANN_JSON_VERSION_{part} in {abi_macros_hpp}')
|
||||||
|
version[part] = match.group(1)
|
||||||
|
|
||||||
|
return abi_tags, '{MAJOR}.{MINOR}.{PATCH}'.format(**version)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('--version', required=True, type=semver, help='Library version number')
|
parser.add_argument('--version', type=semver,
|
||||||
parser.add_argument('output', help='Output directory for nlohmann_json.natvis')
|
help='Library version number (default: parsed from '
|
||||||
|
'include/nlohmann/detail/abi_macros.hpp below "output")')
|
||||||
|
parser.add_argument('output', nargs='?', default=REPO_ROOT,
|
||||||
|
help='Repository root: where include/nlohmann/detail/abi_macros.hpp is '
|
||||||
|
'read from and where nlohmann_json.natvis is written '
|
||||||
|
'(default: the repository root this script lives in)')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
derived_tags, derived_version = abi_info(args.output)
|
||||||
|
|
||||||
namespaces = ['nlohmann']
|
namespaces = ['nlohmann']
|
||||||
abi_prefix = 'json_abi'
|
abi_prefix = 'json_abi'
|
||||||
abi_tags = ['_diag', '_ldvcmp', '_dp', '_bics', '_psp', '_snul']
|
abi_tags = derived_tags
|
||||||
version = '_v' + args.version.replace('.', '_')
|
version = '_v' + (args.version or derived_version).replace('.', '_')
|
||||||
inline_namespaces = []
|
inline_namespaces = []
|
||||||
|
|
||||||
# generate all combinations of inline namespace names
|
# generate all combinations of inline namespace names
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# macro_builder
|
||||||
|
|
||||||
|
Generates the argument-counting macros behind the `NLOHMANN_DEFINE_TYPE_*` and `NLOHMANN_DEFINE_DERIVED_TYPE_*`
|
||||||
|
macros in [`include/nlohmann/detail/macro_scope.hpp`](../../include/nlohmann/detail/macro_scope.hpp):
|
||||||
|
|
||||||
|
- `NLOHMANN_JSON_EXPAND`
|
||||||
|
- `NLOHMANN_JSON_GET_MACRO`, which selects a macro by the number of its arguments (64 slots)
|
||||||
|
- `NLOHMANN_JSON_PASTE`, which calls a function-like macro for each member, and its helpers `NLOHMANN_JSON_PASTE2`
|
||||||
|
to `NLOHMANN_JSON_PASTE64`
|
||||||
|
- `NLOHMANN_JSON_DOUBLE_PASTE`, which the `*_WITH_NAMES` macros use to call a function-like macro for each
|
||||||
|
(JSON name, member) pair, and its helpers `NLOHMANN_JSON_DOUBLE_PASTE3` to `NLOHMANN_JSON_DOUBLE_PASTE63`
|
||||||
|
- the slot table of `NLOHMANN_JSON_TYPE_BODY`, which dispatches `NLOHMANN_DEFINE_TYPE_*(Type)` (no further
|
||||||
|
arguments) to the zero-member implementation and every other argument count to the one-or-more-member
|
||||||
|
implementation
|
||||||
|
|
||||||
|
The number of slots (`max_args` in [`main.cpp`](main.cpp)) sets the member limit of these macros.
|
||||||
|
`NLOHMANN_JSON_PASTE` and `NLOHMANN_JSON_TYPE_BODY` take the function/prefix as their first argument, so 64 slots
|
||||||
|
allow 63 members; `NLOHMANN_JSON_DOUBLE_PASTE` additionally consumes its members two at a time (name, member), so
|
||||||
|
it only defines the odd helpers up to `NLOHMANN_JSON_DOUBLE_PASTE63`.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
From the project root:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
c++ -std=c++11 tools/macro_builder/main.cpp -o macro_builder
|
||||||
|
./macro_builder
|
||||||
|
./macro_builder type_body
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Run `./macro_builder` (no arguments). In `include/nlohmann/detail/macro_scope.hpp`, replace the lines from
|
||||||
|
`#define NLOHMANN_JSON_EXPAND( x ) x` to the `#define NLOHMANN_JSON_DOUBLE_PASTE63(...)` line with the output,
|
||||||
|
without its trailing empty line.
|
||||||
|
2. Run `./macro_builder type_body`. Replace the lines from `#define NLOHMANN_JSON_TYPE_BODY(Prefix, ...)` to the
|
||||||
|
`NLOHMANN_JSON_TYPE_BODY_SENTINEL))` line with the output.
|
||||||
|
3. Run `make amalgamate`. It updates `single_include/nlohmann/json.hpp` and runs `make pretty`, which indents the
|
||||||
|
continuation lines that the tool writes unindented.
|
||||||
|
|
||||||
|
With an unchanged `main.cpp`, these steps reproduce both blocks of `macro_scope.hpp` byte for byte. `make
|
||||||
|
macro_builder_check` (also run by CI, see `.github/workflows/check_amalgamation.yml`) automates this: it builds
|
||||||
|
`main.cpp`, regenerates both blocks, and fails on a diff against the checked-in header.
|
||||||
|
|
||||||
|
## Maintained by hand
|
||||||
|
|
||||||
|
The tool does not generate everything that depends on the number of slots. When changing `max_args`, also update:
|
||||||
|
|
||||||
|
- the documented limit of 63 members in `docs/mkdocs/docs` and the tests at that limit in
|
||||||
|
`tests/src/unit-udt_macro.cpp`
|
||||||
|
|
||||||
|
All three tables pass one macro name per slot to `NLOHMANN_JSON_GET_MACRO`, so they need exactly as many entries as
|
||||||
|
it has slots.
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
void build_code(int max_args)
|
// Builds NLOHMANN_JSON_EXPAND, NLOHMANN_JSON_GET_MACRO, and the
|
||||||
|
// NLOHMANN_JSON_PASTE / NLOHMANN_JSON_PASTE2..PASTE<max_args> dispatch table
|
||||||
|
// and recursive definitions.
|
||||||
|
string build_paste_code(int max_args)
|
||||||
{
|
{
|
||||||
stringstream ss;
|
stringstream ss;
|
||||||
ss << "#define NLOHMANN_JSON_EXPAND( x ) x" << endl;
|
ss << "#define NLOHMANN_JSON_EXPAND( x ) x" << endl;
|
||||||
@@ -12,32 +16,93 @@ void build_code(int max_args)
|
|||||||
for (int i = 0 ; i < max_args ; i++)
|
for (int i = 0 ; i < max_args ; i++)
|
||||||
ss << "_" << i + 1 << ", ";
|
ss << "_" << i + 1 << ", ";
|
||||||
ss << "NAME,...) NAME" << endl;
|
ss << "NAME,...) NAME" << endl;
|
||||||
|
|
||||||
ss << "#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \\" << endl;
|
ss << "#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \\" << endl;
|
||||||
for (int i = max_args ; i > 1 ; i--)
|
for (int i = max_args ; i > 1 ; i--)
|
||||||
ss << "NLOHMANN_JSON_PASTE" << i << ", \\" << endl;
|
ss << "NLOHMANN_JSON_PASTE" << i << ", \\" << endl;
|
||||||
ss << "NLOHMANN_JSON_PASTE1)(__VA_ARGS__))" << endl;
|
ss << "NLOHMANN_JSON_PASTE1)(__VA_ARGS__))" << endl;
|
||||||
|
|
||||||
ss << "#define NLOHMANN_JSON_PASTE2(func, v1) func(v1)" << endl;
|
ss << "#define NLOHMANN_JSON_PASTE2(func, v1) func(v1)" << endl;
|
||||||
for (int i = 3 ; i <= max_args ; i++)
|
for (int i = 3 ; i <= max_args ; i++)
|
||||||
{
|
{
|
||||||
ss << "#define NLOHMANN_JSON_PASTE" << i << "(func, ";
|
ss << "#define NLOHMANN_JSON_PASTE" << i << "(func, ";
|
||||||
for (int j = 1 ; j < i -1 ; j++)
|
for (int j = 1 ; j < i -1 ; j++)
|
||||||
ss << "v" << j << ", ";
|
ss << "v" << j << ", ";
|
||||||
ss << "v" << i-1 << ") NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE" << i-1 << "(func, ";
|
ss << "v" << i-1 << ") NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE" << i-1 << "(func, ";
|
||||||
for (int j = 2 ; j < i-1 ; j++)
|
for (int j = 2 ; j < i-1 ; j++)
|
||||||
ss << "v" << j << ", ";
|
ss << "v" << j << ", ";
|
||||||
ss << "v" << i-1 << ")" << endl;
|
ss << "v" << i-1 << ")" << endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
cout << ss.str() << endl;
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
// Builds the NLOHMANN_JSON_DOUBLE_PASTE dispatch table and recursive
|
||||||
|
// definitions used by the *_WITH_NAMES macros. Its GET_MACRO dispatch reuses
|
||||||
|
// the same max_args slots as NLOHMANN_JSON_PASTE, but DOUBLE_PASTE consumes
|
||||||
|
// its arguments two at a time (name, member), so an even slot count falls
|
||||||
|
// back to the next lower odd NLOHMANN_JSON_DOUBLE_PASTE<N>.
|
||||||
|
string build_double_paste_code(int max_args)
|
||||||
|
{
|
||||||
|
stringstream ss;
|
||||||
|
ss << "#define NLOHMANN_JSON_DOUBLE_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \\" << endl;
|
||||||
|
for (int i = max_args ; i > 1 ; i--)
|
||||||
|
{
|
||||||
|
int k = (i % 2 == 1) ? i : i - 1;
|
||||||
|
ss << "NLOHMANN_JSON_DOUBLE_PASTE" << k << ", \\" << endl;
|
||||||
|
}
|
||||||
|
ss << "NLOHMANN_JSON_DOUBLE_PASTE1)(__VA_ARGS__))" << endl;
|
||||||
|
|
||||||
|
ss << "#define NLOHMANN_JSON_DOUBLE_PASTE3(func, v1, v2) func(v1, v2)" << endl;
|
||||||
|
for (int k = 5 ; k <= max_args - 1 ; k += 2)
|
||||||
|
{
|
||||||
|
ss << "#define NLOHMANN_JSON_DOUBLE_PASTE" << k << "(func, ";
|
||||||
|
for (int j = 1 ; j < k - 1 ; j++)
|
||||||
|
ss << "v" << j << ", ";
|
||||||
|
ss << "v" << k - 1 << ") NLOHMANN_JSON_DOUBLE_PASTE3(func, v1, v2) NLOHMANN_JSON_DOUBLE_PASTE" << k - 2 << "(func, ";
|
||||||
|
for (int j = 3 ; j < k - 1 ; j++)
|
||||||
|
ss << "v" << j << ", ";
|
||||||
|
ss << "v" << k - 1 << ")" << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builds the NLOHMANN_JSON_TYPE_BODY dispatch table: max_args - 1 slots
|
||||||
|
// selecting the *_MEMBERS implementation and a final slot selecting
|
||||||
|
// *_EMPTY, so NLOHMANN_DEFINE_TYPE_*(Type) with no further arguments still
|
||||||
|
// resolves (issue #4041).
|
||||||
|
string build_type_body_table(int max_args)
|
||||||
|
{
|
||||||
|
stringstream ss;
|
||||||
|
ss << "#define NLOHMANN_JSON_TYPE_BODY(Prefix, ...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \\" << endl;
|
||||||
|
const int per_line = 8;
|
||||||
|
for (int i = 1 ; i <= max_args ; i++)
|
||||||
|
{
|
||||||
|
ss << (i == max_args ? "Prefix ## EMPTY" : "Prefix ## MEMBERS") << ", ";
|
||||||
|
if (i % per_line == 0)
|
||||||
|
ss << "\\" << endl;
|
||||||
|
}
|
||||||
|
ss << "NLOHMANN_JSON_TYPE_BODY_SENTINEL))" << endl;
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
int max_args = 64;
|
int max_args = 64;
|
||||||
build_code(max_args);
|
|
||||||
|
// With "type_body", print only the NLOHMANN_JSON_TYPE_BODY dispatch
|
||||||
|
// table (a separate insertion point in macro_scope.hpp); otherwise
|
||||||
|
// print the EXPAND/GET_MACRO/PASTE/DOUBLE_PASTE block that precedes it.
|
||||||
|
if (argc > 1 && string(argv[1]) == "type_body")
|
||||||
|
{
|
||||||
|
cout << build_type_body_table(max_args);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cout << build_paste_code(max_args) << build_double_paste_code(max_args);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
import socket
|
||||||
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
@@ -34,7 +36,7 @@ JSON_VERSION_RE = re.compile(r'\s*#\s*define\s+NLOHMANN_JSON_VERSION_MAJOR\s+')
|
|||||||
|
|
||||||
class ExitHandler(logging.StreamHandler):
|
class ExitHandler(logging.StreamHandler):
|
||||||
def __init__(self, level):
|
def __init__(self, level):
|
||||||
"""."""
|
"""Exit the process on log records at or above level."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.level = level
|
self.level = level
|
||||||
|
|
||||||
@@ -54,7 +56,7 @@ def is_project_root(test_dir='.'):
|
|||||||
|
|
||||||
class DirectoryEventBucket:
|
class DirectoryEventBucket:
|
||||||
def __init__(self, callback, delay=1.2, threshold=0.8):
|
def __init__(self, callback, delay=1.2, threshold=0.8):
|
||||||
"""."""
|
"""Batch directory events and pass their common path to callback."""
|
||||||
self.delay = delay
|
self.delay = delay
|
||||||
self.threshold = timedelta(seconds=threshold)
|
self.threshold = timedelta(seconds=threshold)
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
@@ -99,7 +101,7 @@ class WorkTree:
|
|||||||
make_command = 'make'
|
make_command = 'make'
|
||||||
|
|
||||||
def __init__(self, root_dir, tree_dir):
|
def __init__(self, root_dir, tree_dir):
|
||||||
"""."""
|
"""Track the working tree at tree_dir and its amalgamated header."""
|
||||||
self.root_dir = root_dir
|
self.root_dir = root_dir
|
||||||
self.tree_dir = tree_dir
|
self.tree_dir = tree_dir
|
||||||
self.rel_dir = os.path.relpath(tree_dir, root_dir)
|
self.rel_dir = os.path.relpath(tree_dir, root_dir)
|
||||||
@@ -114,11 +116,11 @@ class WorkTree:
|
|||||||
self.build_time = t.strftime(DATETIME_FORMAT)
|
self.build_time = t.strftime(DATETIME_FORMAT)
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
"""."""
|
"""Hash by working tree directory."""
|
||||||
return hash((self.tree_dir))
|
return hash((self.tree_dir))
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
"""."""
|
"""Compare by working tree directory."""
|
||||||
if not isinstance(other, type(self)):
|
if not isinstance(other, type(self)):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return self.tree_dir == other.tree_dir
|
return self.tree_dir == other.tree_dir
|
||||||
@@ -150,7 +152,7 @@ class WorkTree:
|
|||||||
|
|
||||||
class WorkTrees(FileSystemEventHandler):
|
class WorkTrees(FileSystemEventHandler):
|
||||||
def __init__(self, root_dir):
|
def __init__(self, root_dir):
|
||||||
"""."""
|
"""Find the working trees below root_dir and watch it for changes."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.root_dir = root_dir
|
self.root_dir = root_dir
|
||||||
self.trees = set([])
|
self.trees = set([])
|
||||||
@@ -250,11 +252,11 @@ class WorkTrees(FileSystemEventHandler):
|
|||||||
self.observer.stop()
|
self.observer.stop()
|
||||||
self.observer.join()
|
self.observer.join()
|
||||||
|
|
||||||
class HeaderRequestHandler(SimpleHTTPRequestHandler): # lgtm[py/missing-call-to-init]
|
class HeaderRequestHandler(SimpleHTTPRequestHandler):
|
||||||
cors_origins = DEFAULT_CORS_ORIGINS
|
cors_origins = DEFAULT_CORS_ORIGINS
|
||||||
|
|
||||||
def __init__(self, request, client_address, server):
|
def __init__(self, request, client_address, server):
|
||||||
"""."""
|
"""Handle a request for a header below the working trees' root directory."""
|
||||||
self.worktrees = server.worktrees
|
self.worktrees = server.worktrees
|
||||||
self.worktree = None
|
self.worktree = None
|
||||||
try:
|
try:
|
||||||
@@ -336,7 +338,7 @@ class HeaderRequestHandler(SimpleHTTPRequestHandler): # lgtm[py/missing-call-to-
|
|||||||
|
|
||||||
class DualStackServer(ThreadingHTTPServer):
|
class DualStackServer(ThreadingHTTPServer):
|
||||||
def __init__(self, addr, worktrees):
|
def __init__(self, addr, worktrees):
|
||||||
"""."""
|
"""Serve the headers of worktrees on addr."""
|
||||||
self.worktrees = worktrees
|
self.worktrees = worktrees
|
||||||
super().__init__(addr, HeaderRequestHandler)
|
super().__init__(addr, HeaderRequestHandler)
|
||||||
|
|
||||||
@@ -349,8 +351,6 @@ class DualStackServer(ThreadingHTTPServer):
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import argparse
|
import argparse
|
||||||
import ssl
|
|
||||||
import socket
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
# exit code
|
# exit code
|
||||||
|
|||||||
Reference in New Issue
Block a user