mirror of
https://github.com/nlohmann/json.git
synced 2026-09-29 19:20:30 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6c115a9a6 | ||
|
|
33ef25099d |
@@ -100,7 +100,7 @@ jobs:
|
||||
container: ubuntu:focal
|
||||
strategy:
|
||||
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_disabletuplereferenceconversion, 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_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_no_thread_local]
|
||||
steps:
|
||||
- name: Install build-essential
|
||||
run: apt-get update ; apt-get install -y build-essential unzip wget git libssl-dev
|
||||
|
||||
@@ -55,7 +55,6 @@ option(JSON_Diagnostic_Positions "Enable diagnostic positions." OFF)
|
||||
option(JSON_GlobalUDLs "Place user-defined string literals in the global namespace." ON)
|
||||
option(JSON_ImplicitConversions "Enable implicit conversions." ON)
|
||||
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_Install "Install CMake targets during install step." ${MAIN_PROJECT})
|
||||
option(JSON_MultipleHeaders "Use non-amalgamated version of the library." ON)
|
||||
@@ -102,10 +101,6 @@ if (JSON_DisableEnumSerialization)
|
||||
message(STATUS "Enum integer serialization is disabled (JSON_DISABLE_ENUM_SERIALIZATION=1)")
|
||||
endif()
|
||||
|
||||
if (JSON_DisableTupleReferenceConversion)
|
||||
message(STATUS "Tuple reference conversion is disabled (JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1)")
|
||||
endif()
|
||||
|
||||
if (JSON_LegacyDiscardedValueComparison)
|
||||
message(STATUS "Legacy discarded value comparison enabled (JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1)")
|
||||
endif()
|
||||
@@ -148,7 +143,6 @@ target_compile_definitions(
|
||||
$<$<NOT:$<BOOL:${JSON_GlobalUDLs}>>:JSON_USE_GLOBAL_UDLS=0>
|
||||
$<$<NOT:$<BOOL:${JSON_ImplicitConversions}>>:JSON_USE_IMPLICIT_CONVERSIONS=0>
|
||||
$<$<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_Diagnostic_Positions}>:JSON_DIAGNOSTIC_POSITIONS=1>
|
||||
$<$<BOOL:${JSON_LegacyDiscardedValueComparison}>:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1>
|
||||
|
||||
@@ -276,20 +276,6 @@ add_custom_target(ci_test_disableenumserialization
|
||||
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.
|
||||
###############################################################################
|
||||
|
||||
@@ -210,7 +210,6 @@ 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_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_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_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');
|
||||
|
||||
@@ -159,8 +159,6 @@ basic_json(basic_json&& other) noexcept;
|
||||
- `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 `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&&)`
|
||||
method
|
||||
|
||||
|
||||
@@ -52,7 +52,6 @@ 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_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
|
||||
|
||||
## Comparison behavior
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
# 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.
|
||||
@@ -83,13 +83,6 @@ 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).
|
||||
|
||||
## `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_IO`
|
||||
|
||||
When defined, headers `<cstdio>`, `<ios>`, `<iosfwd>`, `<istream>`, and `<ostream>` are not included and parse functions
|
||||
|
||||
@@ -169,12 +169,6 @@ Enable position diagnostics by defining macro [`JSON_DIAGNOSTIC_POSITIONS`](../a
|
||||
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_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`
|
||||
|
||||
Skip expensive/slow test suites. This option is `OFF` by default. Depends on `JSON_BuildTests`.
|
||||
|
||||
@@ -287,7 +287,6 @@ nav:
|
||||
- 'JSON_DIAGNOSTICS': api/macros/json_diagnostics.md
|
||||
- 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.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_EXPERIMENTAL_FILESYSTEM, JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.md
|
||||
- 'JSON_HAS_RANGES': api/macros/json_has_ranges.md
|
||||
|
||||
@@ -471,13 +471,11 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
|
||||
j = { std::get<Idx>(t)... };
|
||||
}
|
||||
|
||||
// A one-element braced list does not reliably wrap its element: with
|
||||
// JSON_BRACE_INIT_COPY_SEMANTICS it copies it, which would serialize
|
||||
// std::tuple<int>{5} as 5 rather than [5], and some compilers (e.g., Apple clang
|
||||
// 15 and 16) copy an element that is itself a basic_json even without it, so
|
||||
// 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.
|
||||
#if JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
// JSON_BRACE_INIT_COPY_SEMANTICS makes a one-element braced list copy its
|
||||
// element instead of wrapping it, which would serialize std::tuple<int>{5} as 5
|
||||
// rather than [5]. 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>
|
||||
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<0> /*unused*/)
|
||||
{
|
||||
@@ -495,6 +493,7 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
|
||||
j = BasicJsonType::array({std::move(element)});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename BasicJsonType, typename Tuple>
|
||||
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/)
|
||||
|
||||
@@ -9,10 +9,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <array> // array
|
||||
#include <clocale> // localeconv
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdio> // snprintf
|
||||
#include <cstdlib> // strtof, strtod, strtold, strtoll, strtoull
|
||||
#include <initializer_list> // initializer_list
|
||||
#include <string> // char_traits, string
|
||||
#include <utility> // move
|
||||
@@ -217,18 +215,6 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
~lexer() = default;
|
||||
|
||||
private:
|
||||
/////////////////////
|
||||
// locales
|
||||
/////////////////////
|
||||
|
||||
/// return the decimal point of the current locale
|
||||
static char get_decimal_point() noexcept
|
||||
{
|
||||
const auto* loc = localeconv();
|
||||
JSON_ASSERT(loc != nullptr);
|
||||
return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point);
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// scan functions
|
||||
/////////////////////
|
||||
@@ -1036,24 +1022,6 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
}
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
static void strtof(float& f, const char* str, char** endptr) noexcept
|
||||
{
|
||||
f = std::strtof(str, endptr);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
static void strtof(double& f, const char* str, char** endptr) noexcept
|
||||
{
|
||||
f = std::strtod(str, endptr);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
static void strtof(long double& f, const char* str, char** endptr) noexcept
|
||||
{
|
||||
f = std::strtold(str, endptr);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief scan a number literal
|
||||
|
||||
@@ -1093,7 +1061,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
@note The scanner is independent of the current locale: token_buffer
|
||||
always holds `.`. Only the std::strtod fallback of convert_number()
|
||||
depends on the locale, and it looks up the decimal point right
|
||||
before converting (see convert_float_locale_aware()).
|
||||
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.
|
||||
{
|
||||
@@ -1424,59 +1392,6 @@ scan_number_done:
|
||||
return token_type::uninitialized;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief check whether Clinger's fast path can still succeed for this token
|
||||
|
||||
parse_float_fast() needs a significand below 2^53. A mantissa with 17 or
|
||||
more significant digits is at least 10^16 and therefore always exceeds it,
|
||||
so calling the fast path would walk the token one extra time only to
|
||||
decline before strtod has to run anyway.
|
||||
|
||||
Significant digits are the mantissa's digits from the first nonzero one on;
|
||||
the sign, the decimal point, leading zeros, and the exponent do not count.
|
||||
The answer is derived from indices - the digits are not scanned again - so
|
||||
this stays off the hot path of the number scanners.
|
||||
|
||||
@param[in] mantissa_end offset just past the last mantissa byte in
|
||||
token_buffer
|
||||
@return false if parse_float_fast() is guaranteed to decline
|
||||
*/
|
||||
bool mantissa_fits_clinger(std::size_t mantissa_end) const
|
||||
{
|
||||
// 10^16 already exceeds 2^53, so 17 digits can never fit
|
||||
constexpr std::size_t limit = 17;
|
||||
|
||||
const std::size_t neg = (!token_buffer.empty() && token_buffer[0] == '-') ? 1u : 0u;
|
||||
const std::size_t has_dot = (decimal_point_position != std::string::npos) ? 1u : 0u;
|
||||
// the JSON grammar restricts the integer part to "0" or [1-9][0-9]*, so
|
||||
// a leading zero can only be a lone "0", which is not significant
|
||||
const std::size_t lead_zero = (token_buffer[neg] == '0') ? 1u : 0u;
|
||||
JSON_ASSERT(mantissa_end >= neg + has_dot + lead_zero);
|
||||
std::size_t digits = mantissa_end - neg - has_dot - lead_zero;
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(digits < limit))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only a number below 1 can carry further insignificant zeros, and only
|
||||
// while the count stays at the limit does removing them change the
|
||||
// answer - so this loop is skipped for all but a few tokens. The
|
||||
// fraction is located through decimal_point_position rather than by
|
||||
// searching '.'.
|
||||
if (lead_zero != 0)
|
||||
{
|
||||
JSON_ASSERT(has_dot != 0); // an integer "0" cannot reach the limit
|
||||
for (std::size_t i = decimal_point_position + 1;
|
||||
digits >= limit && i < mantissa_end && token_buffer[i] == '0'; ++i)
|
||||
{
|
||||
--digits;
|
||||
}
|
||||
}
|
||||
|
||||
return digits < limit;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert the number text in token_buffer to its value and token type
|
||||
|
||||
@@ -1490,7 +1405,7 @@ scan_number_done:
|
||||
token_buffer (the index of 'e'/'E', or
|
||||
token_buffer.size() when there is no exponent);
|
||||
used to skip Clinger's fast path when it cannot
|
||||
possibly succeed - see mantissa_fits_clinger()
|
||||
possibly succeed - see detail::mantissa_fits_clinger()
|
||||
*/
|
||||
token_type convert_number(token_type number_type, std::size_t mantissa_end)
|
||||
{
|
||||
@@ -1563,77 +1478,15 @@ scan_number_done:
|
||||
// (Eisel-Lemire, locale-independent, correctly rounded) when available;
|
||||
// otherwise the exact Clinger fast path (double only); otherwise the
|
||||
// locale-aware strtof/strtod/strtold.
|
||||
if (parse_float_from_chars(num_begin, num_end, value_float))
|
||||
{
|
||||
return token_type::value_float;
|
||||
}
|
||||
// Skipping a fast path that cannot succeed is lossless and saves a full
|
||||
// extra pass over the token's bytes, which otherwise shows up on
|
||||
// high-precision inputs such as canada.json
|
||||
if (mantissa_fits_clinger(mantissa_end)
|
||||
&& parse_float_fast(num_begin, num_end, value_float))
|
||||
if (convert_float_fast(num_begin, num_end, decimal_point_position, mantissa_end, value_float))
|
||||
{
|
||||
return token_type::value_float;
|
||||
}
|
||||
|
||||
convert_float_locale_aware();
|
||||
convert_float_locale_aware(token_buffer, decimal_point_position, value_float);
|
||||
return token_type::value_float;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert the float in token_buffer with strtof/strtod/strtold
|
||||
|
||||
These functions expect the decimal point of the *current* locale, so it is
|
||||
looked up right before the conversion instead of once when the lexer is
|
||||
constructed: a locale change in between (by a parser callback, a SAX
|
||||
handler, or another thread) must not truncate the value (#5198). The
|
||||
token has been validated before, so if the conversion stops early and the
|
||||
decimal point changed in the meantime, the locale changed between the
|
||||
lookup and the call, and the conversion is repeated with the new decimal
|
||||
point. If the decimal point did not change, a retry cannot succeed: the
|
||||
locale's decimal point is not a single character (e.g., the two-byte
|
||||
U+066B of ar_EG.UTF-8 or fa_IR.UTF-8) and cannot be substituted in place.
|
||||
The value strtod parsed up to that point is kept, as before this change.
|
||||
|
||||
Note that changing the locale in another thread *while* strtod runs is
|
||||
undefined behavior of the C library, which this function cannot prevent.
|
||||
*/
|
||||
void convert_float_locale_aware()
|
||||
{
|
||||
const bool has_dot = decimal_point_position != std::string::npos;
|
||||
char decimal_point = get_decimal_point();
|
||||
for (;;)
|
||||
{
|
||||
const bool substitute = has_dot && decimal_point != '.';
|
||||
if (substitute)
|
||||
{
|
||||
token_buffer[decimal_point_position] = static_cast<typename string_t::value_type>(decimal_point);
|
||||
}
|
||||
|
||||
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
strtof(value_float, token_buffer.data(), &endptr);
|
||||
|
||||
if (substitute)
|
||||
{
|
||||
// get_string() hands the token to the SAX interface with '.'
|
||||
token_buffer[decimal_point_position] = '.';
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(endptr == token_buffer.data() + token_buffer.size()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// retry only if the locale changed; otherwise, this would loop forever
|
||||
const char current_decimal_point = get_decimal_point();
|
||||
if (current_decimal_point == decimal_point)
|
||||
{
|
||||
return;
|
||||
}
|
||||
decimal_point = current_decimal_point;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief contiguous fast path for scanning a number
|
||||
|
||||
|
||||
@@ -10,9 +10,12 @@
|
||||
|
||||
#include <array> // array
|
||||
#include <cfloat> // FLT_EVAL_METHOD
|
||||
#include <clocale> // localeconv
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int64_t, uint64_t
|
||||
#include <cstdlib> // strtof, strtod, strtold
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
@@ -29,8 +32,9 @@
|
||||
|
||||
// This file contains the value-conversion helpers used by the lexer to turn an
|
||||
// already-validated number token into a value, without the locale/errno
|
||||
// overhead of std::strtoull/std::strtod. They are free functions so the lexer
|
||||
// stays focused on scanning; see lexer::convert_number().
|
||||
// overhead of std::strtoull/std::strtod where possible. They are free functions
|
||||
// so the lexer stays focused on scanning (see lexer::convert_number()) and so
|
||||
// that other parsers of JSON text can convert tokens exactly like it does.
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -293,5 +297,183 @@ bool parse_float_from_chars(const char* first, const char* last, FloatType& out)
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief check whether Clinger's fast path can still succeed for a float token
|
||||
|
||||
parse_float_fast() needs a significand below 2^53. A mantissa with 17 or
|
||||
more significant digits is at least 10^16 and therefore always exceeds it,
|
||||
so calling the fast path would walk the token one extra time only to
|
||||
decline before strtod has to run anyway.
|
||||
|
||||
Significant digits are the mantissa's digits from the first nonzero one on;
|
||||
the sign, the decimal point, leading zeros, and the exponent do not count.
|
||||
The answer is derived from indices - the digits are not scanned again - so
|
||||
this stays off the hot path of the number scanners.
|
||||
|
||||
@param[in] token the validated number token ('.' as decimal point)
|
||||
@param[in] decimal_point_position index of the '.' in @a token, or
|
||||
std::string::npos if there is none
|
||||
@param[in] mantissa_end offset just past the last mantissa byte
|
||||
@return false if parse_float_fast() is guaranteed to decline
|
||||
*/
|
||||
inline bool mantissa_fits_clinger(const char* token, std::size_t decimal_point_position, std::size_t mantissa_end) noexcept
|
||||
{
|
||||
// 10^16 already exceeds 2^53, so 17 digits can never fit
|
||||
constexpr std::size_t limit = 17;
|
||||
|
||||
const std::size_t neg = (token[0] == '-') ? 1u : 0u;
|
||||
const std::size_t has_dot = (decimal_point_position != std::string::npos) ? 1u : 0u;
|
||||
// the JSON grammar restricts the integer part to "0" or [1-9][0-9]*, so
|
||||
// a leading zero can only be a lone "0", which is not significant
|
||||
const std::size_t lead_zero = (token[neg] == '0') ? 1u : 0u;
|
||||
JSON_ASSERT(mantissa_end >= neg + has_dot + lead_zero);
|
||||
std::size_t digits = mantissa_end - neg - has_dot - lead_zero;
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(digits < limit))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only a number below 1 can carry further insignificant zeros, and only
|
||||
// while the count stays at the limit does removing them change the
|
||||
// answer - so this loop is skipped for all but a few tokens. The
|
||||
// fraction is located through decimal_point_position rather than by
|
||||
// searching '.'.
|
||||
if (lead_zero != 0)
|
||||
{
|
||||
JSON_ASSERT(has_dot != 0); // an integer "0" cannot reach the limit
|
||||
for (std::size_t i = decimal_point_position + 1;
|
||||
digits >= limit && i < mantissa_end && token[i] == '0'; ++i)
|
||||
{
|
||||
--digits;
|
||||
}
|
||||
}
|
||||
|
||||
return digits < limit;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert a validated float token without the C library, if possible
|
||||
|
||||
Tries std::from_chars (when available) and then Clinger's exact fast path
|
||||
(double only), skipping the latter when it cannot succeed.
|
||||
|
||||
@param[in] first pointer to the first character of the token
|
||||
@param[in] last pointer past the last character
|
||||
@param[in] decimal_point_position index of the '.' in the token, or
|
||||
std::string::npos if there is none
|
||||
@param[in] mantissa_end offset just past the last mantissa byte (the
|
||||
index of 'e'/'E', or the token length)
|
||||
@param[out] value the converted value on success
|
||||
@return true if the value was converted; false if convert_float_locale_aware()
|
||||
must convert it
|
||||
*/
|
||||
template<typename FloatType>
|
||||
bool convert_float_fast(const char* first, const char* last, std::size_t decimal_point_position,
|
||||
std::size_t mantissa_end, FloatType& value) noexcept
|
||||
{
|
||||
if (parse_float_from_chars(first, last, value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// Skipping a fast path that cannot succeed is lossless and saves a full
|
||||
// extra pass over the token's bytes, which otherwise shows up on
|
||||
// high-precision inputs such as canada.json
|
||||
return mantissa_fits_clinger(first, decimal_point_position, mantissa_end)
|
||||
&& parse_float_fast(first, last, value);
|
||||
}
|
||||
|
||||
/// std::strtof, std::strtod, or std::strtold, chosen by the type of @a f
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
inline void strtof_by_type(float& f, const char* str, char** endptr) noexcept
|
||||
{
|
||||
f = std::strtof(str, endptr);
|
||||
}
|
||||
|
||||
/// std::strtof, std::strtod, or std::strtold, chosen by the type of @a f
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
inline void strtof_by_type(double& f, const char* str, char** endptr) noexcept
|
||||
{
|
||||
f = std::strtod(str, endptr);
|
||||
}
|
||||
|
||||
/// std::strtof, std::strtod, or std::strtold, chosen by the type of @a f
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
inline void strtof_by_type(long double& f, const char* str, char** endptr) noexcept
|
||||
{
|
||||
f = std::strtold(str, endptr);
|
||||
}
|
||||
|
||||
/// return the decimal point of the current locale
|
||||
inline char get_decimal_point() noexcept
|
||||
{
|
||||
const auto* loc = localeconv();
|
||||
JSON_ASSERT(loc != nullptr);
|
||||
return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert a validated float token with strtof/strtod/strtold
|
||||
|
||||
These functions expect the decimal point of the *current* locale, so it is
|
||||
looked up right before the conversion instead of once when the lexer is
|
||||
constructed: a locale change in between (by a parser callback, a SAX
|
||||
handler, or another thread) must not truncate the value (#5198). The
|
||||
token has been validated before, so if the conversion stops early and the
|
||||
decimal point changed in the meantime, the locale changed between the
|
||||
lookup and the call, and the conversion is repeated with the new decimal
|
||||
point. If the decimal point did not change, a retry cannot succeed: the
|
||||
locale's decimal point is not a single character (e.g., the two-byte
|
||||
U+066B of ar_EG.UTF-8 or fa_IR.UTF-8) and cannot be substituted in place.
|
||||
The value strtod parsed up to that point is kept, as before this change.
|
||||
|
||||
Note that changing the locale in another thread *while* strtod runs is
|
||||
undefined behavior of the C library, which this function cannot prevent.
|
||||
|
||||
@param[in,out] token the token with '.' as decimal point; its
|
||||
decimal point is replaced during the
|
||||
conversion and restored afterwards
|
||||
(data() must be NUL-terminated)
|
||||
@param[in] decimal_point_position index of the '.' in @a token, or
|
||||
std::string::npos if there is none
|
||||
@param[out] value the converted value
|
||||
*/
|
||||
template<typename StringType, typename FloatType>
|
||||
void convert_float_locale_aware(StringType& token, std::size_t decimal_point_position, FloatType& value)
|
||||
{
|
||||
const bool has_dot = decimal_point_position != std::string::npos;
|
||||
char decimal_point = get_decimal_point();
|
||||
for (;;)
|
||||
{
|
||||
const bool substitute = has_dot && decimal_point != '.';
|
||||
if (substitute)
|
||||
{
|
||||
token[decimal_point_position] = static_cast<typename StringType::value_type>(decimal_point);
|
||||
}
|
||||
|
||||
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
strtof_by_type(value, token.data(), &endptr);
|
||||
|
||||
if (substitute)
|
||||
{
|
||||
// the caller hands the token on (e.g. to the SAX interface) with '.'
|
||||
token[decimal_point_position] = '.';
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(endptr == token.data() + token.size()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// retry only if the locale changed; otherwise, this would loop forever
|
||||
const char current_decimal_point = get_decimal_point();
|
||||
if (current_decimal_point == decimal_point)
|
||||
{
|
||||
return;
|
||||
}
|
||||
decimal_point = current_decimal_point;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
@@ -916,10 +916,6 @@ void templated_json_throw(ExceptionType exception)
|
||||
#define JSON_DISABLE_ENUM_SERIALIZATION 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_USE_GLOBAL_UDLS
|
||||
#define JSON_USE_GLOBAL_UDLS 1
|
||||
#endif
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#undef JSON_INLINE_VARIABLE
|
||||
#undef JSON_NO_UNIQUE_ADDRESS
|
||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#undef JSON_USE_GLOBAL_UDLS
|
||||
|
||||
#ifndef JSON_TEST_KEEP_MACROS
|
||||
|
||||
@@ -636,18 +636,6 @@ template<typename BasicJsonType, typename CompatibleType>
|
||||
struct is_compatible_type
|
||||
: 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>
|
||||
struct is_compatible_binary_type
|
||||
{
|
||||
|
||||
@@ -1595,12 +1595,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
template < typename CompatibleType,
|
||||
typename U = detail::uncvref_t<CompatibleType>,
|
||||
detail::enable_if_t <
|
||||
!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 >
|
||||
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 >
|
||||
basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
|
||||
JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
|
||||
std::forward<CompatibleType>(val))))
|
||||
|
||||
+195
-183
@@ -3327,10 +3327,6 @@ void templated_json_throw(ExceptionType exception)
|
||||
#define JSON_DISABLE_ENUM_SERIALIZATION 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_USE_GLOBAL_UDLS
|
||||
#define JSON_USE_GLOBAL_UDLS 1
|
||||
#endif
|
||||
@@ -4650,18 +4646,6 @@ template<typename BasicJsonType, typename CompatibleType>
|
||||
struct is_compatible_type
|
||||
: 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>
|
||||
struct is_compatible_binary_type
|
||||
{
|
||||
@@ -7016,13 +7000,11 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
|
||||
j = { std::get<Idx>(t)... };
|
||||
}
|
||||
|
||||
// A one-element braced list does not reliably wrap its element: with
|
||||
// JSON_BRACE_INIT_COPY_SEMANTICS it copies it, which would serialize
|
||||
// std::tuple<int>{5} as 5 rather than [5], and some compilers (e.g., Apple clang
|
||||
// 15 and 16) copy an element that is itself a basic_json even without it, so
|
||||
// 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.
|
||||
#if JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
// JSON_BRACE_INIT_COPY_SEMANTICS makes a one-element braced list copy its
|
||||
// element instead of wrapping it, which would serialize std::tuple<int>{5} as 5
|
||||
// rather than [5]. 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>
|
||||
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<0> /*unused*/)
|
||||
{
|
||||
@@ -7040,6 +7022,7 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
|
||||
j = BasicJsonType::array({std::move(element)});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename BasicJsonType, typename Tuple>
|
||||
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/)
|
||||
@@ -8489,10 +8472,8 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
|
||||
#include <array> // array
|
||||
#include <clocale> // localeconv
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdio> // snprintf
|
||||
#include <cstdlib> // strtof, strtod, strtold, strtoll, strtoull
|
||||
#include <initializer_list> // initializer_list
|
||||
#include <string> // char_traits, string
|
||||
#include <utility> // move
|
||||
@@ -8513,9 +8494,12 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
#include <array> // array
|
||||
#include <cfloat> // FLT_EVAL_METHOD
|
||||
#include <clocale> // localeconv
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int64_t, uint64_t
|
||||
#include <cstdlib> // strtof, strtod, strtold
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
|
||||
// #include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
@@ -8533,8 +8517,9 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
// This file contains the value-conversion helpers used by the lexer to turn an
|
||||
// already-validated number token into a value, without the locale/errno
|
||||
// overhead of std::strtoull/std::strtod. They are free functions so the lexer
|
||||
// stays focused on scanning; see lexer::convert_number().
|
||||
// overhead of std::strtoull/std::strtod where possible. They are free functions
|
||||
// so the lexer stays focused on scanning (see lexer::convert_number()) and so
|
||||
// that other parsers of JSON text can convert tokens exactly like it does.
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -8797,6 +8782,184 @@ bool parse_float_from_chars(const char* first, const char* last, FloatType& out)
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief check whether Clinger's fast path can still succeed for a float token
|
||||
|
||||
parse_float_fast() needs a significand below 2^53. A mantissa with 17 or
|
||||
more significant digits is at least 10^16 and therefore always exceeds it,
|
||||
so calling the fast path would walk the token one extra time only to
|
||||
decline before strtod has to run anyway.
|
||||
|
||||
Significant digits are the mantissa's digits from the first nonzero one on;
|
||||
the sign, the decimal point, leading zeros, and the exponent do not count.
|
||||
The answer is derived from indices - the digits are not scanned again - so
|
||||
this stays off the hot path of the number scanners.
|
||||
|
||||
@param[in] token the validated number token ('.' as decimal point)
|
||||
@param[in] decimal_point_position index of the '.' in @a token, or
|
||||
std::string::npos if there is none
|
||||
@param[in] mantissa_end offset just past the last mantissa byte
|
||||
@return false if parse_float_fast() is guaranteed to decline
|
||||
*/
|
||||
inline bool mantissa_fits_clinger(const char* token, std::size_t decimal_point_position, std::size_t mantissa_end) noexcept
|
||||
{
|
||||
// 10^16 already exceeds 2^53, so 17 digits can never fit
|
||||
constexpr std::size_t limit = 17;
|
||||
|
||||
const std::size_t neg = (token[0] == '-') ? 1u : 0u;
|
||||
const std::size_t has_dot = (decimal_point_position != std::string::npos) ? 1u : 0u;
|
||||
// the JSON grammar restricts the integer part to "0" or [1-9][0-9]*, so
|
||||
// a leading zero can only be a lone "0", which is not significant
|
||||
const std::size_t lead_zero = (token[neg] == '0') ? 1u : 0u;
|
||||
JSON_ASSERT(mantissa_end >= neg + has_dot + lead_zero);
|
||||
std::size_t digits = mantissa_end - neg - has_dot - lead_zero;
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(digits < limit))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only a number below 1 can carry further insignificant zeros, and only
|
||||
// while the count stays at the limit does removing them change the
|
||||
// answer - so this loop is skipped for all but a few tokens. The
|
||||
// fraction is located through decimal_point_position rather than by
|
||||
// searching '.'.
|
||||
if (lead_zero != 0)
|
||||
{
|
||||
JSON_ASSERT(has_dot != 0); // an integer "0" cannot reach the limit
|
||||
for (std::size_t i = decimal_point_position + 1;
|
||||
digits >= limit && i < mantissa_end && token[i] == '0'; ++i)
|
||||
{
|
||||
--digits;
|
||||
}
|
||||
}
|
||||
|
||||
return digits < limit;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert a validated float token without the C library, if possible
|
||||
|
||||
Tries std::from_chars (when available) and then Clinger's exact fast path
|
||||
(double only), skipping the latter when it cannot succeed.
|
||||
|
||||
@param[in] first pointer to the first character of the token
|
||||
@param[in] last pointer past the last character
|
||||
@param[in] decimal_point_position index of the '.' in the token, or
|
||||
std::string::npos if there is none
|
||||
@param[in] mantissa_end offset just past the last mantissa byte (the
|
||||
index of 'e'/'E', or the token length)
|
||||
@param[out] value the converted value on success
|
||||
@return true if the value was converted; false if convert_float_locale_aware()
|
||||
must convert it
|
||||
*/
|
||||
template<typename FloatType>
|
||||
bool convert_float_fast(const char* first, const char* last, std::size_t decimal_point_position,
|
||||
std::size_t mantissa_end, FloatType& value) noexcept
|
||||
{
|
||||
if (parse_float_from_chars(first, last, value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// Skipping a fast path that cannot succeed is lossless and saves a full
|
||||
// extra pass over the token's bytes, which otherwise shows up on
|
||||
// high-precision inputs such as canada.json
|
||||
return mantissa_fits_clinger(first, decimal_point_position, mantissa_end)
|
||||
&& parse_float_fast(first, last, value);
|
||||
}
|
||||
|
||||
/// std::strtof, std::strtod, or std::strtold, chosen by the type of @a f
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
inline void strtof_by_type(float& f, const char* str, char** endptr) noexcept
|
||||
{
|
||||
f = std::strtof(str, endptr);
|
||||
}
|
||||
|
||||
/// std::strtof, std::strtod, or std::strtold, chosen by the type of @a f
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
inline void strtof_by_type(double& f, const char* str, char** endptr) noexcept
|
||||
{
|
||||
f = std::strtod(str, endptr);
|
||||
}
|
||||
|
||||
/// std::strtof, std::strtod, or std::strtold, chosen by the type of @a f
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
inline void strtof_by_type(long double& f, const char* str, char** endptr) noexcept
|
||||
{
|
||||
f = std::strtold(str, endptr);
|
||||
}
|
||||
|
||||
/// return the decimal point of the current locale
|
||||
inline char get_decimal_point() noexcept
|
||||
{
|
||||
const auto* loc = localeconv();
|
||||
JSON_ASSERT(loc != nullptr);
|
||||
return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert a validated float token with strtof/strtod/strtold
|
||||
|
||||
These functions expect the decimal point of the *current* locale, so it is
|
||||
looked up right before the conversion instead of once when the lexer is
|
||||
constructed: a locale change in between (by a parser callback, a SAX
|
||||
handler, or another thread) must not truncate the value (#5198). The
|
||||
token has been validated before, so if the conversion stops early and the
|
||||
decimal point changed in the meantime, the locale changed between the
|
||||
lookup and the call, and the conversion is repeated with the new decimal
|
||||
point. If the decimal point did not change, a retry cannot succeed: the
|
||||
locale's decimal point is not a single character (e.g., the two-byte
|
||||
U+066B of ar_EG.UTF-8 or fa_IR.UTF-8) and cannot be substituted in place.
|
||||
The value strtod parsed up to that point is kept, as before this change.
|
||||
|
||||
Note that changing the locale in another thread *while* strtod runs is
|
||||
undefined behavior of the C library, which this function cannot prevent.
|
||||
|
||||
@param[in,out] token the token with '.' as decimal point; its
|
||||
decimal point is replaced during the
|
||||
conversion and restored afterwards
|
||||
(data() must be NUL-terminated)
|
||||
@param[in] decimal_point_position index of the '.' in @a token, or
|
||||
std::string::npos if there is none
|
||||
@param[out] value the converted value
|
||||
*/
|
||||
template<typename StringType, typename FloatType>
|
||||
void convert_float_locale_aware(StringType& token, std::size_t decimal_point_position, FloatType& value)
|
||||
{
|
||||
const bool has_dot = decimal_point_position != std::string::npos;
|
||||
char decimal_point = get_decimal_point();
|
||||
for (;;)
|
||||
{
|
||||
const bool substitute = has_dot && decimal_point != '.';
|
||||
if (substitute)
|
||||
{
|
||||
token[decimal_point_position] = static_cast<typename StringType::value_type>(decimal_point);
|
||||
}
|
||||
|
||||
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
strtof_by_type(value, token.data(), &endptr);
|
||||
|
||||
if (substitute)
|
||||
{
|
||||
// the caller hands the token on (e.g. to the SAX interface) with '.'
|
||||
token[decimal_point_position] = '.';
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(endptr == token.data() + token.size()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// retry only if the locale changed; otherwise, this would loop forever
|
||||
const char current_decimal_point = get_decimal_point();
|
||||
if (current_decimal_point == decimal_point)
|
||||
{
|
||||
return;
|
||||
}
|
||||
decimal_point = current_decimal_point;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
@@ -9326,18 +9489,6 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
~lexer() = default;
|
||||
|
||||
private:
|
||||
/////////////////////
|
||||
// locales
|
||||
/////////////////////
|
||||
|
||||
/// return the decimal point of the current locale
|
||||
static char get_decimal_point() noexcept
|
||||
{
|
||||
const auto* loc = localeconv();
|
||||
JSON_ASSERT(loc != nullptr);
|
||||
return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point);
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// scan functions
|
||||
/////////////////////
|
||||
@@ -10145,24 +10296,6 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
}
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
static void strtof(float& f, const char* str, char** endptr) noexcept
|
||||
{
|
||||
f = std::strtof(str, endptr);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
static void strtof(double& f, const char* str, char** endptr) noexcept
|
||||
{
|
||||
f = std::strtod(str, endptr);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
static void strtof(long double& f, const char* str, char** endptr) noexcept
|
||||
{
|
||||
f = std::strtold(str, endptr);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief scan a number literal
|
||||
|
||||
@@ -10202,7 +10335,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
@note The scanner is independent of the current locale: token_buffer
|
||||
always holds `.`. Only the std::strtod fallback of convert_number()
|
||||
depends on the locale, and it looks up the decimal point right
|
||||
before converting (see convert_float_locale_aware()).
|
||||
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.
|
||||
{
|
||||
@@ -10533,59 +10666,6 @@ scan_number_done:
|
||||
return token_type::uninitialized;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief check whether Clinger's fast path can still succeed for this token
|
||||
|
||||
parse_float_fast() needs a significand below 2^53. A mantissa with 17 or
|
||||
more significant digits is at least 10^16 and therefore always exceeds it,
|
||||
so calling the fast path would walk the token one extra time only to
|
||||
decline before strtod has to run anyway.
|
||||
|
||||
Significant digits are the mantissa's digits from the first nonzero one on;
|
||||
the sign, the decimal point, leading zeros, and the exponent do not count.
|
||||
The answer is derived from indices - the digits are not scanned again - so
|
||||
this stays off the hot path of the number scanners.
|
||||
|
||||
@param[in] mantissa_end offset just past the last mantissa byte in
|
||||
token_buffer
|
||||
@return false if parse_float_fast() is guaranteed to decline
|
||||
*/
|
||||
bool mantissa_fits_clinger(std::size_t mantissa_end) const
|
||||
{
|
||||
// 10^16 already exceeds 2^53, so 17 digits can never fit
|
||||
constexpr std::size_t limit = 17;
|
||||
|
||||
const std::size_t neg = (!token_buffer.empty() && token_buffer[0] == '-') ? 1u : 0u;
|
||||
const std::size_t has_dot = (decimal_point_position != std::string::npos) ? 1u : 0u;
|
||||
// the JSON grammar restricts the integer part to "0" or [1-9][0-9]*, so
|
||||
// a leading zero can only be a lone "0", which is not significant
|
||||
const std::size_t lead_zero = (token_buffer[neg] == '0') ? 1u : 0u;
|
||||
JSON_ASSERT(mantissa_end >= neg + has_dot + lead_zero);
|
||||
std::size_t digits = mantissa_end - neg - has_dot - lead_zero;
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(digits < limit))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only a number below 1 can carry further insignificant zeros, and only
|
||||
// while the count stays at the limit does removing them change the
|
||||
// answer - so this loop is skipped for all but a few tokens. The
|
||||
// fraction is located through decimal_point_position rather than by
|
||||
// searching '.'.
|
||||
if (lead_zero != 0)
|
||||
{
|
||||
JSON_ASSERT(has_dot != 0); // an integer "0" cannot reach the limit
|
||||
for (std::size_t i = decimal_point_position + 1;
|
||||
digits >= limit && i < mantissa_end && token_buffer[i] == '0'; ++i)
|
||||
{
|
||||
--digits;
|
||||
}
|
||||
}
|
||||
|
||||
return digits < limit;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert the number text in token_buffer to its value and token type
|
||||
|
||||
@@ -10599,7 +10679,7 @@ scan_number_done:
|
||||
token_buffer (the index of 'e'/'E', or
|
||||
token_buffer.size() when there is no exponent);
|
||||
used to skip Clinger's fast path when it cannot
|
||||
possibly succeed - see mantissa_fits_clinger()
|
||||
possibly succeed - see detail::mantissa_fits_clinger()
|
||||
*/
|
||||
token_type convert_number(token_type number_type, std::size_t mantissa_end)
|
||||
{
|
||||
@@ -10672,77 +10752,15 @@ scan_number_done:
|
||||
// (Eisel-Lemire, locale-independent, correctly rounded) when available;
|
||||
// otherwise the exact Clinger fast path (double only); otherwise the
|
||||
// locale-aware strtof/strtod/strtold.
|
||||
if (parse_float_from_chars(num_begin, num_end, value_float))
|
||||
{
|
||||
return token_type::value_float;
|
||||
}
|
||||
// Skipping a fast path that cannot succeed is lossless and saves a full
|
||||
// extra pass over the token's bytes, which otherwise shows up on
|
||||
// high-precision inputs such as canada.json
|
||||
if (mantissa_fits_clinger(mantissa_end)
|
||||
&& parse_float_fast(num_begin, num_end, value_float))
|
||||
if (convert_float_fast(num_begin, num_end, decimal_point_position, mantissa_end, value_float))
|
||||
{
|
||||
return token_type::value_float;
|
||||
}
|
||||
|
||||
convert_float_locale_aware();
|
||||
convert_float_locale_aware(token_buffer, decimal_point_position, value_float);
|
||||
return token_type::value_float;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert the float in token_buffer with strtof/strtod/strtold
|
||||
|
||||
These functions expect the decimal point of the *current* locale, so it is
|
||||
looked up right before the conversion instead of once when the lexer is
|
||||
constructed: a locale change in between (by a parser callback, a SAX
|
||||
handler, or another thread) must not truncate the value (#5198). The
|
||||
token has been validated before, so if the conversion stops early and the
|
||||
decimal point changed in the meantime, the locale changed between the
|
||||
lookup and the call, and the conversion is repeated with the new decimal
|
||||
point. If the decimal point did not change, a retry cannot succeed: the
|
||||
locale's decimal point is not a single character (e.g., the two-byte
|
||||
U+066B of ar_EG.UTF-8 or fa_IR.UTF-8) and cannot be substituted in place.
|
||||
The value strtod parsed up to that point is kept, as before this change.
|
||||
|
||||
Note that changing the locale in another thread *while* strtod runs is
|
||||
undefined behavior of the C library, which this function cannot prevent.
|
||||
*/
|
||||
void convert_float_locale_aware()
|
||||
{
|
||||
const bool has_dot = decimal_point_position != std::string::npos;
|
||||
char decimal_point = get_decimal_point();
|
||||
for (;;)
|
||||
{
|
||||
const bool substitute = has_dot && decimal_point != '.';
|
||||
if (substitute)
|
||||
{
|
||||
token_buffer[decimal_point_position] = static_cast<typename string_t::value_type>(decimal_point);
|
||||
}
|
||||
|
||||
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
strtof(value_float, token_buffer.data(), &endptr);
|
||||
|
||||
if (substitute)
|
||||
{
|
||||
// get_string() hands the token to the SAX interface with '.'
|
||||
token_buffer[decimal_point_position] = '.';
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(endptr == token_buffer.data() + token_buffer.size()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// retry only if the locale changed; otherwise, this would loop forever
|
||||
const char current_decimal_point = get_decimal_point();
|
||||
if (current_decimal_point == decimal_point)
|
||||
{
|
||||
return;
|
||||
}
|
||||
decimal_point = current_decimal_point;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief contiguous fast path for scanning a number
|
||||
|
||||
@@ -27693,12 +27711,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
template < typename CompatibleType,
|
||||
typename U = detail::uncvref_t<CompatibleType>,
|
||||
detail::enable_if_t <
|
||||
!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 >
|
||||
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 >
|
||||
basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
|
||||
JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
|
||||
std::forward<CompatibleType>(val))))
|
||||
@@ -32748,7 +32761,6 @@ struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-c
|
||||
#undef JSON_INLINE_VARIABLE
|
||||
#undef JSON_NO_UNIQUE_ADDRESS
|
||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#undef JSON_USE_GLOBAL_UDLS
|
||||
|
||||
#ifndef JSON_TEST_KEEP_MACROS
|
||||
|
||||
@@ -1,93 +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"
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -18,19 +18,6 @@
|
||||
// for some reason including this after the json header leads to linker errors with VS 2017...
|
||||
#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
|
||||
#include <nlohmann/json.hpp>
|
||||
using json = nlohmann::json;
|
||||
@@ -41,7 +28,6 @@ using ordered_json = nlohmann::ordered_json;
|
||||
|
||||
#include <cstdio>
|
||||
#include <list>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
@@ -556,20 +542,6 @@ 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")
|
||||
{
|
||||
// see https://github.com/nlohmann/json/pull/2181#issuecomment-653326060
|
||||
|
||||
@@ -8,3 +8,6 @@ The following changes have been made to the code with respect to <https://github
|
||||
- membership check
|
||||
- made function from `_is_within`
|
||||
- removed unused variable `actual_path`
|
||||
- Added the optional config key `external`: include paths listed there are kept as
|
||||
`#include` directives instead of being inlined (the first directive per path; the
|
||||
repeated ones are commented out).
|
||||
|
||||
@@ -57,6 +57,11 @@ Python v.2.7.0 or higher is required.
|
||||
amalgamation. Have a look at `test/source.c.json` and `test/include.h.json`
|
||||
to see two examples.
|
||||
|
||||
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
|
||||
that includes another amalgamated header. Only the first directive for each
|
||||
of these paths is kept; the repeated ones are commented out.
|
||||
|
||||
* The `-s, --source` option should specify the path to the source directory.
|
||||
This is useful for supporting separate source and build directories.
|
||||
|
||||
|
||||
@@ -62,6 +62,10 @@ class Amalgamation(object):
|
||||
return None
|
||||
|
||||
def __init__(self, args):
|
||||
# include paths that are kept as #include directives instead of
|
||||
# being inlined (e.g. a header amalgamated on its own)
|
||||
self.external = []
|
||||
self.included_external = []
|
||||
with open(args.config, 'r') as f:
|
||||
config = json.loads(f.read())
|
||||
for key in config:
|
||||
@@ -220,11 +224,14 @@ class TranslationUnit(object):
|
||||
while include_match:
|
||||
if not _is_within(include_match, skippable_contexts):
|
||||
include_path = include_match.group("path")
|
||||
search_same_dir = include_match.group(1) == '"'
|
||||
found_included_path = self.amalgamation.find_included_file(
|
||||
include_path, self.file_dir if search_same_dir else None)
|
||||
if found_included_path:
|
||||
includes.append((include_match, found_included_path))
|
||||
if include_path in self.amalgamation.external:
|
||||
includes.append((include_match, None))
|
||||
else:
|
||||
search_same_dir = include_match.group(1) == '"'
|
||||
found_included_path = self.amalgamation.find_included_file(
|
||||
include_path, self.file_dir if search_same_dir else None)
|
||||
if found_included_path:
|
||||
includes.append((include_match, found_included_path))
|
||||
|
||||
include_match = self.include_pattern.search(self.content,
|
||||
include_match.end())
|
||||
@@ -235,6 +242,17 @@ class TranslationUnit(object):
|
||||
for include in includes:
|
||||
include_match, found_included_path = include
|
||||
tmp_content += self.content[prev_end:include_match.start()]
|
||||
if found_included_path is None:
|
||||
# an external header: keep the first directive and comment
|
||||
# out the repeated ones
|
||||
include_path = include_match.group("path")
|
||||
if include_path in self.amalgamation.included_external:
|
||||
tmp_content += "// {0}".format(include_match.group(0))
|
||||
else:
|
||||
self.amalgamation.included_external.append(include_path)
|
||||
tmp_content += include_match.group(0)
|
||||
prev_end = include_match.end()
|
||||
continue
|
||||
tmp_content += "// {0}\n".format(include_match.group(0))
|
||||
if found_included_path not in self.amalgamation.included_files:
|
||||
t = TranslationUnit(found_included_path, self.amalgamation, False)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"project": "JSON for Modern C++",
|
||||
"target": "single_include/nlohmann/json_view.hpp",
|
||||
"sources": [
|
||||
"include/nlohmann/json_view.hpp"
|
||||
],
|
||||
"include_paths": ["include"],
|
||||
"external": ["nlohmann/json.hpp"]
|
||||
}
|
||||
Reference in New Issue
Block a user