mirror of
https://github.com/nlohmann/json.git
synced 2026-10-16 11:17:13 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
649961c8fe | ||
|
|
fe8900bd92 | ||
|
|
48d5e59adf | ||
|
|
547e745289 | ||
|
|
5423dd8de3 | ||
|
|
32ca00cdfa |
No files matched your search
@@ -1625,9 +1625,12 @@ scan_number_done:
|
|||||||
const std::size_t avail = rem + 1;
|
const std::size_t avail = rem + 1;
|
||||||
|
|
||||||
// validate + classify the number extent (mirrors scan_number()'s grammar)
|
// validate + classify the number extent (mirrors scan_number()'s grammar)
|
||||||
|
// and accumulate the mantissa digits on the way, so that tokens with up
|
||||||
|
// to 19 digits need no second pass for their value
|
||||||
std::size_t i = 0;
|
std::size_t i = 0;
|
||||||
std::size_t dot_index = std::string::npos;
|
std::size_t dot_index = std::string::npos;
|
||||||
token_type number_type = token_type::value_unsigned;
|
token_type number_type = token_type::value_unsigned;
|
||||||
|
std::uint64_t w = 0;
|
||||||
if (data[0] == '-')
|
if (data[0] == '-')
|
||||||
{
|
{
|
||||||
number_type = token_type::value_integer;
|
number_type = token_type::value_integer;
|
||||||
@@ -1637,22 +1640,21 @@ scan_number_done:
|
|||||||
return token_type::uninitialized;
|
return token_type::uninitialized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const std::size_t integer_begin = i;
|
||||||
if (data[i] == '0')
|
if (data[i] == '0')
|
||||||
{
|
{
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
else if (data[i] >= '1' && data[i] <= '9')
|
else if (data[i] >= '1' && data[i] <= '9')
|
||||||
{
|
{
|
||||||
++i;
|
i = accumulate_digits(data, i, avail, w);
|
||||||
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
|
||||||
{
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return token_type::uninitialized;
|
return token_type::uninitialized;
|
||||||
}
|
}
|
||||||
|
std::size_t digit_count = i - integer_begin;
|
||||||
|
std::size_t fraction_digits = 0;
|
||||||
if (i < avail && data[i] == '.')
|
if (i < avail && data[i] == '.')
|
||||||
{
|
{
|
||||||
number_type = token_type::value_float;
|
number_type = token_type::value_float;
|
||||||
@@ -1662,29 +1664,39 @@ scan_number_done:
|
|||||||
{
|
{
|
||||||
return token_type::uninitialized;
|
return token_type::uninitialized;
|
||||||
}
|
}
|
||||||
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
const std::size_t fraction_begin = i;
|
||||||
{
|
i = accumulate_digits(data, i, avail, w);
|
||||||
++i;
|
fraction_digits = i - fraction_begin;
|
||||||
}
|
digit_count += fraction_digits;
|
||||||
}
|
}
|
||||||
// the mantissa ends here, whether or not an exponent part follows
|
// the mantissa ends here, whether or not an exponent part follows
|
||||||
const std::size_t mantissa_end = i;
|
const std::size_t mantissa_end = i;
|
||||||
|
std::int64_t exponent = 0;
|
||||||
if (i < avail && (data[i] == 'e' || data[i] == 'E'))
|
if (i < avail && (data[i] == 'e' || data[i] == 'E'))
|
||||||
{
|
{
|
||||||
number_type = token_type::value_float;
|
number_type = token_type::value_float;
|
||||||
++i;
|
++i;
|
||||||
|
bool negative_exponent = false;
|
||||||
if (i < avail && (data[i] == '+' || data[i] == '-'))
|
if (i < avail && (data[i] == '+' || data[i] == '-'))
|
||||||
{
|
{
|
||||||
|
negative_exponent = data[i] == '-';
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
|
if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
|
||||||
{
|
{
|
||||||
return token_type::uninitialized;
|
return token_type::uninitialized;
|
||||||
}
|
}
|
||||||
|
// saturated far beyond every range, see parse_float_exponent()
|
||||||
|
constexpr std::int64_t saturation = 100000000000000000; // 10^17
|
||||||
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||||
{
|
{
|
||||||
|
if (exponent < saturation)
|
||||||
|
{
|
||||||
|
exponent = (exponent * 10) + (data[i] - '0');
|
||||||
|
}
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
exponent = negative_exponent ? -exponent : exponent;
|
||||||
}
|
}
|
||||||
const std::size_t len = i;
|
const std::size_t len = i;
|
||||||
if (number_may_continue(len == avail, std::integral_constant<bool, bulk_window> {}))
|
if (number_may_continue(len == avail, std::integral_constant<bool, bulk_window> {}))
|
||||||
@@ -1705,7 +1717,10 @@ scan_number_done:
|
|||||||
#if !JSON_DIAGNOSTIC_POSITIONS
|
#if !JSON_DIAGNOSTIC_POSITIONS
|
||||||
if (number_type != token_type::value_float)
|
if (number_type != token_type::value_float)
|
||||||
{
|
{
|
||||||
const token_type integer_result = convert_integer(number_type, data, data + len);
|
// up to 18 digits always fit into std::uint64_t and std::int64_t
|
||||||
|
const token_type integer_result = (digit_count <= 18)
|
||||||
|
? store_integer(number_type, w)
|
||||||
|
: convert_integer(number_type, data, data + len);
|
||||||
if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized))
|
if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized))
|
||||||
{
|
{
|
||||||
bulk_consume(data + 1, len - 1);
|
bulk_consume(data + 1, len - 1);
|
||||||
@@ -1719,15 +1734,42 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// materialize the token exactly as scan_number() would. reset() already
|
|
||||||
// cleared token_buffer, so append() fills it (assign() is avoided
|
|
||||||
// because custom string_t types need not provide it)
|
|
||||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
|
||||||
decimal_point_position = dot_index;
|
decimal_point_position = dot_index;
|
||||||
|
|
||||||
bulk_consume(data + 1, len - 1);
|
bulk_consume(data + 1, len - 1);
|
||||||
read_number_terminator(std::integral_constant<bool, bulk_window> {});
|
read_number_terminator(std::integral_constant<bool, bulk_window> {});
|
||||||
|
|
||||||
|
// a float whose digits all fit into w converts from the accumulated
|
||||||
|
// value; longer ones (and other float formats) take the general path
|
||||||
|
if (number_type == token_type::value_float && digit_count <= 19)
|
||||||
|
{
|
||||||
|
float_significand s;
|
||||||
|
s.w = w;
|
||||||
|
s.exponent = exponent - static_cast<std::int64_t>(fraction_digits);
|
||||||
|
s.negative = data[0] == '-';
|
||||||
|
if (convert_accumulated_float(s, std::integral_constant<bool, has_native_float_format<number_float_t>::value> {}))
|
||||||
|
{
|
||||||
|
// the token text is only copied into token_buffer when
|
||||||
|
// get_string() asks for it; the library's own SAX handlers
|
||||||
|
// ignore it. A window into a stream may be refilled by the
|
||||||
|
// next read, so there the text is copied right away.
|
||||||
|
if (bulk_window)
|
||||||
|
{
|
||||||
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pending_number_text = data;
|
||||||
|
pending_number_length = len;
|
||||||
|
}
|
||||||
|
return token_type::value_float;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// materialize the token exactly as scan_number() would. reset() already
|
||||||
|
// cleared token_buffer, so append() fills it (assign() is avoided
|
||||||
|
// because custom string_t types need not provide it)
|
||||||
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
||||||
return convert_number(number_type, mantissa_end);
|
return convert_number(number_type, mantissa_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1756,6 +1798,35 @@ scan_number_done:
|
|||||||
/// the input is not handed back, so the terminator can stay unread
|
/// the input is not handed back, so the terminator can stay unread
|
||||||
void read_number_terminator(std::false_type /*window*/) const noexcept {}
|
void read_number_terminator(std::false_type /*window*/) const noexcept {}
|
||||||
|
|
||||||
|
/// binary32/binary64: the value of a float token from its accumulated
|
||||||
|
/// significand (see scan_number_bulk_contiguous())
|
||||||
|
bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept
|
||||||
|
{
|
||||||
|
value_float = decimal_to_float<number_float_t>(s);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// other formats (e.g. x87 long double): not instantiated for them, as
|
||||||
|
/// decimal_to_float() only supports binary32/binary64
|
||||||
|
static bool convert_accumulated_float(const float_significand& /*s*/, std::false_type /*native*/) noexcept
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the value of an integer token with at most 18 digits, accumulated in @a w
|
||||||
|
token_type store_integer(token_type number_type, std::uint64_t w) noexcept
|
||||||
|
{
|
||||||
|
if (number_type == token_type::value_unsigned)
|
||||||
|
{
|
||||||
|
value_unsigned = static_cast<number_unsigned_t>(w);
|
||||||
|
// reject values that do not round-trip into a narrower type
|
||||||
|
return (static_cast<std::uint64_t>(value_unsigned) == w) ? token_type::value_unsigned : token_type::uninitialized;
|
||||||
|
}
|
||||||
|
const std::int64_t x = -static_cast<std::int64_t>(w);
|
||||||
|
value_integer = static_cast<number_integer_t>(x);
|
||||||
|
return (static_cast<std::int64_t>(value_integer) == x) ? token_type::value_integer : token_type::uninitialized;
|
||||||
|
}
|
||||||
|
|
||||||
/// contiguous input: try the number fast path, else the byte-path scanner
|
/// contiguous input: try the number fast path, else the byte-path scanner
|
||||||
token_type scan_number_dispatch(std::true_type /*bulk*/)
|
token_type scan_number_dispatch(std::true_type /*bulk*/)
|
||||||
{
|
{
|
||||||
@@ -1798,6 +1869,7 @@ scan_number_done:
|
|||||||
void reset() noexcept
|
void reset() noexcept
|
||||||
{
|
{
|
||||||
token_buffer.clear();
|
token_buffer.clear();
|
||||||
|
pending_number_text = nullptr;
|
||||||
decimal_point_position = std::string::npos;
|
decimal_point_position = std::string::npos;
|
||||||
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
@@ -2057,10 +2129,30 @@ scan_number_done:
|
|||||||
/// return current string value
|
/// return current string value
|
||||||
string_t& get_string()
|
string_t& get_string()
|
||||||
{
|
{
|
||||||
|
if (pending_number_text != nullptr)
|
||||||
|
{
|
||||||
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(pending_number_text), pending_number_length);
|
||||||
|
pending_number_text = nullptr;
|
||||||
|
}
|
||||||
// a number token holds '.' regardless of the locale (#4084)
|
// a number token holds '.' regardless of the locale (#4084)
|
||||||
return token_buffer;
|
return token_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// the value of a string token (key or string), which never has pending
|
||||||
|
/// number text, so get_string()'s check is not needed
|
||||||
|
string_t& get_string_token() noexcept
|
||||||
|
{
|
||||||
|
JSON_ASSERT(pending_number_text == nullptr);
|
||||||
|
return token_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the text of a float token for a SAX handler that ignores it (the
|
||||||
|
/// library's own handlers): empty, without copying the token
|
||||||
|
const string_t& get_ignored_number_text() const noexcept
|
||||||
|
{
|
||||||
|
return token_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////
|
/////////////////////
|
||||||
// diagnostics
|
// diagnostics
|
||||||
/////////////////////
|
/////////////////////
|
||||||
@@ -2456,6 +2548,11 @@ scan_number_done:
|
|||||||
/// whether the next get() call should just return current
|
/// whether the next get() call should just return current
|
||||||
bool next_unget = false;
|
bool next_unget = false;
|
||||||
|
|
||||||
|
/// the text of the current float token in the input, not yet copied into
|
||||||
|
/// token_buffer (see get_string()), or nullptr
|
||||||
|
const char* pending_number_text = nullptr;
|
||||||
|
std::size_t pending_number_length = 0;
|
||||||
|
|
||||||
/// the start position of the current token
|
/// the start position of the current token
|
||||||
position_t position {};
|
position_t position {};
|
||||||
|
|
||||||
|
|||||||
@@ -275,13 +275,53 @@ JSON_HEDLEY_ALWAYS_INLINE std::uint32_t parse_eight_digits(std::uint64_t v) noex
|
|||||||
return static_cast<std::uint32_t>(((v & 0x0000FFFF0000FFFFu) * 42949672960001u) >> 32u);
|
return static_cast<std::uint32_t>(((v & 0x0000FFFF0000FFFFu) * 42949672960001u) >> 32u);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// whether [first, last) contains a digit other than '0'
|
/// whether the eight bytes in @a v (see read_eight_bytes()) are all ASCII
|
||||||
inline bool has_nonzero_digit(const char* first, const char* last) noexcept
|
/// digits (after fast_float)
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE bool is_eight_digits(std::uint64_t v) noexcept
|
||||||
{
|
{
|
||||||
return std::any_of(first, last, [](char c) noexcept
|
return ((v & 0xF0F0F0F0F0F0F0F0u) | (((v + 0x0606060606060606u) & 0xF0F0F0F0F0F0F0F0u) >> 4u)) == 0x3333333333333333u;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief skip the ASCII digits starting at data[i] and accumulate their value
|
||||||
|
|
||||||
|
Eight digits are taken at a time while at least eight bytes remain. @a w wraps
|
||||||
|
around after 19 digits; the caller only uses it for shorter digit runs.
|
||||||
|
|
||||||
|
@return the index of the first byte at or after @a i that is not a digit
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE std::size_t accumulate_digits(const char* data, std::size_t i, std::size_t avail, std::uint64_t& w) noexcept
|
||||||
{
|
{
|
||||||
return c != '0';
|
while (avail - i >= 8)
|
||||||
});
|
{
|
||||||
|
const std::uint64_t v = read_eight_bytes(data + i);
|
||||||
|
if (!is_eight_digits(v))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
w = (w * 100000000u) + parse_eight_digits(v);
|
||||||
|
i += 8;
|
||||||
|
}
|
||||||
|
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||||
|
{
|
||||||
|
w = (w * 10u) + static_cast<std::uint64_t>(data[i] - '0');
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// whether [first, last) contains a digit other than '0'; a plain loop, as
|
||||||
|
/// GCC calls std::any_of out of line even for the usual empty range
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE bool has_nonzero_digit(const char* first, const char* last) noexcept
|
||||||
|
{
|
||||||
|
for (; first != last; ++first)
|
||||||
|
{
|
||||||
|
if (*first != '0')
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the value of the validated exponent digits [+-]?[0-9]+ in [first, last),
|
/// the value of the validated exponent digits [+-]?[0-9]+ in [first, last),
|
||||||
@@ -327,7 +367,7 @@ truncated is set.
|
|||||||
std::string::npos if there is none
|
std::string::npos if there is none
|
||||||
@param[in] mantissa_end index of the 'e'/'E', or the token length
|
@param[in] mantissa_end index of the 'e'/'E', or the token length
|
||||||
*/
|
*/
|
||||||
inline float_significand parse_float_significand(const char* first, const char* last,
|
JSON_HEDLEY_ALWAYS_INLINE float_significand parse_float_significand(const char* first, const char* last,
|
||||||
std::size_t decimal_point_position, std::size_t mantissa_end) noexcept
|
std::size_t decimal_point_position, std::size_t mantissa_end) noexcept
|
||||||
{
|
{
|
||||||
float_significand s;
|
float_significand s;
|
||||||
|
|||||||
@@ -51,6 +51,20 @@ template<typename BasicJsonType>
|
|||||||
using parser_callback_t =
|
using parser_callback_t =
|
||||||
std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>;
|
std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>;
|
||||||
|
|
||||||
|
/// whether a SAX handler ignores the text of float tokens (the library's own
|
||||||
|
/// handlers do), so that the parser need not materialize it
|
||||||
|
template<typename SAX>
|
||||||
|
struct sax_ignores_number_text : std::false_type {};
|
||||||
|
|
||||||
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
|
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
||||||
|
|
||||||
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
|
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
||||||
|
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
struct sax_ignores_number_text<json_sax_acceptor<BasicJsonType>> : std::true_type {};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief syntax analysis
|
@brief syntax analysis
|
||||||
|
|
||||||
@@ -171,6 +185,23 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// the text of the current float token for @a sax (see sax_ignores_number_text)
|
||||||
|
template<typename SAX>
|
||||||
|
const string_t& number_float_text(SAX* /*sax*/)
|
||||||
|
{
|
||||||
|
return number_float_text(std::integral_constant<bool, sax_ignores_number_text<SAX>::value> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
const string_t& number_float_text(std::true_type /*ignored*/) const noexcept
|
||||||
|
{
|
||||||
|
return m_lexer.get_ignored_number_text();
|
||||||
|
}
|
||||||
|
|
||||||
|
string_t& number_float_text(std::false_type /*ignored*/)
|
||||||
|
{
|
||||||
|
return m_lexer.get_string();
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief run a DOM SAX parser to completion and position the lexer
|
@brief run a DOM SAX parser to completion and position the lexer
|
||||||
|
|
||||||
@@ -247,7 +278,7 @@ class parser
|
|||||||
{
|
{
|
||||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
||||||
}
|
}
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -301,7 +332,7 @@ class parser
|
|||||||
out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
|
out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, number_float_text(sax))))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -347,7 +378,7 @@ class parser
|
|||||||
|
|
||||||
case token_type::value_string:
|
case token_type::value_string:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string_token())))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -456,7 +487,7 @@ class parser
|
|||||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+192
-24
@@ -10316,13 +10316,53 @@ JSON_HEDLEY_ALWAYS_INLINE std::uint32_t parse_eight_digits(std::uint64_t v) noex
|
|||||||
return static_cast<std::uint32_t>(((v & 0x0000FFFF0000FFFFu) * 42949672960001u) >> 32u);
|
return static_cast<std::uint32_t>(((v & 0x0000FFFF0000FFFFu) * 42949672960001u) >> 32u);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// whether [first, last) contains a digit other than '0'
|
/// whether the eight bytes in @a v (see read_eight_bytes()) are all ASCII
|
||||||
inline bool has_nonzero_digit(const char* first, const char* last) noexcept
|
/// digits (after fast_float)
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE bool is_eight_digits(std::uint64_t v) noexcept
|
||||||
{
|
{
|
||||||
return std::any_of(first, last, [](char c) noexcept
|
return ((v & 0xF0F0F0F0F0F0F0F0u) | (((v + 0x0606060606060606u) & 0xF0F0F0F0F0F0F0F0u) >> 4u)) == 0x3333333333333333u;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief skip the ASCII digits starting at data[i] and accumulate their value
|
||||||
|
|
||||||
|
Eight digits are taken at a time while at least eight bytes remain. @a w wraps
|
||||||
|
around after 19 digits; the caller only uses it for shorter digit runs.
|
||||||
|
|
||||||
|
@return the index of the first byte at or after @a i that is not a digit
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE std::size_t accumulate_digits(const char* data, std::size_t i, std::size_t avail, std::uint64_t& w) noexcept
|
||||||
{
|
{
|
||||||
return c != '0';
|
while (avail - i >= 8)
|
||||||
});
|
{
|
||||||
|
const std::uint64_t v = read_eight_bytes(data + i);
|
||||||
|
if (!is_eight_digits(v))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
w = (w * 100000000u) + parse_eight_digits(v);
|
||||||
|
i += 8;
|
||||||
|
}
|
||||||
|
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||||
|
{
|
||||||
|
w = (w * 10u) + static_cast<std::uint64_t>(data[i] - '0');
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// whether [first, last) contains a digit other than '0'; a plain loop, as
|
||||||
|
/// GCC calls std::any_of out of line even for the usual empty range
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE bool has_nonzero_digit(const char* first, const char* last) noexcept
|
||||||
|
{
|
||||||
|
for (; first != last; ++first)
|
||||||
|
{
|
||||||
|
if (*first != '0')
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the value of the validated exponent digits [+-]?[0-9]+ in [first, last),
|
/// the value of the validated exponent digits [+-]?[0-9]+ in [first, last),
|
||||||
@@ -10368,7 +10408,7 @@ truncated is set.
|
|||||||
std::string::npos if there is none
|
std::string::npos if there is none
|
||||||
@param[in] mantissa_end index of the 'e'/'E', or the token length
|
@param[in] mantissa_end index of the 'e'/'E', or the token length
|
||||||
*/
|
*/
|
||||||
inline float_significand parse_float_significand(const char* first, const char* last,
|
JSON_HEDLEY_ALWAYS_INLINE float_significand parse_float_significand(const char* first, const char* last,
|
||||||
std::size_t decimal_point_position, std::size_t mantissa_end) noexcept
|
std::size_t decimal_point_position, std::size_t mantissa_end) noexcept
|
||||||
{
|
{
|
||||||
float_significand s;
|
float_significand s;
|
||||||
@@ -13071,9 +13111,12 @@ scan_number_done:
|
|||||||
const std::size_t avail = rem + 1;
|
const std::size_t avail = rem + 1;
|
||||||
|
|
||||||
// validate + classify the number extent (mirrors scan_number()'s grammar)
|
// validate + classify the number extent (mirrors scan_number()'s grammar)
|
||||||
|
// and accumulate the mantissa digits on the way, so that tokens with up
|
||||||
|
// to 19 digits need no second pass for their value
|
||||||
std::size_t i = 0;
|
std::size_t i = 0;
|
||||||
std::size_t dot_index = std::string::npos;
|
std::size_t dot_index = std::string::npos;
|
||||||
token_type number_type = token_type::value_unsigned;
|
token_type number_type = token_type::value_unsigned;
|
||||||
|
std::uint64_t w = 0;
|
||||||
if (data[0] == '-')
|
if (data[0] == '-')
|
||||||
{
|
{
|
||||||
number_type = token_type::value_integer;
|
number_type = token_type::value_integer;
|
||||||
@@ -13083,22 +13126,21 @@ scan_number_done:
|
|||||||
return token_type::uninitialized;
|
return token_type::uninitialized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const std::size_t integer_begin = i;
|
||||||
if (data[i] == '0')
|
if (data[i] == '0')
|
||||||
{
|
{
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
else if (data[i] >= '1' && data[i] <= '9')
|
else if (data[i] >= '1' && data[i] <= '9')
|
||||||
{
|
{
|
||||||
++i;
|
i = accumulate_digits(data, i, avail, w);
|
||||||
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
|
||||||
{
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return token_type::uninitialized;
|
return token_type::uninitialized;
|
||||||
}
|
}
|
||||||
|
std::size_t digit_count = i - integer_begin;
|
||||||
|
std::size_t fraction_digits = 0;
|
||||||
if (i < avail && data[i] == '.')
|
if (i < avail && data[i] == '.')
|
||||||
{
|
{
|
||||||
number_type = token_type::value_float;
|
number_type = token_type::value_float;
|
||||||
@@ -13108,29 +13150,39 @@ scan_number_done:
|
|||||||
{
|
{
|
||||||
return token_type::uninitialized;
|
return token_type::uninitialized;
|
||||||
}
|
}
|
||||||
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
const std::size_t fraction_begin = i;
|
||||||
{
|
i = accumulate_digits(data, i, avail, w);
|
||||||
++i;
|
fraction_digits = i - fraction_begin;
|
||||||
}
|
digit_count += fraction_digits;
|
||||||
}
|
}
|
||||||
// the mantissa ends here, whether or not an exponent part follows
|
// the mantissa ends here, whether or not an exponent part follows
|
||||||
const std::size_t mantissa_end = i;
|
const std::size_t mantissa_end = i;
|
||||||
|
std::int64_t exponent = 0;
|
||||||
if (i < avail && (data[i] == 'e' || data[i] == 'E'))
|
if (i < avail && (data[i] == 'e' || data[i] == 'E'))
|
||||||
{
|
{
|
||||||
number_type = token_type::value_float;
|
number_type = token_type::value_float;
|
||||||
++i;
|
++i;
|
||||||
|
bool negative_exponent = false;
|
||||||
if (i < avail && (data[i] == '+' || data[i] == '-'))
|
if (i < avail && (data[i] == '+' || data[i] == '-'))
|
||||||
{
|
{
|
||||||
|
negative_exponent = data[i] == '-';
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
|
if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
|
||||||
{
|
{
|
||||||
return token_type::uninitialized;
|
return token_type::uninitialized;
|
||||||
}
|
}
|
||||||
|
// saturated far beyond every range, see parse_float_exponent()
|
||||||
|
constexpr std::int64_t saturation = 100000000000000000; // 10^17
|
||||||
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||||
{
|
{
|
||||||
|
if (exponent < saturation)
|
||||||
|
{
|
||||||
|
exponent = (exponent * 10) + (data[i] - '0');
|
||||||
|
}
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
exponent = negative_exponent ? -exponent : exponent;
|
||||||
}
|
}
|
||||||
const std::size_t len = i;
|
const std::size_t len = i;
|
||||||
if (number_may_continue(len == avail, std::integral_constant<bool, bulk_window> {}))
|
if (number_may_continue(len == avail, std::integral_constant<bool, bulk_window> {}))
|
||||||
@@ -13151,7 +13203,10 @@ scan_number_done:
|
|||||||
#if !JSON_DIAGNOSTIC_POSITIONS
|
#if !JSON_DIAGNOSTIC_POSITIONS
|
||||||
if (number_type != token_type::value_float)
|
if (number_type != token_type::value_float)
|
||||||
{
|
{
|
||||||
const token_type integer_result = convert_integer(number_type, data, data + len);
|
// up to 18 digits always fit into std::uint64_t and std::int64_t
|
||||||
|
const token_type integer_result = (digit_count <= 18)
|
||||||
|
? store_integer(number_type, w)
|
||||||
|
: convert_integer(number_type, data, data + len);
|
||||||
if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized))
|
if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized))
|
||||||
{
|
{
|
||||||
bulk_consume(data + 1, len - 1);
|
bulk_consume(data + 1, len - 1);
|
||||||
@@ -13165,15 +13220,42 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// materialize the token exactly as scan_number() would. reset() already
|
|
||||||
// cleared token_buffer, so append() fills it (assign() is avoided
|
|
||||||
// because custom string_t types need not provide it)
|
|
||||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
|
||||||
decimal_point_position = dot_index;
|
decimal_point_position = dot_index;
|
||||||
|
|
||||||
bulk_consume(data + 1, len - 1);
|
bulk_consume(data + 1, len - 1);
|
||||||
read_number_terminator(std::integral_constant<bool, bulk_window> {});
|
read_number_terminator(std::integral_constant<bool, bulk_window> {});
|
||||||
|
|
||||||
|
// a float whose digits all fit into w converts from the accumulated
|
||||||
|
// value; longer ones (and other float formats) take the general path
|
||||||
|
if (number_type == token_type::value_float && digit_count <= 19)
|
||||||
|
{
|
||||||
|
float_significand s;
|
||||||
|
s.w = w;
|
||||||
|
s.exponent = exponent - static_cast<std::int64_t>(fraction_digits);
|
||||||
|
s.negative = data[0] == '-';
|
||||||
|
if (convert_accumulated_float(s, std::integral_constant<bool, has_native_float_format<number_float_t>::value> {}))
|
||||||
|
{
|
||||||
|
// the token text is only copied into token_buffer when
|
||||||
|
// get_string() asks for it; the library's own SAX handlers
|
||||||
|
// ignore it. A window into a stream may be refilled by the
|
||||||
|
// next read, so there the text is copied right away.
|
||||||
|
if (bulk_window)
|
||||||
|
{
|
||||||
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pending_number_text = data;
|
||||||
|
pending_number_length = len;
|
||||||
|
}
|
||||||
|
return token_type::value_float;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// materialize the token exactly as scan_number() would. reset() already
|
||||||
|
// cleared token_buffer, so append() fills it (assign() is avoided
|
||||||
|
// because custom string_t types need not provide it)
|
||||||
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
||||||
return convert_number(number_type, mantissa_end);
|
return convert_number(number_type, mantissa_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13202,6 +13284,35 @@ scan_number_done:
|
|||||||
/// the input is not handed back, so the terminator can stay unread
|
/// the input is not handed back, so the terminator can stay unread
|
||||||
void read_number_terminator(std::false_type /*window*/) const noexcept {}
|
void read_number_terminator(std::false_type /*window*/) const noexcept {}
|
||||||
|
|
||||||
|
/// binary32/binary64: the value of a float token from its accumulated
|
||||||
|
/// significand (see scan_number_bulk_contiguous())
|
||||||
|
bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept
|
||||||
|
{
|
||||||
|
value_float = decimal_to_float<number_float_t>(s);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// other formats (e.g. x87 long double): not instantiated for them, as
|
||||||
|
/// decimal_to_float() only supports binary32/binary64
|
||||||
|
static bool convert_accumulated_float(const float_significand& /*s*/, std::false_type /*native*/) noexcept
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the value of an integer token with at most 18 digits, accumulated in @a w
|
||||||
|
token_type store_integer(token_type number_type, std::uint64_t w) noexcept
|
||||||
|
{
|
||||||
|
if (number_type == token_type::value_unsigned)
|
||||||
|
{
|
||||||
|
value_unsigned = static_cast<number_unsigned_t>(w);
|
||||||
|
// reject values that do not round-trip into a narrower type
|
||||||
|
return (static_cast<std::uint64_t>(value_unsigned) == w) ? token_type::value_unsigned : token_type::uninitialized;
|
||||||
|
}
|
||||||
|
const std::int64_t x = -static_cast<std::int64_t>(w);
|
||||||
|
value_integer = static_cast<number_integer_t>(x);
|
||||||
|
return (static_cast<std::int64_t>(value_integer) == x) ? token_type::value_integer : token_type::uninitialized;
|
||||||
|
}
|
||||||
|
|
||||||
/// contiguous input: try the number fast path, else the byte-path scanner
|
/// contiguous input: try the number fast path, else the byte-path scanner
|
||||||
token_type scan_number_dispatch(std::true_type /*bulk*/)
|
token_type scan_number_dispatch(std::true_type /*bulk*/)
|
||||||
{
|
{
|
||||||
@@ -13244,6 +13355,7 @@ scan_number_done:
|
|||||||
void reset() noexcept
|
void reset() noexcept
|
||||||
{
|
{
|
||||||
token_buffer.clear();
|
token_buffer.clear();
|
||||||
|
pending_number_text = nullptr;
|
||||||
decimal_point_position = std::string::npos;
|
decimal_point_position = std::string::npos;
|
||||||
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
@@ -13503,10 +13615,30 @@ scan_number_done:
|
|||||||
/// return current string value
|
/// return current string value
|
||||||
string_t& get_string()
|
string_t& get_string()
|
||||||
{
|
{
|
||||||
|
if (pending_number_text != nullptr)
|
||||||
|
{
|
||||||
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(pending_number_text), pending_number_length);
|
||||||
|
pending_number_text = nullptr;
|
||||||
|
}
|
||||||
// a number token holds '.' regardless of the locale (#4084)
|
// a number token holds '.' regardless of the locale (#4084)
|
||||||
return token_buffer;
|
return token_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// the value of a string token (key or string), which never has pending
|
||||||
|
/// number text, so get_string()'s check is not needed
|
||||||
|
string_t& get_string_token() noexcept
|
||||||
|
{
|
||||||
|
JSON_ASSERT(pending_number_text == nullptr);
|
||||||
|
return token_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the text of a float token for a SAX handler that ignores it (the
|
||||||
|
/// library's own handlers): empty, without copying the token
|
||||||
|
const string_t& get_ignored_number_text() const noexcept
|
||||||
|
{
|
||||||
|
return token_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////
|
/////////////////////
|
||||||
// diagnostics
|
// diagnostics
|
||||||
/////////////////////
|
/////////////////////
|
||||||
@@ -13902,6 +14034,11 @@ scan_number_done:
|
|||||||
/// whether the next get() call should just return current
|
/// whether the next get() call should just return current
|
||||||
bool next_unget = false;
|
bool next_unget = false;
|
||||||
|
|
||||||
|
/// the text of the current float token in the input, not yet copied into
|
||||||
|
/// token_buffer (see get_string()), or nullptr
|
||||||
|
const char* pending_number_text = nullptr;
|
||||||
|
std::size_t pending_number_length = 0;
|
||||||
|
|
||||||
/// the start position of the current token
|
/// the start position of the current token
|
||||||
position_t position {};
|
position_t position {};
|
||||||
|
|
||||||
@@ -19770,6 +19907,20 @@ template<typename BasicJsonType>
|
|||||||
using parser_callback_t =
|
using parser_callback_t =
|
||||||
std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>;
|
std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>;
|
||||||
|
|
||||||
|
/// whether a SAX handler ignores the text of float tokens (the library's own
|
||||||
|
/// handlers do), so that the parser need not materialize it
|
||||||
|
template<typename SAX>
|
||||||
|
struct sax_ignores_number_text : std::false_type {};
|
||||||
|
|
||||||
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
|
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
||||||
|
|
||||||
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
|
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
||||||
|
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
struct sax_ignores_number_text<json_sax_acceptor<BasicJsonType>> : std::true_type {};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief syntax analysis
|
@brief syntax analysis
|
||||||
|
|
||||||
@@ -19890,6 +20041,23 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// the text of the current float token for @a sax (see sax_ignores_number_text)
|
||||||
|
template<typename SAX>
|
||||||
|
const string_t& number_float_text(SAX* /*sax*/)
|
||||||
|
{
|
||||||
|
return number_float_text(std::integral_constant<bool, sax_ignores_number_text<SAX>::value> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
const string_t& number_float_text(std::true_type /*ignored*/) const noexcept
|
||||||
|
{
|
||||||
|
return m_lexer.get_ignored_number_text();
|
||||||
|
}
|
||||||
|
|
||||||
|
string_t& number_float_text(std::false_type /*ignored*/)
|
||||||
|
{
|
||||||
|
return m_lexer.get_string();
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief run a DOM SAX parser to completion and position the lexer
|
@brief run a DOM SAX parser to completion and position the lexer
|
||||||
|
|
||||||
@@ -19966,7 +20134,7 @@ class parser
|
|||||||
{
|
{
|
||||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
||||||
}
|
}
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -20020,7 +20188,7 @@ class parser
|
|||||||
out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
|
out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, number_float_text(sax))))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -20066,7 +20234,7 @@ class parser
|
|||||||
|
|
||||||
case token_type::value_string:
|
case token_type::value_string:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string_token())))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -20175,7 +20343,7 @@ class parser
|
|||||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,12 @@ using nlohmann::json;
|
|||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <cstdint> // uint32_t, uint64_t
|
#include <cstdint> // uint32_t, uint64_t
|
||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
|
#include <cmath> // isfinite
|
||||||
#include <cstdlib> // strtod
|
#include <cstdlib> // strtod
|
||||||
#include <cstring> // memcpy
|
#include <cstring> // memcpy
|
||||||
#include <limits> // numeric_limits
|
#include <limits> // numeric_limits
|
||||||
|
#include <initializer_list> // initializer_list
|
||||||
|
#include <list> // list
|
||||||
#include <map> // map
|
#include <map> // map
|
||||||
#include <random> // mt19937
|
#include <random> // mt19937
|
||||||
#include <sstream> // stringstream
|
#include <sstream> // stringstream
|
||||||
@@ -332,6 +335,109 @@ TEST_CASE("lexer number fast path")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("digits accumulated while scanning")
|
||||||
|
{
|
||||||
|
// The fast path accumulates the digits while it validates them, eight
|
||||||
|
// at a time where possible, and converts tokens with at most 19
|
||||||
|
// digits (floats) or 18 digits (integers) from that value. Every digit
|
||||||
|
// count from 1 to 25, in the integer and in the fraction part, with
|
||||||
|
// and without sign and exponent, must give the byte path's result. A
|
||||||
|
// std::list has no contiguous storage, so parsing it takes the byte
|
||||||
|
// path.
|
||||||
|
// type and value, or the error (values beyond the range of double
|
||||||
|
// are rejected with out_of_range.406 on both paths)
|
||||||
|
const auto outcome = [](const std::string & doc, bool byte_path) -> std::string
|
||||||
|
{
|
||||||
|
JSON_TRY
|
||||||
|
{
|
||||||
|
const std::list<char> chars(doc.begin(), doc.end());
|
||||||
|
const json j = byte_path ? json::parse(chars.begin(), chars.end()) : json::parse(doc);
|
||||||
|
return std::string(j[0].type_name()) + '|' + j.dump();
|
||||||
|
}
|
||||||
|
JSON_CATCH (const json::exception & e)
|
||||||
|
{
|
||||||
|
return e.what();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// the concatenation of @a parts
|
||||||
|
const auto join = [](std::initializer_list<std::string> parts)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
for (const auto& part : parts)
|
||||||
|
{
|
||||||
|
result += part;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> numbers;
|
||||||
|
for (std::size_t digits = 1; digits <= 25; ++digits)
|
||||||
|
{
|
||||||
|
std::string run;
|
||||||
|
for (std::size_t i = 0; i < digits; ++i)
|
||||||
|
{
|
||||||
|
run += static_cast<char>('1' + static_cast<char>((i * 7) % 9));
|
||||||
|
}
|
||||||
|
const std::string nines(digits, '9');
|
||||||
|
for (const char* sign_text :
|
||||||
|
{"", "-"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
const std::string sign = sign_text;
|
||||||
|
numbers.push_back(join({sign, run}));
|
||||||
|
numbers.push_back(join({sign, nines}));
|
||||||
|
numbers.push_back(join({sign, "1", std::string(digits - 1, '0')}));
|
||||||
|
numbers.push_back(join({sign, "0.", run}));
|
||||||
|
numbers.push_back(join({sign, "0.", std::string(digits, '0'), "1"}));
|
||||||
|
numbers.push_back(join({sign, run, ".", run}));
|
||||||
|
numbers.push_back(join({sign, run, "e-", std::to_string(digits)}));
|
||||||
|
numbers.push_back(join({sign, "1.", run, "E+", std::to_string(digits * 13)}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// exponents beyond every range, and zeros
|
||||||
|
numbers.insert(numbers.end(), {"1e400", "1e-400", "-1e99999999999999999999", "1e-99999999999999999999",
|
||||||
|
"0e99999999999999999999", "0.0e-5", "-0.000", "123456789e-0000000000000000000000330"
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const auto& n : numbers)
|
||||||
|
{
|
||||||
|
CAPTURE(n)
|
||||||
|
const std::string doc = "[" + n + "]";
|
||||||
|
const std::string a = outcome(doc, false);
|
||||||
|
CHECK(a == outcome(doc, true));
|
||||||
|
const double expected = std::strtod(n.c_str(), nullptr);
|
||||||
|
if (a.compare(0, 6, "number") == 0 && std::isfinite(expected) && n.find_first_of(".eE") != std::string::npos)
|
||||||
|
{
|
||||||
|
CHECK(json::parse(doc)[0].get<double>() == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("integers that do not fit a narrower type become floats")
|
||||||
|
{
|
||||||
|
// up to 18 digits, the fast path stores the accumulated value without
|
||||||
|
// an overflow check; a narrower number_integer_t/number_unsigned_t
|
||||||
|
// must still see the value rejected and converted as a float
|
||||||
|
using json32 = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int32_t, std::uint32_t, float>;
|
||||||
|
for (const char* n :
|
||||||
|
{"4294967295", "4294967296", "2147483647", "-2147483648", "-2147483649", "99999999999", "-99999999999"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(n)
|
||||||
|
const std::string doc = std::string("[") + n + "]";
|
||||||
|
const std::list<char> chars(doc.begin(), doc.end());
|
||||||
|
const json32 a = json32::parse(doc);
|
||||||
|
const json32 b = json32::parse(chars.begin(), chars.end());
|
||||||
|
CHECK(a[0].type() == b[0].type());
|
||||||
|
CHECK(a == b);
|
||||||
|
}
|
||||||
|
CHECK(json32::parse("[4294967295]")[0].is_number_unsigned());
|
||||||
|
CHECK(json32::parse("[4294967296]")[0].is_number_float());
|
||||||
|
CHECK(json32::parse("[-2147483648]")[0].is_number_integer());
|
||||||
|
CHECK(json32::parse("[-2147483649]")[0].is_number_float());
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("token type classification")
|
SECTION("token type classification")
|
||||||
{
|
{
|
||||||
CHECK((scan_string("0") == json::lexer::token_type::value_unsigned));
|
CHECK((scan_string("0") == json::lexer::token_type::value_unsigned));
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ using nlohmann::json;
|
|||||||
#include <valarray>
|
#include <valarray>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstdint>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <iterator>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -2450,6 +2452,79 @@ std::string parse_error_message_range(IteratorType first, IteratorType last)
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("text of float tokens")
|
||||||
|
{
|
||||||
|
// For contiguous input, the lexer copies the text of a float token into
|
||||||
|
// its buffer only when a SAX handler asks for it (the library's own
|
||||||
|
// handlers do not). A user handler must still see the exact text, for
|
||||||
|
// every input adapter and in every position of a document.
|
||||||
|
const std::string doc = R"([1.5, -0.0, 0.1, 1e5, -2.5E-4, 12345678901234567890.5, 0.12345678901234567890123,
|
||||||
|
{"a": 3.25, "b": [6.02e23, 1]}, "x", true, 7e-1])";
|
||||||
|
const std::vector<std::string> expected_floats =
|
||||||
|
{
|
||||||
|
"number_float(1.5)", "number_float(-0.0)", "number_float(0.1)", "number_float(1e5)", "number_float(-2.5E-4)",
|
||||||
|
"number_float(12345678901234567890.5)", "number_float(0.12345678901234567890123)", "number_float(3.25)",
|
||||||
|
"number_float(6.02e23)", "number_float(7e-1)"
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto floats = [](const std::vector<std::string>& events)
|
||||||
|
{
|
||||||
|
std::vector<std::string> result;
|
||||||
|
std::copy_if(events.begin(), events.end(), std::back_inserter(result), [](const std::string & e)
|
||||||
|
{
|
||||||
|
return e.compare(0, 13, "number_float(") == 0;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
SECTION("contiguous input")
|
||||||
|
{
|
||||||
|
SaxEventLogger l;
|
||||||
|
CHECK(json::sax_parse(doc, &l));
|
||||||
|
CHECK(floats(l.events) == expected_floats);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("byte input")
|
||||||
|
{
|
||||||
|
const std::vector<std::uint8_t> bytes(doc.begin(), doc.end());
|
||||||
|
SaxEventLogger l;
|
||||||
|
CHECK(json::sax_parse(bytes, &l));
|
||||||
|
CHECK(floats(l.events) == expected_floats);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("stream input")
|
||||||
|
{
|
||||||
|
std::istringstream is(doc);
|
||||||
|
SaxEventLogger l;
|
||||||
|
CHECK(json::sax_parse(is, &l));
|
||||||
|
CHECK(floats(l.events) == expected_floats);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("non-contiguous iterators")
|
||||||
|
{
|
||||||
|
const std::list<char> chars(doc.begin(), doc.end());
|
||||||
|
SaxEventLogger l;
|
||||||
|
CHECK(json::sax_parse(chars.begin(), chars.end(), &l));
|
||||||
|
CHECK(floats(l.events) == expected_floats);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("the library's own handlers do not need the text")
|
||||||
|
{
|
||||||
|
using lexer_adapter = nlohmann::detail::iterator_input_adapter<const char*>;
|
||||||
|
CHECK(nlohmann::detail::sax_ignores_number_text<nlohmann::detail::json_sax_dom_parser<json, lexer_adapter>>::value);
|
||||||
|
CHECK(nlohmann::detail::sax_ignores_number_text<nlohmann::detail::json_sax_dom_callback_parser<json, lexer_adapter>>::value);
|
||||||
|
CHECK(nlohmann::detail::sax_ignores_number_text<nlohmann::detail::json_sax_acceptor<json>>::value);
|
||||||
|
CHECK_FALSE(nlohmann::detail::sax_ignores_number_text<SaxEventLogger>::value);
|
||||||
|
|
||||||
|
// and the values are the same as when the text is read
|
||||||
|
const json j = json::parse(doc);
|
||||||
|
CHECK(j[0] == 1.5);
|
||||||
|
CHECK(j[5].get<double>() == 12345678901234567890.5);
|
||||||
|
CHECK(j[7]["b"][0] == 6.02e23);
|
||||||
|
CHECK(json::accept(doc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("last-read diagnostics are identical across input adapters")
|
TEST_CASE("last-read diagnostics are identical across input adapters")
|
||||||
{
|
{
|
||||||
// The lexer reconstructs the "last read" token lazily for seekable adapters
|
// The lexer reconstructs the "last read" token lazily for seekable adapters
|
||||||
|
|||||||
Reference in new issue
Block a user