Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74f5ecbd35 |
No files matched your search
@@ -268,10 +268,10 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
public:
|
public:
|
||||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||||
|
|
||||||
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept
|
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_values_ = false) noexcept
|
||||||
: ia(std::move(adapter))
|
: ia(std::move(adapter))
|
||||||
, ignore_comments(ignore_comments_)
|
, ignore_comments(ignore_comments_)
|
||||||
, discard_number_values(discard_number_values_)
|
, discard_values(discard_values_)
|
||||||
{
|
{
|
||||||
// the token string of an error before the first token is everything
|
// the token string of an error before the first token is everything
|
||||||
// read so far
|
// read so far
|
||||||
@@ -388,6 +388,8 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
checks if it is inside the range. If a violation was detected, set up an
|
checks if it is inside the range. If a violation was detected, set up an
|
||||||
error message and return false. Otherwise, return true.
|
error message and return false. Otherwise, return true.
|
||||||
|
|
||||||
|
@param[in] keep whether the bytes are added to token_buffer (false for
|
||||||
|
accept(), see scan())
|
||||||
@param[in] ranges list of integers; interpreted as list of pairs of
|
@param[in] ranges list of integers; interpreted as list of pairs of
|
||||||
inclusive lower and upper bound, respectively
|
inclusive lower and upper bound, respectively
|
||||||
|
|
||||||
@@ -396,17 +398,18 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
|
|
||||||
@return true if and only if no range violation was detected
|
@return true if and only if no range violation was detected
|
||||||
*/
|
*/
|
||||||
bool next_byte_in_range(std::initializer_list<char_int_type> ranges)
|
template<bool Keep>
|
||||||
|
bool next_byte_in_range(std::integral_constant<bool, Keep> keep, std::initializer_list<char_int_type> ranges)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6);
|
JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6);
|
||||||
add(current);
|
add_string_char(keep, current);
|
||||||
|
|
||||||
for (auto range = ranges.begin(); range != ranges.end(); ++range)
|
for (auto range = ranges.begin(); range != ranges.end(); ++range)
|
||||||
{
|
{
|
||||||
get();
|
get();
|
||||||
if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) // NOLINT(bugprone-inc-dec-in-conditions)
|
if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) // NOLINT(bugprone-inc-dec-in-conditions)
|
||||||
{
|
{
|
||||||
add(current);
|
add_string_char(keep, current);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -419,14 +422,16 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// contiguous input: bulk-append the run of ordinary characters and complete
|
/// contiguous input: bulk-append the run of ordinary characters and complete
|
||||||
/// well-formed UTF-8 sequences starting at the current read position, then
|
/// well-formed UTF-8 sequences starting at the current read position (only
|
||||||
|
/// skip it if @a keep is false), then
|
||||||
/// read the first byte that needs individual handling (the closing quote, an
|
/// read the first byte that needs individual handling (the closing quote, an
|
||||||
/// escape, a control character, or an ill-formed UTF-8 byte) like get().
|
/// escape, a control character, or an ill-formed UTF-8 byte) like get().
|
||||||
/// That byte is taken from the buffer that was just scanned. Through get(),
|
/// That byte is taken from the buffer that was just scanned. Through get(),
|
||||||
/// Apple Clang merged this read with the other get() calls of the scan
|
/// Apple Clang merged this read with the other get() calls of the scan
|
||||||
/// function, which load the read position again right after it was stored
|
/// function, which load the read position again right after it was stored
|
||||||
/// (3% of json::accept() on jeopardy.json on Apple M1).
|
/// (3% of json::accept() on jeopardy.json on Apple M1).
|
||||||
char_int_type get_after_string_run(std::true_type /*bulk*/)
|
template<bool Keep>
|
||||||
|
char_int_type get_after_string_run(std::true_type /*bulk*/, std::integral_constant<bool, Keep> keep)
|
||||||
{
|
{
|
||||||
// a pending unget must be consumed through the normal path first
|
// a pending unget must be consumed through the normal path first
|
||||||
if (next_unget)
|
if (next_unget)
|
||||||
@@ -444,7 +449,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
const std::size_t pos = lexer_string_bulk_run(data, remaining);
|
const std::size_t pos = lexer_string_bulk_run(data, remaining);
|
||||||
if (pos != 0)
|
if (pos != 0)
|
||||||
{
|
{
|
||||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
append_string_run(keep, data, pos);
|
||||||
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
||||||
}
|
}
|
||||||
if (pos == remaining)
|
if (pos == remaining)
|
||||||
@@ -488,7 +493,8 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// streaming input: no bulk fast path
|
/// streaming input: no bulk fast path
|
||||||
char_int_type get_after_string_run(std::false_type /*bulk*/)
|
template<bool Keep>
|
||||||
|
char_int_type get_after_string_run(std::false_type /*bulk*/, std::integral_constant<bool, Keep> /*keep*/)
|
||||||
{
|
{
|
||||||
return get();
|
return get();
|
||||||
}
|
}
|
||||||
@@ -502,13 +508,18 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
may contain \0 bytes), and token_buffer.size() is the number of bytes in the
|
may contain \0 bytes), and token_buffer.size() is the number of bytes in the
|
||||||
string.
|
string.
|
||||||
|
|
||||||
|
@param[in] keep whether the string is copied into token_buffer; if false
|
||||||
|
(accept(), see scan()), it is only checked, with the same
|
||||||
|
errors, and token_buffer stays empty
|
||||||
|
|
||||||
@return token_type::value_string if string could be successfully scanned,
|
@return token_type::value_string if string could be successfully scanned,
|
||||||
token_type::parse_error otherwise
|
token_type::parse_error otherwise
|
||||||
|
|
||||||
@note In case of errors, variable error_message contains a textual
|
@note In case of errors, variable error_message contains a textual
|
||||||
description.
|
description.
|
||||||
*/
|
*/
|
||||||
token_type scan_string()
|
template<bool Keep>
|
||||||
|
token_type scan_string(std::integral_constant<bool, Keep> keep)
|
||||||
{
|
{
|
||||||
// reset token_buffer (ignore opening quote)
|
// reset token_buffer (ignore opening quote)
|
||||||
reset();
|
reset();
|
||||||
@@ -520,7 +531,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
{
|
{
|
||||||
// bulk-consume ordinary characters from contiguous input, then
|
// bulk-consume ordinary characters from contiguous input, then
|
||||||
// get the next character, the one that ends the run
|
// get the next character, the one that ends the run
|
||||||
switch (get_after_string_run(std::integral_constant<bool, bulk_scan>()))
|
switch (get_after_string_run(std::integral_constant<bool, bulk_scan>(), keep))
|
||||||
{
|
{
|
||||||
// end of file while parsing the string
|
// end of file while parsing the string
|
||||||
case char_traits<char_type>::eof():
|
case char_traits<char_type>::eof():
|
||||||
@@ -542,35 +553,35 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
{
|
{
|
||||||
// quotation mark
|
// quotation mark
|
||||||
case '\"':
|
case '\"':
|
||||||
add('\"');
|
add_string_char(keep, '\"');
|
||||||
break;
|
break;
|
||||||
// reverse solidus
|
// reverse solidus
|
||||||
case '\\':
|
case '\\':
|
||||||
add('\\');
|
add_string_char(keep, '\\');
|
||||||
break;
|
break;
|
||||||
// solidus
|
// solidus
|
||||||
case '/':
|
case '/':
|
||||||
add('/');
|
add_string_char(keep, '/');
|
||||||
break;
|
break;
|
||||||
// backspace
|
// backspace
|
||||||
case 'b':
|
case 'b':
|
||||||
add('\b');
|
add_string_char(keep, '\b');
|
||||||
break;
|
break;
|
||||||
// form feed
|
// form feed
|
||||||
case 'f':
|
case 'f':
|
||||||
add('\f');
|
add_string_char(keep, '\f');
|
||||||
break;
|
break;
|
||||||
// line feed
|
// line feed
|
||||||
case 'n':
|
case 'n':
|
||||||
add('\n');
|
add_string_char(keep, '\n');
|
||||||
break;
|
break;
|
||||||
// carriage return
|
// carriage return
|
||||||
case 'r':
|
case 'r':
|
||||||
add('\r');
|
add_string_char(keep, '\r');
|
||||||
break;
|
break;
|
||||||
// tab
|
// tab
|
||||||
case 't':
|
case 't':
|
||||||
add('\t');
|
add_string_char(keep, '\t');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// unicode escapes
|
// unicode escapes
|
||||||
@@ -638,10 +649,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF);
|
JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF);
|
||||||
|
|
||||||
// translate codepoint into bytes
|
// translate codepoint into bytes
|
||||||
encode_utf8(static_cast<std::uint32_t>(codepoint), [this](std::uint32_t byte)
|
add_codepoint(keep, codepoint);
|
||||||
{
|
|
||||||
add(static_cast<char_int_type>(byte));
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -824,7 +832,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
case 0x7E:
|
case 0x7E:
|
||||||
case 0x7F:
|
case 0x7F:
|
||||||
{
|
{
|
||||||
add(current);
|
add_string_char(keep, current);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -860,7 +868,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
case 0xDE:
|
case 0xDE:
|
||||||
case 0xDF:
|
case 0xDF:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF})))
|
if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range(keep, {0x80, 0xBF})))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -870,7 +878,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
// U+0800..U+0FFF: bytes E0 A0..BF 80..BF
|
// U+0800..U+0FFF: bytes E0 A0..BF 80..BF
|
||||||
case 0xE0:
|
case 0xE0:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF}))))
|
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0xA0, 0xBF, 0x80, 0xBF}))))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -894,7 +902,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
case 0xEE:
|
case 0xEE:
|
||||||
case 0xEF:
|
case 0xEF:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF}))))
|
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0xBF, 0x80, 0xBF}))))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -904,7 +912,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
// U+D000..U+D7FF: bytes ED 80..9F 80..BF
|
// U+D000..U+D7FF: bytes ED 80..9F 80..BF
|
||||||
case 0xED:
|
case 0xED:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF}))))
|
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0x9F, 0x80, 0xBF}))))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -914,7 +922,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
// U+10000..U+3FFFF F0 90..BF 80..BF 80..BF
|
// U+10000..U+3FFFF F0 90..BF 80..BF 80..BF
|
||||||
case 0xF0:
|
case 0xF0:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
|
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -926,7 +934,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
case 0xF2:
|
case 0xF2:
|
||||||
case 0xF3:
|
case 0xF3:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
|
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -936,7 +944,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
// U+100000..U+10FFFF F4 80..8F 80..BF 80..BF
|
// U+100000..U+10FFFF F4 80..8F 80..BF 80..BF
|
||||||
case 0xF4:
|
case 0xF4:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF}))))
|
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF}))))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -1427,7 +1435,7 @@ scan_number_done:
|
|||||||
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)
|
||||||
{
|
{
|
||||||
// accept() only needs to know whether the input is valid, so it sets
|
// accept() only needs to know whether the input is valid, so it sets
|
||||||
// discard_number_values (see json.hpp), and an integer token whose
|
// discard_values (see json.hpp), and an integer token whose
|
||||||
// digit count shows that it fits is reported without calling
|
// digit count shows that it fits is reported without calling
|
||||||
// convert_integer(). A number with up to 18 digits always fits into
|
// convert_integer(). A number with up to 18 digits always fits into
|
||||||
// both std::uint64_t and std::int64_t (18 nines is about 1e18, below
|
// both std::uint64_t and std::int64_t (18 nines is about 1e18, below
|
||||||
@@ -1442,7 +1450,7 @@ scan_number_done:
|
|||||||
// json_sax_acceptor, whose number callbacks discard their argument and
|
// json_sax_acceptor, whose number callbacks discard their argument and
|
||||||
// return true, and the parser rejects neither integers nor finite
|
// return true, and the parser rejects neither integers nor finite
|
||||||
// floats. value_unsigned/value_integer are left unset here, so a caller
|
// floats. value_unsigned/value_integer are left unset here, so a caller
|
||||||
// that reads the converted value must not set discard_number_values.
|
// that reads the converted value must not set discard_values.
|
||||||
//
|
//
|
||||||
// On contiguous input, scan_number_bulk_contiguous() converts integer
|
// On contiguous input, scan_number_bulk_contiguous() converts integer
|
||||||
// tokens itself and does not pass them to this function, unless
|
// tokens itself and does not pass them to this function, unless
|
||||||
@@ -1450,7 +1458,7 @@ scan_number_done:
|
|||||||
// reached for input without bulk access (e.g. streams), with
|
// reached for input without bulk access (e.g. streams), with
|
||||||
// JSON_DIAGNOSTIC_POSITIONS, or when scan_number_bulk_contiguous()
|
// JSON_DIAGNOSTIC_POSITIONS, or when scan_number_bulk_contiguous()
|
||||||
// falls back to scan_number().
|
// falls back to scan_number().
|
||||||
if (discard_number_values)
|
if (discard_values)
|
||||||
{
|
{
|
||||||
constexpr std::size_t safe_digit_count = 18;
|
constexpr std::size_t safe_digit_count = 18;
|
||||||
if (number_type == token_type::value_unsigned && token_buffer.size() <= safe_digit_count)
|
if (number_type == token_type::value_unsigned && token_buffer.size() <= safe_digit_count)
|
||||||
@@ -1701,7 +1709,7 @@ scan_number_done:
|
|||||||
// w < 10^19, so a value below 10^max_exponent10 cannot overflow, and a
|
// w < 10^19, so a value below 10^max_exponent10 cannot overflow, and a
|
||||||
// value that underflows is a finite number as well. Only a token that
|
// value that underflows is a finite number as well. Only a token that
|
||||||
// may overflow has to be converted to find out.
|
// may overflow has to be converted to find out.
|
||||||
if (JSON_HEDLEY_UNLIKELY(discard_number_values && s.exponent + 19 <= std::numeric_limits<number_float_t>::max_exponent10))
|
if (JSON_HEDLEY_UNLIKELY(discard_values && s.exponent + 19 <= std::numeric_limits<number_float_t>::max_exponent10))
|
||||||
{
|
{
|
||||||
value_float = 0;
|
value_float = 0;
|
||||||
return true;
|
return true;
|
||||||
@@ -2033,6 +2041,34 @@ scan_number_done:
|
|||||||
token_buffer.push_back(static_cast<typename string_t::value_type>(c));
|
token_buffer.push_back(static_cast<typename string_t::value_type>(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// add a character of a string token; accept() checks the string but does
|
||||||
|
/// not keep its value (@a keep is false)
|
||||||
|
void add_string_char(std::true_type /*keep*/, char_int_type c)
|
||||||
|
{
|
||||||
|
token_buffer.push_back(static_cast<typename string_t::value_type>(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_string_char(std::false_type /*keep*/, char_int_type /*c*/) noexcept {}
|
||||||
|
|
||||||
|
/// add the UTF-8 bytes of a code point from a \\u escape to a string token
|
||||||
|
void add_codepoint(std::true_type /*keep*/, int codepoint)
|
||||||
|
{
|
||||||
|
encode_utf8(static_cast<std::uint32_t>(codepoint), [this](std::uint32_t byte)
|
||||||
|
{
|
||||||
|
add(static_cast<char_int_type>(byte));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_codepoint(std::false_type /*keep*/, int /*codepoint*/) noexcept {}
|
||||||
|
|
||||||
|
/// append a run of ordinary string characters (see get_after_string_run())
|
||||||
|
void append_string_run(std::true_type /*keep*/, const unsigned char* data, std::size_t n)
|
||||||
|
{
|
||||||
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void append_string_run(std::false_type /*keep*/, const unsigned char* /*data*/, std::size_t /*n*/) noexcept {}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/////////////////////
|
/////////////////////
|
||||||
// value getters
|
// value getters
|
||||||
@@ -2427,7 +2463,11 @@ scan_number_done:
|
|||||||
|
|
||||||
// string
|
// string
|
||||||
case '\"':
|
case '\"':
|
||||||
return scan_string();
|
// accept() (discard_values) checks strings without copying them
|
||||||
|
// into token_buffer. The choice is made once per string: a
|
||||||
|
// check inside the string loop made number-heavy documents up
|
||||||
|
// to 3 % slower (mesh.json, Apple M1)
|
||||||
|
return discard_values ? scan_string(std::false_type {}) : scan_string(std::true_type {});
|
||||||
|
|
||||||
// number
|
// number
|
||||||
case '-':
|
case '-':
|
||||||
@@ -2512,11 +2552,13 @@ scan_number_done:
|
|||||||
std::size_t decimal_point_position = std::string::npos;
|
std::size_t decimal_point_position = std::string::npos;
|
||||||
|
|
||||||
/// whether the caller only needs the token types and never looks at the
|
/// whether the caller only needs the token types and never looks at the
|
||||||
/// converted numeric values; set only by accept(), which parses through
|
/// values of number and string tokens; set only by accept(), which parses
|
||||||
/// json_sax_acceptor. When set, convert_number() skips converting integer
|
/// through json_sax_acceptor. When set, convert_number() skips converting
|
||||||
/// tokens whose digit count guarantees that they fit into 64 bits (see
|
/// integer tokens whose digit count guarantees that they fit into 64 bits
|
||||||
/// there)
|
/// (see there), floats are converted only when they may overflow (see
|
||||||
const bool discard_number_values = false;
|
/// convert_accumulated_float()), and strings are checked without being
|
||||||
|
/// copied into token_buffer (see scan())
|
||||||
|
const bool discard_values = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|||||||
@@ -89,9 +89,9 @@ class parser
|
|||||||
const bool allow_exceptions_ = true,
|
const bool allow_exceptions_ = true,
|
||||||
const bool ignore_comments = false,
|
const bool ignore_comments = false,
|
||||||
const bool ignore_trailing_commas_ = false,
|
const bool ignore_trailing_commas_ = false,
|
||||||
const bool discard_number_values_ = false)
|
const bool discard_values_ = false)
|
||||||
: callback(std::move(cb))
|
: callback(std::move(cb))
|
||||||
, m_lexer(std::move(adapter), ignore_comments, discard_number_values_)
|
, m_lexer(std::move(adapter), ignore_comments, discard_values_)
|
||||||
, allow_exceptions(allow_exceptions_)
|
, allow_exceptions(allow_exceptions_)
|
||||||
, ignore_trailing_commas(ignore_trailing_commas_)
|
, ignore_trailing_commas(ignore_trailing_commas_)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2088,8 +2088,7 @@ class binary_writer
|
|||||||
/// arrays: the first index with one more digit; only maintained by
|
/// arrays: the first index with one more digit; only maintained by
|
||||||
/// @ref calc_bson_sizes
|
/// @ref calc_bson_sizes
|
||||||
std::uint64_t index_boundary = 10;
|
std::uint64_t index_boundary = 10;
|
||||||
/// @ref calc_bson_sizes: where its size goes in the table; @ref
|
/// @ref calc_bson_sizes only: where its size goes in the table
|
||||||
/// write_bson_document_single_pass: the position of its size field
|
|
||||||
std::size_t size_slot;
|
std::size_t size_slot;
|
||||||
/// @ref calc_bson_sizes only: the size of its entries seen so far
|
/// @ref calc_bson_sizes only: the size of its entries seen so far
|
||||||
std::size_t entries_size = 0;
|
std::size_t entries_size = 0;
|
||||||
@@ -2340,197 +2339,6 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename SinkType>
|
|
||||||
using sink_written_t = decltype(std::declval<const SinkType&>().written());
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief whether BSON documents can be written in a single pass: the sink
|
|
||||||
can overwrite what it has written (output_vector_sink), and every
|
|
||||||
integer fits the 64 bits BSON has, so that writing an array of
|
|
||||||
numbers needs no range check
|
|
||||||
*/
|
|
||||||
using bson_single_pass_sink = std::integral_constant < bool,
|
|
||||||
is_detected<sink_written_t, OutputSinkType>::value
|
|
||||||
&& std::numeric_limits<typename BasicJsonType::number_integer_t>::is_specialized
|
|
||||||
&& std::numeric_limits<typename BasicJsonType::number_integer_t>::digits <= std::numeric_limits<std::int64_t>::digits
|
|
||||||
&& std::numeric_limits<typename BasicJsonType::number_unsigned_t>::is_specialized
|
|
||||||
&& std::numeric_limits<typename BasicJsonType::number_unsigned_t>::digits <= std::numeric_limits<std::uint64_t>::digits >;
|
|
||||||
|
|
||||||
/// write a placeholder for the size of an object or array whose entries
|
|
||||||
/// follow; @return its position, for @ref patch_bson_size
|
|
||||||
std::size_t write_bson_size_placeholder()
|
|
||||||
{
|
|
||||||
const std::size_t pos = oa.written();
|
|
||||||
write_number<std::int32_t>(0, true);
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// end the object or array whose size is at @a pos and write its size
|
|
||||||
/// there: everything from that field to the terminating null byte
|
|
||||||
/// @return false (and the size is not written) if it does not fit
|
|
||||||
bool close_bson_container(const std::size_t pos)
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x00));
|
|
||||||
const std::size_t size = oa.written() - pos;
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(size)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::array<CharType, sizeof(std::int32_t)> bytes{};
|
|
||||||
store_little_endian<std::int32_t>(bytes.data(), static_cast<std::int32_t>(size));
|
|
||||||
oa.overwrite(pos, bytes.data(), bytes.size());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief check the value @a j of a BSON entry as @ref calc_bson_value_size
|
|
||||||
does, for @ref write_bson_document_single_pass
|
|
||||||
@return at least the size of the value
|
|
||||||
A number, boolean, or null needs no check there (its integers have at most
|
|
||||||
64 bits) and has at most 8 characters; a discarded value is rejected with
|
|
||||||
the same exception when it is written.
|
|
||||||
*/
|
|
||||||
std::size_t check_bson_value(const BasicJsonType& j)
|
|
||||||
{
|
|
||||||
return (j.is_string() || j.is_binary()) ? calc_bson_value_size(j) : 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// whether an entry of @a entry_size characters keeps the document that
|
|
||||||
/// begins at @a start, closed by @a open_containers null bytes, within
|
|
||||||
/// the 32-bit size of BSON
|
|
||||||
bool bson_entry_fits(const std::size_t start, const std::size_t entry_size, const std::size_t open_containers) const noexcept
|
|
||||||
{
|
|
||||||
const std::size_t limit = static_cast<std::size_t>((std::numeric_limits<std::int32_t>::max)());
|
|
||||||
const std::size_t written = oa.written() - start;
|
|
||||||
return written <= limit && entry_size <= limit - written && open_containers <= limit - written - entry_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief Serializes the JSON object @a document as a BSON document in a
|
|
||||||
single pass, writing the size of each object and array once it is
|
|
||||||
complete
|
|
||||||
|
|
||||||
Checks every key and value as @ref calc_bson_sizes does, in the same
|
|
||||||
order, so the same exception is thrown; the output written up to then is
|
|
||||||
discarded with the vector it was written to.
|
|
||||||
|
|
||||||
@return false as soon as the document or a string, binary value, object,
|
|
||||||
or array in it would be larger than the 32-bit BSON sizes allow;
|
|
||||||
nothing large has been written then, and the caller writes the
|
|
||||||
document in two passes, which reports that error
|
|
||||||
*/
|
|
||||||
template < typename SinkType = OutputSinkType,
|
|
||||||
typename std::enable_if < is_detected<sink_written_t, SinkType>::value, int >::type = 0 >
|
|
||||||
bool write_bson_document_single_pass(const BasicJsonType& document)
|
|
||||||
{
|
|
||||||
const std::size_t start = oa.written();
|
|
||||||
// the object or array whose entries are being written (with the
|
|
||||||
// position of its size), and the ones it is in
|
|
||||||
bson_frame current(&document, write_bson_size_placeholder());
|
|
||||||
std::vector<bson_frame> parents;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
const BasicJsonType* nested = nullptr;
|
|
||||||
std::size_t nested_size_pos = 0;
|
|
||||||
if (current.value->is_object())
|
|
||||||
{
|
|
||||||
const auto& object = *current.value->m_data.m_value.object;
|
|
||||||
while (nested == nullptr && current.member != object.cend())
|
|
||||||
{
|
|
||||||
const auto& el = *current.member;
|
|
||||||
++current.member;
|
|
||||||
const std::size_t header_size = calc_bson_entry_header_size(el.first, el.second);
|
|
||||||
const std::size_t value_size = el.second.is_structured() ? 0 : check_bson_value(el.second);
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, header_size + value_size, parents.size() + 1)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (el.second.is_structured())
|
|
||||||
{
|
|
||||||
write_bson_entry_header(el.first, el.second.is_object() ? 0x03 : 0x04);
|
|
||||||
nested_size_pos = write_bson_size_placeholder();
|
|
||||||
nested = &el.second;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_bson_value(el.first, el.second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const auto& array = *current.value->m_data.m_value.array;
|
|
||||||
while (nested == nullptr && current.index < array.size())
|
|
||||||
{
|
|
||||||
if (is_bson_fixed_size(array[current.index]))
|
|
||||||
{
|
|
||||||
write_bson_array_fixed_entries(current);
|
|
||||||
if (current.index == array.size())
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// a string, binary value, object or array
|
|
||||||
const BasicJsonType& el = array[current.index];
|
|
||||||
const string_t index_name(current.index_name.data(), current.index_name_size);
|
|
||||||
current.next_index();
|
|
||||||
const std::size_t value_size = el.is_structured() ? 0 : check_bson_value(el);
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, 2 + index_name.size() + value_size, parents.size() + 1)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (el.is_structured())
|
|
||||||
{
|
|
||||||
write_bson_entry_header(index_name, el.is_object() ? 0x03 : 0x04);
|
|
||||||
nested_size_pos = write_bson_size_placeholder();
|
|
||||||
nested = ⪙
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_bson_value(index_name, el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nested != nullptr)
|
|
||||||
{
|
|
||||||
if (nested->is_array())
|
|
||||||
{
|
|
||||||
// an array of booleans, numbers and null is written without a frame on the stack
|
|
||||||
bson_frame leaf(nested, nested_size_pos);
|
|
||||||
write_bson_array_fixed_entries(leaf);
|
|
||||||
if (leaf.index == nested->m_data.m_value.array->size())
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(nested_size_pos)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
parents.push_back(std::move(current));
|
|
||||||
current = std::move(leaf);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
parents.push_back(std::move(current));
|
|
||||||
current = bson_frame(nested, nested_size_pos);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(current.size_slot)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (parents.empty())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
current = std::move(parents.back());
|
|
||||||
parents.pop_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief Serializes the JSON object @a document as a BSON document
|
@brief Serializes the JSON object @a document as a BSON document
|
||||||
|
|
||||||
@@ -2542,39 +2350,6 @@ class binary_writer
|
|||||||
@pre document.type() == value_t::object
|
@pre document.type() == value_t::object
|
||||||
*/
|
*/
|
||||||
void write_bson_document(const BasicJsonType& document)
|
void write_bson_document(const BasicJsonType& document)
|
||||||
{
|
|
||||||
write_bson_document(document, bson_single_pass_sink());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the sink can overwrite: one pass, unless the error handler replaces or
|
|
||||||
/// drops ill-formed UTF-8, which changes the sizes the checks compute
|
|
||||||
void write_bson_document(const BasicJsonType& document, std::true_type /*single_pass*/)
|
|
||||||
{
|
|
||||||
const std::size_t start = oa.written();
|
|
||||||
if ((error_handler == error_handler_t::keep || error_handler == error_handler_t::strict)
|
|
||||||
&& write_bson_document_single_pass(document))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// a length does not fit: start over, so that it is reported as the
|
|
||||||
// two passes report it, before anything is written
|
|
||||||
oa.rewind(start);
|
|
||||||
write_bson_document_two_pass(document);
|
|
||||||
}
|
|
||||||
|
|
||||||
void write_bson_document(const BasicJsonType& document, std::false_type /*single_pass*/)
|
|
||||||
{
|
|
||||||
write_bson_document_two_pass(document);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief Serializes the JSON object @a document as a BSON document: the
|
|
||||||
sizes of all objects and arrays first, then the entries
|
|
||||||
|
|
||||||
@param[in] document the JSON object to serialize
|
|
||||||
@pre document.type() == value_t::object
|
|
||||||
*/
|
|
||||||
void write_bson_document_two_pass(const BasicJsonType& document)
|
|
||||||
{
|
{
|
||||||
std::vector<std::size_t> nested_sizes;
|
std::vector<std::size_t> nested_sizes;
|
||||||
const std::size_t document_size = calc_bson_sizes(document, nested_sizes);
|
const std::size_t document_size = calc_bson_sizes(document, nested_sizes);
|
||||||
|
|||||||
@@ -227,29 +227,6 @@ class output_vector_sink
|
|||||||
void flush() noexcept
|
void flush() noexcept
|
||||||
{}
|
{}
|
||||||
|
|
||||||
/// the number of characters written so far, the position of the next one
|
|
||||||
std::size_t written() const noexcept
|
|
||||||
{
|
|
||||||
return position();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief replace @a length characters written before, from position @a pos
|
|
||||||
///
|
|
||||||
/// Lets a writer put a length in front of data it has written since, such
|
|
||||||
/// as the size of a BSON document, without computing it in advance.
|
|
||||||
void overwrite(const std::size_t pos, const CharType* s, const std::size_t length) noexcept
|
|
||||||
{
|
|
||||||
JSON_ASSERT(pos + length <= position());
|
|
||||||
std::memcpy(v->data() + pos, s, length * sizeof(CharType));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// drop the characters written from position @a pos on
|
|
||||||
void rewind(const std::size_t pos) noexcept
|
|
||||||
{
|
|
||||||
JSON_ASSERT(pos <= position());
|
|
||||||
cursor = v->data() + pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// number of characters of *v written so far
|
/// number of characters of *v written so far
|
||||||
std::size_t position() const noexcept
|
std::size_t position() const noexcept
|
||||||
|
|||||||
@@ -184,11 +184,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
const bool allow_exceptions = true,
|
const bool allow_exceptions = true,
|
||||||
const bool ignore_comments = false,
|
const bool ignore_comments = false,
|
||||||
const bool ignore_trailing_commas = false,
|
const bool ignore_trailing_commas = false,
|
||||||
const bool discard_number_values = false
|
const bool discard_values = false
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter),
|
return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter),
|
||||||
std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas, discard_number_values);
|
std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas, discard_values);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
Generated
+88
-294
@@ -13124,10 +13124,10 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
public:
|
public:
|
||||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||||
|
|
||||||
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept
|
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_values_ = false) noexcept
|
||||||
: ia(std::move(adapter))
|
: ia(std::move(adapter))
|
||||||
, ignore_comments(ignore_comments_)
|
, ignore_comments(ignore_comments_)
|
||||||
, discard_number_values(discard_number_values_)
|
, discard_values(discard_values_)
|
||||||
{
|
{
|
||||||
// the token string of an error before the first token is everything
|
// the token string of an error before the first token is everything
|
||||||
// read so far
|
// read so far
|
||||||
@@ -13244,6 +13244,8 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
checks if it is inside the range. If a violation was detected, set up an
|
checks if it is inside the range. If a violation was detected, set up an
|
||||||
error message and return false. Otherwise, return true.
|
error message and return false. Otherwise, return true.
|
||||||
|
|
||||||
|
@param[in] keep whether the bytes are added to token_buffer (false for
|
||||||
|
accept(), see scan())
|
||||||
@param[in] ranges list of integers; interpreted as list of pairs of
|
@param[in] ranges list of integers; interpreted as list of pairs of
|
||||||
inclusive lower and upper bound, respectively
|
inclusive lower and upper bound, respectively
|
||||||
|
|
||||||
@@ -13252,17 +13254,18 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
|
|
||||||
@return true if and only if no range violation was detected
|
@return true if and only if no range violation was detected
|
||||||
*/
|
*/
|
||||||
bool next_byte_in_range(std::initializer_list<char_int_type> ranges)
|
template<bool Keep>
|
||||||
|
bool next_byte_in_range(std::integral_constant<bool, Keep> keep, std::initializer_list<char_int_type> ranges)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6);
|
JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6);
|
||||||
add(current);
|
add_string_char(keep, current);
|
||||||
|
|
||||||
for (auto range = ranges.begin(); range != ranges.end(); ++range)
|
for (auto range = ranges.begin(); range != ranges.end(); ++range)
|
||||||
{
|
{
|
||||||
get();
|
get();
|
||||||
if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) // NOLINT(bugprone-inc-dec-in-conditions)
|
if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) // NOLINT(bugprone-inc-dec-in-conditions)
|
||||||
{
|
{
|
||||||
add(current);
|
add_string_char(keep, current);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -13275,14 +13278,16 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// contiguous input: bulk-append the run of ordinary characters and complete
|
/// contiguous input: bulk-append the run of ordinary characters and complete
|
||||||
/// well-formed UTF-8 sequences starting at the current read position, then
|
/// well-formed UTF-8 sequences starting at the current read position (only
|
||||||
|
/// skip it if @a keep is false), then
|
||||||
/// read the first byte that needs individual handling (the closing quote, an
|
/// read the first byte that needs individual handling (the closing quote, an
|
||||||
/// escape, a control character, or an ill-formed UTF-8 byte) like get().
|
/// escape, a control character, or an ill-formed UTF-8 byte) like get().
|
||||||
/// That byte is taken from the buffer that was just scanned. Through get(),
|
/// That byte is taken from the buffer that was just scanned. Through get(),
|
||||||
/// Apple Clang merged this read with the other get() calls of the scan
|
/// Apple Clang merged this read with the other get() calls of the scan
|
||||||
/// function, which load the read position again right after it was stored
|
/// function, which load the read position again right after it was stored
|
||||||
/// (3% of json::accept() on jeopardy.json on Apple M1).
|
/// (3% of json::accept() on jeopardy.json on Apple M1).
|
||||||
char_int_type get_after_string_run(std::true_type /*bulk*/)
|
template<bool Keep>
|
||||||
|
char_int_type get_after_string_run(std::true_type /*bulk*/, std::integral_constant<bool, Keep> keep)
|
||||||
{
|
{
|
||||||
// a pending unget must be consumed through the normal path first
|
// a pending unget must be consumed through the normal path first
|
||||||
if (next_unget)
|
if (next_unget)
|
||||||
@@ -13300,7 +13305,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
const std::size_t pos = lexer_string_bulk_run(data, remaining);
|
const std::size_t pos = lexer_string_bulk_run(data, remaining);
|
||||||
if (pos != 0)
|
if (pos != 0)
|
||||||
{
|
{
|
||||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
append_string_run(keep, data, pos);
|
||||||
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
||||||
}
|
}
|
||||||
if (pos == remaining)
|
if (pos == remaining)
|
||||||
@@ -13344,7 +13349,8 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// streaming input: no bulk fast path
|
/// streaming input: no bulk fast path
|
||||||
char_int_type get_after_string_run(std::false_type /*bulk*/)
|
template<bool Keep>
|
||||||
|
char_int_type get_after_string_run(std::false_type /*bulk*/, std::integral_constant<bool, Keep> /*keep*/)
|
||||||
{
|
{
|
||||||
return get();
|
return get();
|
||||||
}
|
}
|
||||||
@@ -13358,13 +13364,18 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
may contain \0 bytes), and token_buffer.size() is the number of bytes in the
|
may contain \0 bytes), and token_buffer.size() is the number of bytes in the
|
||||||
string.
|
string.
|
||||||
|
|
||||||
|
@param[in] keep whether the string is copied into token_buffer; if false
|
||||||
|
(accept(), see scan()), it is only checked, with the same
|
||||||
|
errors, and token_buffer stays empty
|
||||||
|
|
||||||
@return token_type::value_string if string could be successfully scanned,
|
@return token_type::value_string if string could be successfully scanned,
|
||||||
token_type::parse_error otherwise
|
token_type::parse_error otherwise
|
||||||
|
|
||||||
@note In case of errors, variable error_message contains a textual
|
@note In case of errors, variable error_message contains a textual
|
||||||
description.
|
description.
|
||||||
*/
|
*/
|
||||||
token_type scan_string()
|
template<bool Keep>
|
||||||
|
token_type scan_string(std::integral_constant<bool, Keep> keep)
|
||||||
{
|
{
|
||||||
// reset token_buffer (ignore opening quote)
|
// reset token_buffer (ignore opening quote)
|
||||||
reset();
|
reset();
|
||||||
@@ -13376,7 +13387,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
{
|
{
|
||||||
// bulk-consume ordinary characters from contiguous input, then
|
// bulk-consume ordinary characters from contiguous input, then
|
||||||
// get the next character, the one that ends the run
|
// get the next character, the one that ends the run
|
||||||
switch (get_after_string_run(std::integral_constant<bool, bulk_scan>()))
|
switch (get_after_string_run(std::integral_constant<bool, bulk_scan>(), keep))
|
||||||
{
|
{
|
||||||
// end of file while parsing the string
|
// end of file while parsing the string
|
||||||
case char_traits<char_type>::eof():
|
case char_traits<char_type>::eof():
|
||||||
@@ -13398,35 +13409,35 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
{
|
{
|
||||||
// quotation mark
|
// quotation mark
|
||||||
case '\"':
|
case '\"':
|
||||||
add('\"');
|
add_string_char(keep, '\"');
|
||||||
break;
|
break;
|
||||||
// reverse solidus
|
// reverse solidus
|
||||||
case '\\':
|
case '\\':
|
||||||
add('\\');
|
add_string_char(keep, '\\');
|
||||||
break;
|
break;
|
||||||
// solidus
|
// solidus
|
||||||
case '/':
|
case '/':
|
||||||
add('/');
|
add_string_char(keep, '/');
|
||||||
break;
|
break;
|
||||||
// backspace
|
// backspace
|
||||||
case 'b':
|
case 'b':
|
||||||
add('\b');
|
add_string_char(keep, '\b');
|
||||||
break;
|
break;
|
||||||
// form feed
|
// form feed
|
||||||
case 'f':
|
case 'f':
|
||||||
add('\f');
|
add_string_char(keep, '\f');
|
||||||
break;
|
break;
|
||||||
// line feed
|
// line feed
|
||||||
case 'n':
|
case 'n':
|
||||||
add('\n');
|
add_string_char(keep, '\n');
|
||||||
break;
|
break;
|
||||||
// carriage return
|
// carriage return
|
||||||
case 'r':
|
case 'r':
|
||||||
add('\r');
|
add_string_char(keep, '\r');
|
||||||
break;
|
break;
|
||||||
// tab
|
// tab
|
||||||
case 't':
|
case 't':
|
||||||
add('\t');
|
add_string_char(keep, '\t');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// unicode escapes
|
// unicode escapes
|
||||||
@@ -13494,10 +13505,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF);
|
JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF);
|
||||||
|
|
||||||
// translate codepoint into bytes
|
// translate codepoint into bytes
|
||||||
encode_utf8(static_cast<std::uint32_t>(codepoint), [this](std::uint32_t byte)
|
add_codepoint(keep, codepoint);
|
||||||
{
|
|
||||||
add(static_cast<char_int_type>(byte));
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -13680,7 +13688,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
case 0x7E:
|
case 0x7E:
|
||||||
case 0x7F:
|
case 0x7F:
|
||||||
{
|
{
|
||||||
add(current);
|
add_string_char(keep, current);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13716,7 +13724,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
case 0xDE:
|
case 0xDE:
|
||||||
case 0xDF:
|
case 0xDF:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF})))
|
if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range(keep, {0x80, 0xBF})))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -13726,7 +13734,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
// U+0800..U+0FFF: bytes E0 A0..BF 80..BF
|
// U+0800..U+0FFF: bytes E0 A0..BF 80..BF
|
||||||
case 0xE0:
|
case 0xE0:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF}))))
|
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0xA0, 0xBF, 0x80, 0xBF}))))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -13750,7 +13758,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
case 0xEE:
|
case 0xEE:
|
||||||
case 0xEF:
|
case 0xEF:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF}))))
|
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0xBF, 0x80, 0xBF}))))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -13760,7 +13768,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
// U+D000..U+D7FF: bytes ED 80..9F 80..BF
|
// U+D000..U+D7FF: bytes ED 80..9F 80..BF
|
||||||
case 0xED:
|
case 0xED:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF}))))
|
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0x9F, 0x80, 0xBF}))))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -13770,7 +13778,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
// U+10000..U+3FFFF F0 90..BF 80..BF 80..BF
|
// U+10000..U+3FFFF F0 90..BF 80..BF 80..BF
|
||||||
case 0xF0:
|
case 0xF0:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
|
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -13782,7 +13790,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
case 0xF2:
|
case 0xF2:
|
||||||
case 0xF3:
|
case 0xF3:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
|
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -13792,7 +13800,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
// U+100000..U+10FFFF F4 80..8F 80..BF 80..BF
|
// U+100000..U+10FFFF F4 80..8F 80..BF 80..BF
|
||||||
case 0xF4:
|
case 0xF4:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF}))))
|
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF}))))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -14283,7 +14291,7 @@ scan_number_done:
|
|||||||
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)
|
||||||
{
|
{
|
||||||
// accept() only needs to know whether the input is valid, so it sets
|
// accept() only needs to know whether the input is valid, so it sets
|
||||||
// discard_number_values (see json.hpp), and an integer token whose
|
// discard_values (see json.hpp), and an integer token whose
|
||||||
// digit count shows that it fits is reported without calling
|
// digit count shows that it fits is reported without calling
|
||||||
// convert_integer(). A number with up to 18 digits always fits into
|
// convert_integer(). A number with up to 18 digits always fits into
|
||||||
// both std::uint64_t and std::int64_t (18 nines is about 1e18, below
|
// both std::uint64_t and std::int64_t (18 nines is about 1e18, below
|
||||||
@@ -14298,7 +14306,7 @@ scan_number_done:
|
|||||||
// json_sax_acceptor, whose number callbacks discard their argument and
|
// json_sax_acceptor, whose number callbacks discard their argument and
|
||||||
// return true, and the parser rejects neither integers nor finite
|
// return true, and the parser rejects neither integers nor finite
|
||||||
// floats. value_unsigned/value_integer are left unset here, so a caller
|
// floats. value_unsigned/value_integer are left unset here, so a caller
|
||||||
// that reads the converted value must not set discard_number_values.
|
// that reads the converted value must not set discard_values.
|
||||||
//
|
//
|
||||||
// On contiguous input, scan_number_bulk_contiguous() converts integer
|
// On contiguous input, scan_number_bulk_contiguous() converts integer
|
||||||
// tokens itself and does not pass them to this function, unless
|
// tokens itself and does not pass them to this function, unless
|
||||||
@@ -14306,7 +14314,7 @@ scan_number_done:
|
|||||||
// reached for input without bulk access (e.g. streams), with
|
// reached for input without bulk access (e.g. streams), with
|
||||||
// JSON_DIAGNOSTIC_POSITIONS, or when scan_number_bulk_contiguous()
|
// JSON_DIAGNOSTIC_POSITIONS, or when scan_number_bulk_contiguous()
|
||||||
// falls back to scan_number().
|
// falls back to scan_number().
|
||||||
if (discard_number_values)
|
if (discard_values)
|
||||||
{
|
{
|
||||||
constexpr std::size_t safe_digit_count = 18;
|
constexpr std::size_t safe_digit_count = 18;
|
||||||
if (number_type == token_type::value_unsigned && token_buffer.size() <= safe_digit_count)
|
if (number_type == token_type::value_unsigned && token_buffer.size() <= safe_digit_count)
|
||||||
@@ -14557,7 +14565,7 @@ scan_number_done:
|
|||||||
// w < 10^19, so a value below 10^max_exponent10 cannot overflow, and a
|
// w < 10^19, so a value below 10^max_exponent10 cannot overflow, and a
|
||||||
// value that underflows is a finite number as well. Only a token that
|
// value that underflows is a finite number as well. Only a token that
|
||||||
// may overflow has to be converted to find out.
|
// may overflow has to be converted to find out.
|
||||||
if (JSON_HEDLEY_UNLIKELY(discard_number_values && s.exponent + 19 <= std::numeric_limits<number_float_t>::max_exponent10))
|
if (JSON_HEDLEY_UNLIKELY(discard_values && s.exponent + 19 <= std::numeric_limits<number_float_t>::max_exponent10))
|
||||||
{
|
{
|
||||||
value_float = 0;
|
value_float = 0;
|
||||||
return true;
|
return true;
|
||||||
@@ -14889,6 +14897,34 @@ scan_number_done:
|
|||||||
token_buffer.push_back(static_cast<typename string_t::value_type>(c));
|
token_buffer.push_back(static_cast<typename string_t::value_type>(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// add a character of a string token; accept() checks the string but does
|
||||||
|
/// not keep its value (@a keep is false)
|
||||||
|
void add_string_char(std::true_type /*keep*/, char_int_type c)
|
||||||
|
{
|
||||||
|
token_buffer.push_back(static_cast<typename string_t::value_type>(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_string_char(std::false_type /*keep*/, char_int_type /*c*/) noexcept {}
|
||||||
|
|
||||||
|
/// add the UTF-8 bytes of a code point from a \\u escape to a string token
|
||||||
|
void add_codepoint(std::true_type /*keep*/, int codepoint)
|
||||||
|
{
|
||||||
|
encode_utf8(static_cast<std::uint32_t>(codepoint), [this](std::uint32_t byte)
|
||||||
|
{
|
||||||
|
add(static_cast<char_int_type>(byte));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_codepoint(std::false_type /*keep*/, int /*codepoint*/) noexcept {}
|
||||||
|
|
||||||
|
/// append a run of ordinary string characters (see get_after_string_run())
|
||||||
|
void append_string_run(std::true_type /*keep*/, const unsigned char* data, std::size_t n)
|
||||||
|
{
|
||||||
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void append_string_run(std::false_type /*keep*/, const unsigned char* /*data*/, std::size_t /*n*/) noexcept {}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/////////////////////
|
/////////////////////
|
||||||
// value getters
|
// value getters
|
||||||
@@ -15283,7 +15319,11 @@ scan_number_done:
|
|||||||
|
|
||||||
// string
|
// string
|
||||||
case '\"':
|
case '\"':
|
||||||
return scan_string();
|
// accept() (discard_values) checks strings without copying them
|
||||||
|
// into token_buffer. The choice is made once per string: a
|
||||||
|
// check inside the string loop made number-heavy documents up
|
||||||
|
// to 3 % slower (mesh.json, Apple M1)
|
||||||
|
return discard_values ? scan_string(std::false_type {}) : scan_string(std::true_type {});
|
||||||
|
|
||||||
// number
|
// number
|
||||||
case '-':
|
case '-':
|
||||||
@@ -15368,11 +15408,13 @@ scan_number_done:
|
|||||||
std::size_t decimal_point_position = std::string::npos;
|
std::size_t decimal_point_position = std::string::npos;
|
||||||
|
|
||||||
/// whether the caller only needs the token types and never looks at the
|
/// whether the caller only needs the token types and never looks at the
|
||||||
/// converted numeric values; set only by accept(), which parses through
|
/// values of number and string tokens; set only by accept(), which parses
|
||||||
/// json_sax_acceptor. When set, convert_number() skips converting integer
|
/// through json_sax_acceptor. When set, convert_number() skips converting
|
||||||
/// tokens whose digit count guarantees that they fit into 64 bits (see
|
/// integer tokens whose digit count guarantees that they fit into 64 bits
|
||||||
/// there)
|
/// (see there), floats are converted only when they may overflow (see
|
||||||
const bool discard_number_values = false;
|
/// convert_accumulated_float()), and strings are checked without being
|
||||||
|
/// copied into token_buffer (see scan())
|
||||||
|
const bool discard_values = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
@@ -22581,9 +22623,9 @@ class parser
|
|||||||
const bool allow_exceptions_ = true,
|
const bool allow_exceptions_ = true,
|
||||||
const bool ignore_comments = false,
|
const bool ignore_comments = false,
|
||||||
const bool ignore_trailing_commas_ = false,
|
const bool ignore_trailing_commas_ = false,
|
||||||
const bool discard_number_values_ = false)
|
const bool discard_values_ = false)
|
||||||
: callback(std::move(cb))
|
: callback(std::move(cb))
|
||||||
, m_lexer(std::move(adapter), ignore_comments, discard_number_values_)
|
, m_lexer(std::move(adapter), ignore_comments, discard_values_)
|
||||||
, allow_exceptions(allow_exceptions_)
|
, allow_exceptions(allow_exceptions_)
|
||||||
, ignore_trailing_commas(ignore_trailing_commas_)
|
, ignore_trailing_commas(ignore_trailing_commas_)
|
||||||
{
|
{
|
||||||
@@ -26870,29 +26912,6 @@ class output_vector_sink
|
|||||||
void flush() noexcept
|
void flush() noexcept
|
||||||
{}
|
{}
|
||||||
|
|
||||||
/// the number of characters written so far, the position of the next one
|
|
||||||
std::size_t written() const noexcept
|
|
||||||
{
|
|
||||||
return position();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief replace @a length characters written before, from position @a pos
|
|
||||||
///
|
|
||||||
/// Lets a writer put a length in front of data it has written since, such
|
|
||||||
/// as the size of a BSON document, without computing it in advance.
|
|
||||||
void overwrite(const std::size_t pos, const CharType* s, const std::size_t length) noexcept
|
|
||||||
{
|
|
||||||
JSON_ASSERT(pos + length <= position());
|
|
||||||
std::memcpy(v->data() + pos, s, length * sizeof(CharType));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// drop the characters written from position @a pos on
|
|
||||||
void rewind(const std::size_t pos) noexcept
|
|
||||||
{
|
|
||||||
JSON_ASSERT(pos <= position());
|
|
||||||
cursor = v->data() + pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// number of characters of *v written so far
|
/// number of characters of *v written so far
|
||||||
std::size_t position() const noexcept
|
std::size_t position() const noexcept
|
||||||
@@ -29337,8 +29356,7 @@ class binary_writer
|
|||||||
/// arrays: the first index with one more digit; only maintained by
|
/// arrays: the first index with one more digit; only maintained by
|
||||||
/// @ref calc_bson_sizes
|
/// @ref calc_bson_sizes
|
||||||
std::uint64_t index_boundary = 10;
|
std::uint64_t index_boundary = 10;
|
||||||
/// @ref calc_bson_sizes: where its size goes in the table; @ref
|
/// @ref calc_bson_sizes only: where its size goes in the table
|
||||||
/// write_bson_document_single_pass: the position of its size field
|
|
||||||
std::size_t size_slot;
|
std::size_t size_slot;
|
||||||
/// @ref calc_bson_sizes only: the size of its entries seen so far
|
/// @ref calc_bson_sizes only: the size of its entries seen so far
|
||||||
std::size_t entries_size = 0;
|
std::size_t entries_size = 0;
|
||||||
@@ -29589,197 +29607,6 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename SinkType>
|
|
||||||
using sink_written_t = decltype(std::declval<const SinkType&>().written());
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief whether BSON documents can be written in a single pass: the sink
|
|
||||||
can overwrite what it has written (output_vector_sink), and every
|
|
||||||
integer fits the 64 bits BSON has, so that writing an array of
|
|
||||||
numbers needs no range check
|
|
||||||
*/
|
|
||||||
using bson_single_pass_sink = std::integral_constant < bool,
|
|
||||||
is_detected<sink_written_t, OutputSinkType>::value
|
|
||||||
&& std::numeric_limits<typename BasicJsonType::number_integer_t>::is_specialized
|
|
||||||
&& std::numeric_limits<typename BasicJsonType::number_integer_t>::digits <= std::numeric_limits<std::int64_t>::digits
|
|
||||||
&& std::numeric_limits<typename BasicJsonType::number_unsigned_t>::is_specialized
|
|
||||||
&& std::numeric_limits<typename BasicJsonType::number_unsigned_t>::digits <= std::numeric_limits<std::uint64_t>::digits >;
|
|
||||||
|
|
||||||
/// write a placeholder for the size of an object or array whose entries
|
|
||||||
/// follow; @return its position, for @ref patch_bson_size
|
|
||||||
std::size_t write_bson_size_placeholder()
|
|
||||||
{
|
|
||||||
const std::size_t pos = oa.written();
|
|
||||||
write_number<std::int32_t>(0, true);
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// end the object or array whose size is at @a pos and write its size
|
|
||||||
/// there: everything from that field to the terminating null byte
|
|
||||||
/// @return false (and the size is not written) if it does not fit
|
|
||||||
bool close_bson_container(const std::size_t pos)
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x00));
|
|
||||||
const std::size_t size = oa.written() - pos;
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(size)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::array<CharType, sizeof(std::int32_t)> bytes{};
|
|
||||||
store_little_endian<std::int32_t>(bytes.data(), static_cast<std::int32_t>(size));
|
|
||||||
oa.overwrite(pos, bytes.data(), bytes.size());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief check the value @a j of a BSON entry as @ref calc_bson_value_size
|
|
||||||
does, for @ref write_bson_document_single_pass
|
|
||||||
@return at least the size of the value
|
|
||||||
A number, boolean, or null needs no check there (its integers have at most
|
|
||||||
64 bits) and has at most 8 characters; a discarded value is rejected with
|
|
||||||
the same exception when it is written.
|
|
||||||
*/
|
|
||||||
std::size_t check_bson_value(const BasicJsonType& j)
|
|
||||||
{
|
|
||||||
return (j.is_string() || j.is_binary()) ? calc_bson_value_size(j) : 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// whether an entry of @a entry_size characters keeps the document that
|
|
||||||
/// begins at @a start, closed by @a open_containers null bytes, within
|
|
||||||
/// the 32-bit size of BSON
|
|
||||||
bool bson_entry_fits(const std::size_t start, const std::size_t entry_size, const std::size_t open_containers) const noexcept
|
|
||||||
{
|
|
||||||
const std::size_t limit = static_cast<std::size_t>((std::numeric_limits<std::int32_t>::max)());
|
|
||||||
const std::size_t written = oa.written() - start;
|
|
||||||
return written <= limit && entry_size <= limit - written && open_containers <= limit - written - entry_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief Serializes the JSON object @a document as a BSON document in a
|
|
||||||
single pass, writing the size of each object and array once it is
|
|
||||||
complete
|
|
||||||
|
|
||||||
Checks every key and value as @ref calc_bson_sizes does, in the same
|
|
||||||
order, so the same exception is thrown; the output written up to then is
|
|
||||||
discarded with the vector it was written to.
|
|
||||||
|
|
||||||
@return false as soon as the document or a string, binary value, object,
|
|
||||||
or array in it would be larger than the 32-bit BSON sizes allow;
|
|
||||||
nothing large has been written then, and the caller writes the
|
|
||||||
document in two passes, which reports that error
|
|
||||||
*/
|
|
||||||
template < typename SinkType = OutputSinkType,
|
|
||||||
typename std::enable_if < is_detected<sink_written_t, SinkType>::value, int >::type = 0 >
|
|
||||||
bool write_bson_document_single_pass(const BasicJsonType& document)
|
|
||||||
{
|
|
||||||
const std::size_t start = oa.written();
|
|
||||||
// the object or array whose entries are being written (with the
|
|
||||||
// position of its size), and the ones it is in
|
|
||||||
bson_frame current(&document, write_bson_size_placeholder());
|
|
||||||
std::vector<bson_frame> parents;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
const BasicJsonType* nested = nullptr;
|
|
||||||
std::size_t nested_size_pos = 0;
|
|
||||||
if (current.value->is_object())
|
|
||||||
{
|
|
||||||
const auto& object = *current.value->m_data.m_value.object;
|
|
||||||
while (nested == nullptr && current.member != object.cend())
|
|
||||||
{
|
|
||||||
const auto& el = *current.member;
|
|
||||||
++current.member;
|
|
||||||
const std::size_t header_size = calc_bson_entry_header_size(el.first, el.second);
|
|
||||||
const std::size_t value_size = el.second.is_structured() ? 0 : check_bson_value(el.second);
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, header_size + value_size, parents.size() + 1)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (el.second.is_structured())
|
|
||||||
{
|
|
||||||
write_bson_entry_header(el.first, el.second.is_object() ? 0x03 : 0x04);
|
|
||||||
nested_size_pos = write_bson_size_placeholder();
|
|
||||||
nested = &el.second;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_bson_value(el.first, el.second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const auto& array = *current.value->m_data.m_value.array;
|
|
||||||
while (nested == nullptr && current.index < array.size())
|
|
||||||
{
|
|
||||||
if (is_bson_fixed_size(array[current.index]))
|
|
||||||
{
|
|
||||||
write_bson_array_fixed_entries(current);
|
|
||||||
if (current.index == array.size())
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// a string, binary value, object or array
|
|
||||||
const BasicJsonType& el = array[current.index];
|
|
||||||
const string_t index_name(current.index_name.data(), current.index_name_size);
|
|
||||||
current.next_index();
|
|
||||||
const std::size_t value_size = el.is_structured() ? 0 : check_bson_value(el);
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, 2 + index_name.size() + value_size, parents.size() + 1)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (el.is_structured())
|
|
||||||
{
|
|
||||||
write_bson_entry_header(index_name, el.is_object() ? 0x03 : 0x04);
|
|
||||||
nested_size_pos = write_bson_size_placeholder();
|
|
||||||
nested = ⪙
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_bson_value(index_name, el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nested != nullptr)
|
|
||||||
{
|
|
||||||
if (nested->is_array())
|
|
||||||
{
|
|
||||||
// an array of booleans, numbers and null is written without a frame on the stack
|
|
||||||
bson_frame leaf(nested, nested_size_pos);
|
|
||||||
write_bson_array_fixed_entries(leaf);
|
|
||||||
if (leaf.index == nested->m_data.m_value.array->size())
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(nested_size_pos)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
parents.push_back(std::move(current));
|
|
||||||
current = std::move(leaf);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
parents.push_back(std::move(current));
|
|
||||||
current = bson_frame(nested, nested_size_pos);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(current.size_slot)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (parents.empty())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
current = std::move(parents.back());
|
|
||||||
parents.pop_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief Serializes the JSON object @a document as a BSON document
|
@brief Serializes the JSON object @a document as a BSON document
|
||||||
|
|
||||||
@@ -29791,39 +29618,6 @@ class binary_writer
|
|||||||
@pre document.type() == value_t::object
|
@pre document.type() == value_t::object
|
||||||
*/
|
*/
|
||||||
void write_bson_document(const BasicJsonType& document)
|
void write_bson_document(const BasicJsonType& document)
|
||||||
{
|
|
||||||
write_bson_document(document, bson_single_pass_sink());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the sink can overwrite: one pass, unless the error handler replaces or
|
|
||||||
/// drops ill-formed UTF-8, which changes the sizes the checks compute
|
|
||||||
void write_bson_document(const BasicJsonType& document, std::true_type /*single_pass*/)
|
|
||||||
{
|
|
||||||
const std::size_t start = oa.written();
|
|
||||||
if ((error_handler == error_handler_t::keep || error_handler == error_handler_t::strict)
|
|
||||||
&& write_bson_document_single_pass(document))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// a length does not fit: start over, so that it is reported as the
|
|
||||||
// two passes report it, before anything is written
|
|
||||||
oa.rewind(start);
|
|
||||||
write_bson_document_two_pass(document);
|
|
||||||
}
|
|
||||||
|
|
||||||
void write_bson_document(const BasicJsonType& document, std::false_type /*single_pass*/)
|
|
||||||
{
|
|
||||||
write_bson_document_two_pass(document);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief Serializes the JSON object @a document as a BSON document: the
|
|
||||||
sizes of all objects and arrays first, then the entries
|
|
||||||
|
|
||||||
@param[in] document the JSON object to serialize
|
|
||||||
@pre document.type() == value_t::object
|
|
||||||
*/
|
|
||||||
void write_bson_document_two_pass(const BasicJsonType& document)
|
|
||||||
{
|
{
|
||||||
std::vector<std::size_t> nested_sizes;
|
std::vector<std::size_t> nested_sizes;
|
||||||
const std::size_t document_size = calc_bson_sizes(document, nested_sizes);
|
const std::size_t document_size = calc_bson_sizes(document, nested_sizes);
|
||||||
@@ -37228,11 +37022,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
const bool allow_exceptions = true,
|
const bool allow_exceptions = true,
|
||||||
const bool ignore_comments = false,
|
const bool ignore_comments = false,
|
||||||
const bool ignore_trailing_commas = false,
|
const bool ignore_trailing_commas = false,
|
||||||
const bool discard_number_values = false
|
const bool discard_values = false
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter),
|
return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter),
|
||||||
std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas, discard_number_values);
|
std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas, discard_values);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -2267,132 +2267,3 @@ TEST_CASE("BSON large strings and binaries (chunked reader)")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("BSON written in one pass and in two passes")
|
|
||||||
{
|
|
||||||
// to_bson() into a returned vector writes each size once the object or
|
|
||||||
// array is complete; through an output adapter, all sizes are computed
|
|
||||||
// first. Both must produce the same bytes and throw the same exceptions.
|
|
||||||
const auto two_pass = [](const json & j, const json::error_handler_t handler)
|
|
||||||
{
|
|
||||||
std::vector<std::uint8_t> result;
|
|
||||||
json::to_bson(j, result, handler);
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto check_same = [&](const json & j, const json::error_handler_t handler)
|
|
||||||
{
|
|
||||||
CAPTURE(j.dump(-1, ' ', true, json::error_handler_t::replace))
|
|
||||||
CAPTURE(static_cast<int>(handler))
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
std::string one_pass_error;
|
|
||||||
std::string two_pass_error;
|
|
||||||
std::vector<std::uint8_t> one;
|
|
||||||
std::vector<std::uint8_t> two;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
one = json::to_bson(j, handler);
|
|
||||||
}
|
|
||||||
catch (const json::exception& e)
|
|
||||||
{
|
|
||||||
one_pass_error = e.what();
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
two = two_pass(j, handler);
|
|
||||||
}
|
|
||||||
catch (const json::exception& e)
|
|
||||||
{
|
|
||||||
two_pass_error = e.what();
|
|
||||||
}
|
|
||||||
CHECK(one_pass_error == two_pass_error);
|
|
||||||
CHECK(one == two);
|
|
||||||
#else
|
|
||||||
CHECK(json::to_bson(j, handler) == two_pass(j, handler));
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
const json::error_handler_t handlers[] = {json::error_handler_t::keep, json::error_handler_t::strict,
|
|
||||||
json::error_handler_t::replace, json::error_handler_t::ignore
|
|
||||||
};
|
|
||||||
|
|
||||||
SECTION("corpus")
|
|
||||||
{
|
|
||||||
for (const auto& value : utils::round_trip_corpus::values())
|
|
||||||
{
|
|
||||||
const json document = value.is_object() ? value : json{{"value", value}};
|
|
||||||
for (const auto handler : handlers)
|
|
||||||
{
|
|
||||||
check_same(document, handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("nesting and arrays of every length class")
|
|
||||||
{
|
|
||||||
json document = json::object();
|
|
||||||
document["empty object"] = json::object();
|
|
||||||
document["empty array"] = json::array();
|
|
||||||
for (const std::size_t n :
|
|
||||||
{
|
|
||||||
std::size_t(1), std::size_t(9), std::size_t(10), std::size_t(11), std::size_t(99), std::size_t(100), std::size_t(101), std::size_t(1000)
|
|
||||||
})
|
|
||||||
{
|
|
||||||
json numbers = json::array();
|
|
||||||
json mixed = json::array();
|
|
||||||
for (std::size_t i = 0; i < n; ++i)
|
|
||||||
{
|
|
||||||
numbers.push_back(i % 3 == 0 ? json(static_cast<double>(i) / 3) : json(static_cast<std::int64_t>(i) * 1000003));
|
|
||||||
mixed.push_back(i % 4 == 0 ? json("s" + std::to_string(i)) : i % 4 == 1 ? json::array({i, json::object({{"k", i}})}) : i % 4 == 2 ? json(nullptr) : json(i % 2 == 0));
|
|
||||||
}
|
|
||||||
document["numbers " + std::to_string(n)] = numbers;
|
|
||||||
document["mixed " + std::to_string(n)] = mixed;
|
|
||||||
document["nested " + std::to_string(n)] = json::object({{"a", numbers}, {"b", json::array({mixed, json::object()})}});
|
|
||||||
}
|
|
||||||
document["binary"] = json::binary({1, 2, 3}, 0x80);
|
|
||||||
document["binary old"] = json::binary({1, 2, 3}, 0x02);
|
|
||||||
for (const auto handler : handlers)
|
|
||||||
{
|
|
||||||
check_same(document, handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("errors at every position")
|
|
||||||
{
|
|
||||||
// an entry that cannot be written, at each place among valid ones
|
|
||||||
const std::string bad_utf8 = "\xC3\x28";
|
|
||||||
const json bad_entries[] =
|
|
||||||
{
|
|
||||||
json::object({{std::string("a\0b", 3), 1}}),
|
|
||||||
json::object({{"s", bad_utf8}}),
|
|
||||||
json::object({{bad_utf8, 1}}),
|
|
||||||
json::array({1, 2, bad_utf8}),
|
|
||||||
json::object({{"b", json::binary({1}, 0x100)}}),
|
|
||||||
json::object({{"d", json(json::value_t::discarded)}}),
|
|
||||||
json::array({json(json::value_t::discarded)}),
|
|
||||||
};
|
|
||||||
for (const auto& bad : bad_entries)
|
|
||||||
{
|
|
||||||
for (std::size_t pos = 0; pos < 4; ++pos)
|
|
||||||
{
|
|
||||||
json document = json::object();
|
|
||||||
json list = json::array();
|
|
||||||
for (std::size_t i = 0; i < 4; ++i)
|
|
||||||
{
|
|
||||||
if (i == pos)
|
|
||||||
{
|
|
||||||
document["x" + std::to_string(i)] = bad;
|
|
||||||
list.push_back(bad);
|
|
||||||
}
|
|
||||||
document["n" + std::to_string(i)] = json::array({i, "str", json::object({{"k", i}})});
|
|
||||||
list.push_back(i);
|
|
||||||
}
|
|
||||||
document["list"] = list;
|
|
||||||
for (const auto handler : handlers)
|
|
||||||
{
|
|
||||||
check_same(document, handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2982,6 +2982,50 @@ TEST_CASE("accept() agrees with parse() on floats around the overflow limit")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("accept() agrees with parse() on strings, which it checks without copying them")
|
||||||
|
{
|
||||||
|
// accept() scans strings and object keys with a separate instantiation of
|
||||||
|
// the string scanner that does not copy them into the token buffer; it
|
||||||
|
// must accept and reject exactly what parse() does
|
||||||
|
const std::vector<std::string> pieces =
|
||||||
|
{
|
||||||
|
"", "abc", "\\\"\\\\\\/\\b\\f\\n\\r\\t", "\\u0041\\u00e4\\u20AC", "\\ud83d\\ude00", "\\uD83D\\uDE00",
|
||||||
|
"\\ud800", "\\udc00", "\\ud800\\u0041", "\\ud800x", "\\udbff\\udfff", "\\u12", "\\u12g4", "\\x", "\\",
|
||||||
|
"\xc3\xa4", "\xe2\x82\xac", "\xf0\x9f\x98\x80", "\xf4\x8f\xbf\xbf", "\xc0\xaf", "\xed\xa0\x80", "\xf4\x90\x80\x80",
|
||||||
|
"\xff", "\xe2\x82", "\xc3", "\x01", "\x1f", "\x7f", "\n", std::string(1, '\0')
|
||||||
|
};
|
||||||
|
|
||||||
|
for (std::size_t run = 0; run <= 70; ++run)
|
||||||
|
{
|
||||||
|
for (const std::string& piece : pieces)
|
||||||
|
{
|
||||||
|
// a run of ordinary characters puts the piece at every offset of the bulk scanners
|
||||||
|
const std::string str = std::string(run, 'a') + piece;
|
||||||
|
for (const std::string& text :
|
||||||
|
{
|
||||||
|
"\"" + str + "\"", "[\"" + str + "\"]", "{\"" + str + "\":1}", "{\"k\":\"" + str + "\",\"" + str + "\":[\"" + str + "\"]}",
|
||||||
|
"[\"" + str
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(text)
|
||||||
|
for (int options = 0; options < 4; ++options)
|
||||||
|
{
|
||||||
|
const bool ignore_comments = (options & 1) != 0;
|
||||||
|
const bool ignore_trailing_commas = (options & 2) != 0;
|
||||||
|
const bool parses = !json::parse(text, nullptr, false, ignore_comments, ignore_trailing_commas).is_discarded();
|
||||||
|
CHECK(json::accept(text, ignore_comments, ignore_trailing_commas) == parses);
|
||||||
|
|
||||||
|
std::istringstream stream(text);
|
||||||
|
CHECK(json::accept(stream, ignore_comments, ignore_trailing_commas) == parses);
|
||||||
|
|
||||||
|
const std::list<char> chars(text.begin(), text.end());
|
||||||
|
CHECK(json::accept(chars.begin(), chars.end(), ignore_comments, ignore_trailing_commas) == parses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// a DOM parser that reports parse errors without throwing, as in the sax_no_exception example
|
// a DOM parser that reports parse errors without throwing, as in the sax_no_exception example
|
||||||
|
|||||||
Reference in new issue
Block a user