mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
884 lines
30 KiB
C++
884 lines
30 KiB
C++
#include <array>
|
|
#include <cstring>
|
|
#include <type_traits>
|
|
#ifndef SIMDJSON_GENERIC_STRING_BUILDER_INL_H
|
|
|
|
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
|
|
#define SIMDJSON_GENERIC_STRING_BUILDER_INL_H
|
|
#include "simdjson/generic/builder/json_string_builder.h"
|
|
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
|
|
|
/*
|
|
* Empirically, we have found that an inlined optimization is important for
|
|
* performance. The following macros are not ideal. We should find a better
|
|
* way to inline the code.
|
|
*/
|
|
|
|
#if defined(__SSE2__) || defined(__x86_64__) || defined(__x86_64) || \
|
|
(defined(_M_AMD64) || defined(_M_X64) || \
|
|
(defined(_M_IX86_FP) && _M_IX86_FP == 2))
|
|
#ifndef SIMDJSON_EXPERIMENTAL_HAS_SSE2
|
|
#define SIMDJSON_EXPERIMENTAL_HAS_SSE2 1
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(__aarch64__) || defined(_M_ARM64)
|
|
#ifndef SIMDJSON_EXPERIMENTAL_HAS_NEON
|
|
#define SIMDJSON_EXPERIMENTAL_HAS_NEON 1
|
|
#endif
|
|
#endif
|
|
#if defined(__loongarch_sx)
|
|
#ifndef SIMDJSON_EXPERIMENTAL_HAS_LSX
|
|
#define SIMDJSON_EXPERIMENTAL_HAS_LSX 1
|
|
#endif
|
|
#endif
|
|
#if SIMDJSON_EXPERIMENTAL_HAS_NEON
|
|
#include <arm_neon.h>
|
|
#ifdef _MSC_VER
|
|
#include <intrin.h>
|
|
#endif
|
|
#endif
|
|
#if SIMDJSON_EXPERIMENTAL_HAS_SSE2
|
|
#include <emmintrin.h>
|
|
#ifdef _MSC_VER
|
|
#include <intrin.h>
|
|
#endif
|
|
#endif
|
|
#if SIMDJSON_EXPERIMENTAL_HAS_LSX
|
|
#include <lsxintrin.h>
|
|
#endif
|
|
|
|
|
|
namespace simdjson {
|
|
namespace SIMDJSON_IMPLEMENTATION {
|
|
namespace builder {
|
|
|
|
static SIMDJSON_CONSTEXPR_LAMBDA std::array<uint8_t, 256>
|
|
json_quotable_character = {
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
|
|
/**
|
|
|
|
A possible SWAR implementation of has_json_escapable_byte. It is not used
|
|
because it is slower than the current implementation. It is kept here for
|
|
reference (to show that we tried it).
|
|
|
|
inline bool has_json_escapable_byte(uint64_t x) {
|
|
uint64_t is_ascii = 0x8080808080808080ULL & ~x;
|
|
uint64_t xor2 = x ^ 0x0202020202020202ULL;
|
|
uint64_t lt32_or_eq34 = xor2 - 0x2121212121212121ULL;
|
|
uint64_t sub92 = x ^ 0x5C5C5C5C5C5C5C5CULL;
|
|
uint64_t eq92 = (sub92 - 0x0101010101010101ULL);
|
|
return ((lt32_or_eq34 | eq92) & is_ascii) != 0;
|
|
}
|
|
|
|
**/
|
|
|
|
SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline bool
|
|
simple_needs_escaping(std::string_view v) {
|
|
for (char c : v) {
|
|
// a table lookup is faster than a series of comparisons
|
|
if (json_quotable_character[static_cast<uint8_t>(c)]) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#if SIMDJSON_EXPERIMENTAL_HAS_NEON
|
|
simdjson_inline bool fast_needs_escaping(std::string_view view) {
|
|
if (view.size() < 16) {
|
|
return simple_needs_escaping(view);
|
|
}
|
|
size_t i = 0;
|
|
uint8x16_t running = vdupq_n_u8(0);
|
|
uint8x16_t v34 = vdupq_n_u8(34);
|
|
uint8x16_t v92 = vdupq_n_u8(92);
|
|
|
|
for (; i + 15 < view.size(); i += 16) {
|
|
uint8x16_t word = vld1q_u8((const uint8_t *)view.data() + i);
|
|
running = vorrq_u8(running, vceqq_u8(word, v34));
|
|
running = vorrq_u8(running, vceqq_u8(word, v92));
|
|
running = vorrq_u8(running, vcltq_u8(word, vdupq_n_u8(32)));
|
|
}
|
|
if (i < view.size()) {
|
|
uint8x16_t word =
|
|
vld1q_u8((const uint8_t *)view.data() + view.length() - 16);
|
|
running = vorrq_u8(running, vceqq_u8(word, v34));
|
|
running = vorrq_u8(running, vceqq_u8(word, v92));
|
|
running = vorrq_u8(running, vcltq_u8(word, vdupq_n_u8(32)));
|
|
}
|
|
return vmaxvq_u32(vreinterpretq_u32_u8(running)) != 0;
|
|
}
|
|
#elif SIMDJSON_EXPERIMENTAL_HAS_SSE2
|
|
simdjson_inline bool fast_needs_escaping(std::string_view view) {
|
|
if (view.size() < 16) {
|
|
return simple_needs_escaping(view);
|
|
}
|
|
size_t i = 0;
|
|
__m128i running = _mm_setzero_si128();
|
|
for (; i + 15 < view.size(); i += 16) {
|
|
|
|
__m128i word =
|
|
_mm_loadu_si128(reinterpret_cast<const __m128i *>(view.data() + i));
|
|
running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(34)));
|
|
running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(92)));
|
|
running = _mm_or_si128(
|
|
running, _mm_cmpeq_epi8(_mm_subs_epu8(word, _mm_set1_epi8(31)),
|
|
_mm_setzero_si128()));
|
|
}
|
|
if (i < view.size()) {
|
|
__m128i word = _mm_loadu_si128(
|
|
reinterpret_cast<const __m128i *>(view.data() + view.length() - 16));
|
|
running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(34)));
|
|
running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(92)));
|
|
running = _mm_or_si128(
|
|
running, _mm_cmpeq_epi8(_mm_subs_epu8(word, _mm_set1_epi8(31)),
|
|
_mm_setzero_si128()));
|
|
}
|
|
return _mm_movemask_epi8(running) != 0;
|
|
}
|
|
#else
|
|
simdjson_inline bool fast_needs_escaping(std::string_view view) {
|
|
return simple_needs_escaping(view);
|
|
}
|
|
#endif
|
|
|
|
// Scalar fallback for finding next quotable character
|
|
SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline size_t
|
|
find_next_json_quotable_character_scalar(const std::string_view view,
|
|
size_t location) noexcept {
|
|
for (auto pos = view.begin() + location; pos != view.end(); ++pos) {
|
|
if (json_quotable_character[static_cast<uint8_t>(*pos)]) {
|
|
return pos - view.begin();
|
|
}
|
|
}
|
|
return size_t(view.size());
|
|
}
|
|
|
|
// SIMD-accelerated position finding that directly locates the first quotable
|
|
// character, combining detection and position extraction in a single pass to
|
|
// minimize redundant work.
|
|
#if SIMDJSON_EXPERIMENTAL_HAS_NEON
|
|
simdjson_inline size_t
|
|
find_next_json_quotable_character(const std::string_view view,
|
|
size_t location) noexcept {
|
|
const size_t len = view.size();
|
|
const uint8_t *ptr =
|
|
reinterpret_cast<const uint8_t *>(view.data()) + location;
|
|
size_t remaining = len - location;
|
|
|
|
// SIMD constants for characters requiring escape
|
|
uint8x16_t v34 = vdupq_n_u8(34); // '"'
|
|
uint8x16_t v92 = vdupq_n_u8(92); // '\\'
|
|
uint8x16_t v32 = vdupq_n_u8(32); // control char threshold
|
|
|
|
while (remaining >= 16) {
|
|
uint8x16_t word = vld1q_u8(ptr);
|
|
|
|
// Check for quotable characters: '"', '\\', or control chars (< 32)
|
|
uint8x16_t needs_escape = vceqq_u8(word, v34);
|
|
needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92));
|
|
needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32));
|
|
|
|
const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4);
|
|
const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0);
|
|
if(mask != 0) {
|
|
size_t offset = ptr - reinterpret_cast<const uint8_t *>(view.data());
|
|
auto trailing_zero = trailing_zeroes(mask);
|
|
return offset + (trailing_zero >> 2);
|
|
}
|
|
ptr += 16;
|
|
remaining -= 16;
|
|
}
|
|
|
|
// Scalar fallback for remaining bytes
|
|
size_t current = len - remaining;
|
|
return find_next_json_quotable_character_scalar(view, current);
|
|
}
|
|
#elif SIMDJSON_EXPERIMENTAL_HAS_SSE2
|
|
simdjson_inline size_t
|
|
find_next_json_quotable_character(const std::string_view view,
|
|
size_t location) noexcept {
|
|
const size_t len = view.size();
|
|
const uint8_t *ptr =
|
|
reinterpret_cast<const uint8_t *>(view.data()) + location;
|
|
size_t remaining = len - location;
|
|
|
|
// SIMD constants
|
|
__m128i v34 = _mm_set1_epi8(34); // '"'
|
|
__m128i v92 = _mm_set1_epi8(92); // '\\'
|
|
__m128i v31 = _mm_set1_epi8(31); // for control char detection
|
|
|
|
while (remaining >= 16) {
|
|
__m128i word = _mm_loadu_si128(reinterpret_cast<const __m128i *>(ptr));
|
|
|
|
// Check for quotable characters
|
|
__m128i needs_escape = _mm_cmpeq_epi8(word, v34);
|
|
needs_escape = _mm_or_si128(needs_escape, _mm_cmpeq_epi8(word, v92));
|
|
needs_escape = _mm_or_si128(
|
|
needs_escape,
|
|
_mm_cmpeq_epi8(_mm_subs_epu8(word, v31), _mm_setzero_si128()));
|
|
|
|
int mask = _mm_movemask_epi8(needs_escape);
|
|
if (mask != 0) {
|
|
// Found quotable character - use trailing zero count to find position
|
|
size_t offset = ptr - reinterpret_cast<const uint8_t *>(view.data());
|
|
#ifdef _MSC_VER
|
|
unsigned long trailing_zero = 0;
|
|
_BitScanForward(&trailing_zero, mask);
|
|
return offset + trailing_zero;
|
|
#else
|
|
return offset + __builtin_ctz(mask);
|
|
#endif
|
|
}
|
|
ptr += 16;
|
|
remaining -= 16;
|
|
}
|
|
|
|
// Scalar fallback for remaining bytes
|
|
size_t current = len - remaining;
|
|
return find_next_json_quotable_character_scalar(view, current);
|
|
}
|
|
#elif SIMDJSON_EXPERIMENTAL_HAS_LSX
|
|
simdjson_inline size_t
|
|
find_next_json_quotable_character(const std::string_view view,
|
|
size_t location) noexcept {
|
|
const size_t len = view.size();
|
|
const uint8_t *ptr =
|
|
reinterpret_cast<const uint8_t *>(view.data()) + location;
|
|
size_t remaining = len - location;
|
|
|
|
//SIMD constants for characters requiring escape
|
|
__m128i v34 = __lsx_vreplgr2vr_b(34); // '"'
|
|
__m128i v92 = __lsx_vreplgr2vr_b(92); // '\\'
|
|
__m128i v32 = __lsx_vreplgr2vr_b(32); // control char threshold
|
|
|
|
while (remaining >= 16){
|
|
__m128i word = __lsx_vld(ptr, 0);
|
|
|
|
//Check for the quotable characters: '"', '\\', or control char (<32)
|
|
__m128i needs_escape = __lsx_vseq_b(word, v34);
|
|
needs_escape = __lsx_vor_v(needs_escape, __lsx_vseq_b(word, v92));
|
|
needs_escape = __lsx_vor_v(needs_escape, __lsx_vslt_bu(word, v32));
|
|
|
|
if (!__lsx_bz_v(needs_escape)){
|
|
|
|
//Found quotable character - extract exact byte position
|
|
uint64_t lo = __lsx_vpickve2gr_du(needs_escape,0);
|
|
uint64_t hi = __lsx_vpickve2gr_du(needs_escape,1);
|
|
size_t offset = ptr - reinterpret_cast<const uint8_t *>(view.data());
|
|
if ( lo != 0) {
|
|
return offset + __builtin_ctzll(lo) / 8;
|
|
} else {
|
|
return offset + 8 + __builtin_ctzll(hi) / 8;
|
|
}
|
|
}
|
|
ptr += 16;
|
|
remaining -= 16;
|
|
}
|
|
size_t current = len - remaining;
|
|
return find_next_json_quotable_character_scalar(view, current);
|
|
}
|
|
#else
|
|
SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline size_t
|
|
find_next_json_quotable_character(const std::string_view view,
|
|
size_t location) noexcept {
|
|
return find_next_json_quotable_character_scalar(view, location);
|
|
}
|
|
#endif
|
|
|
|
SIMDJSON_CONSTEXPR_LAMBDA static std::string_view control_chars[] = {
|
|
"\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006",
|
|
"\\u0007", "\\b", "\\t", "\\n", "\\u000b", "\\f", "\\r",
|
|
"\\u000e", "\\u000f", "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014",
|
|
"\\u0015", "\\u0016", "\\u0017", "\\u0018", "\\u0019", "\\u001a", "\\u001b",
|
|
"\\u001c", "\\u001d", "\\u001e", "\\u001f"};
|
|
|
|
// All Unicode characters may be placed within the quotation marks, except for
|
|
// the characters that MUST be escaped: quotation mark, reverse solidus, and the
|
|
// control characters (U+0000 through U+001F). There are two-character sequence
|
|
// escape representations of some popular characters:
|
|
// \", \\, \b, \f, \n, \r, \t.
|
|
SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline void escape_json_char(char c, char *&out) {
|
|
if (c == '"') {
|
|
memcpy(out, "\\\"", 2);
|
|
out += 2;
|
|
} else if (c == '\\') {
|
|
memcpy(out, "\\\\", 2);
|
|
out += 2;
|
|
} else {
|
|
std::string_view v = control_chars[uint8_t(c)];
|
|
memcpy(out, v.data(), v.size());
|
|
out += v.size();
|
|
}
|
|
}
|
|
|
|
// Writes the escaped version of input to out, returning the number of bytes
|
|
// written. Uses SIMD position finding to locate quotable characters efficiently.
|
|
inline size_t write_string_escaped(const std::string_view input, char *out) {
|
|
size_t mysize = input.size();
|
|
|
|
// Use SIMD position finder directly - it returns mysize if no escape needed
|
|
size_t location = find_next_json_quotable_character(input, 0);
|
|
if (location == mysize) {
|
|
// Fast path: no escaping needed
|
|
memcpy(out, input.data(), input.size());
|
|
return input.size();
|
|
}
|
|
|
|
const char *const initout = out;
|
|
memcpy(out, input.data(), location);
|
|
out += location;
|
|
escape_json_char(input[location], out);
|
|
location += 1;
|
|
while (location < mysize) {
|
|
size_t newlocation = find_next_json_quotable_character(input, location);
|
|
memcpy(out, input.data() + location, newlocation - location);
|
|
out += newlocation - location;
|
|
location = newlocation;
|
|
if (location == mysize) {
|
|
break;
|
|
}
|
|
escape_json_char(input[location], out);
|
|
location += 1;
|
|
}
|
|
return out - initout;
|
|
}
|
|
|
|
simdjson_inline string_builder::string_builder(size_t initial_capacity)
|
|
: buffer(new(std::nothrow) char[initial_capacity]), position(0),
|
|
capacity(buffer.get() != nullptr ? initial_capacity : 0),
|
|
is_valid(buffer.get() != nullptr) {}
|
|
|
|
simdjson_inline bool string_builder::capacity_check(size_t upcoming_bytes) {
|
|
// We use the convention that when is_valid is false, then the capacity and
|
|
// the position are 0.
|
|
// Most of the time, this function will return true.
|
|
if (simdjson_likely(upcoming_bytes <= capacity - position)) {
|
|
return true;
|
|
}
|
|
// check for overflow, most of the time there is no overflow
|
|
if (simdjson_unlikely(position + upcoming_bytes < position)) {
|
|
return false;
|
|
}
|
|
// We will rarely get here.
|
|
grow_buffer((std::max)(capacity * 2, position + upcoming_bytes));
|
|
// If the buffer allocation failed, we set is_valid to false.
|
|
return is_valid;
|
|
}
|
|
|
|
simdjson_inline void string_builder::grow_buffer(size_t desired_capacity) {
|
|
if (!is_valid) {
|
|
return;
|
|
}
|
|
std::unique_ptr<char[]> new_buffer(new (std::nothrow) char[desired_capacity]);
|
|
if (new_buffer.get() == nullptr) {
|
|
set_valid(false);
|
|
return;
|
|
}
|
|
std::memcpy(new_buffer.get(), buffer.get(), position);
|
|
buffer.swap(new_buffer);
|
|
capacity = desired_capacity;
|
|
}
|
|
|
|
simdjson_inline void string_builder::set_valid(bool valid) noexcept {
|
|
if (!valid) {
|
|
is_valid = false;
|
|
capacity = 0;
|
|
position = 0;
|
|
buffer.reset();
|
|
} else {
|
|
is_valid = true;
|
|
}
|
|
}
|
|
|
|
simdjson_inline size_t string_builder::size() const noexcept {
|
|
return position;
|
|
}
|
|
|
|
simdjson_inline void string_builder::append(char c) noexcept {
|
|
if (capacity_check(1)) {
|
|
buffer.get()[position++] = c;
|
|
}
|
|
}
|
|
|
|
simdjson_inline void string_builder::append_null() noexcept {
|
|
constexpr char null_literal[] = "null";
|
|
constexpr size_t null_len = sizeof(null_literal) - 1;
|
|
if (capacity_check(null_len)) {
|
|
std::memcpy(buffer.get() + position, null_literal, null_len);
|
|
position += null_len;
|
|
}
|
|
}
|
|
|
|
simdjson_inline void string_builder::clear() noexcept {
|
|
position = 0;
|
|
// if it was invalid, we should try to repair it
|
|
if (!is_valid) {
|
|
capacity = 0;
|
|
buffer.reset();
|
|
is_valid = true;
|
|
}
|
|
}
|
|
|
|
namespace internal {
|
|
|
|
template <typename number_type, typename = typename std::enable_if<
|
|
std::is_unsigned<number_type>::value>::type>
|
|
simdjson_really_inline int int_log2(number_type x) {
|
|
return 63 - leading_zeroes(uint64_t(x) | 1);
|
|
}
|
|
|
|
simdjson_really_inline int fast_digit_count_32(uint32_t x) {
|
|
static uint64_t table[] = {
|
|
4294967296, 8589934582, 8589934582, 8589934582, 12884901788,
|
|
12884901788, 12884901788, 17179868184, 17179868184, 17179868184,
|
|
21474826480, 21474826480, 21474826480, 21474826480, 25769703776,
|
|
25769703776, 25769703776, 30063771072, 30063771072, 30063771072,
|
|
34349738368, 34349738368, 34349738368, 34349738368, 38554705664,
|
|
38554705664, 38554705664, 41949672960, 41949672960, 41949672960,
|
|
42949672960, 42949672960};
|
|
return uint32_t((x + table[int_log2(x)]) >> 32);
|
|
}
|
|
|
|
simdjson_really_inline int fast_digit_count_64(uint64_t x) {
|
|
static uint64_t table[] = {9,
|
|
99,
|
|
999,
|
|
9999,
|
|
99999,
|
|
999999,
|
|
9999999,
|
|
99999999,
|
|
999999999,
|
|
9999999999,
|
|
99999999999,
|
|
999999999999,
|
|
9999999999999,
|
|
99999999999999,
|
|
999999999999999ULL,
|
|
9999999999999999ULL,
|
|
99999999999999999ULL,
|
|
999999999999999999ULL,
|
|
9999999999999999999ULL};
|
|
int y = (19 * int_log2(x) >> 6);
|
|
y += x > table[y];
|
|
return y + 1;
|
|
}
|
|
|
|
template <typename number_type, typename = typename std::enable_if<
|
|
std::is_unsigned<number_type>::value>::type>
|
|
simdjson_really_inline size_t digit_count(number_type v) noexcept {
|
|
static_assert(sizeof(number_type) == 8 || sizeof(number_type) == 4 ||
|
|
sizeof(number_type) == 2 || sizeof(number_type) == 1,
|
|
"We only support 8-bit, 16-bit, 32-bit and 64-bit numbers");
|
|
SIMDJSON_IF_CONSTEXPR(sizeof(number_type) <= 4) {
|
|
return fast_digit_count_32(static_cast<uint32_t>(v));
|
|
}
|
|
else {
|
|
return fast_digit_count_64(static_cast<uint64_t>(v));
|
|
}
|
|
}
|
|
static const char decimal_table[200] = {
|
|
0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30, 0x34, 0x30, 0x35,
|
|
0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x31, 0x31,
|
|
0x31, 0x32, 0x31, 0x33, 0x31, 0x34, 0x31, 0x35, 0x31, 0x36, 0x31, 0x37,
|
|
0x31, 0x38, 0x31, 0x39, 0x32, 0x30, 0x32, 0x31, 0x32, 0x32, 0x32, 0x33,
|
|
0x32, 0x34, 0x32, 0x35, 0x32, 0x36, 0x32, 0x37, 0x32, 0x38, 0x32, 0x39,
|
|
0x33, 0x30, 0x33, 0x31, 0x33, 0x32, 0x33, 0x33, 0x33, 0x34, 0x33, 0x35,
|
|
0x33, 0x36, 0x33, 0x37, 0x33, 0x38, 0x33, 0x39, 0x34, 0x30, 0x34, 0x31,
|
|
0x34, 0x32, 0x34, 0x33, 0x34, 0x34, 0x34, 0x35, 0x34, 0x36, 0x34, 0x37,
|
|
0x34, 0x38, 0x34, 0x39, 0x35, 0x30, 0x35, 0x31, 0x35, 0x32, 0x35, 0x33,
|
|
0x35, 0x34, 0x35, 0x35, 0x35, 0x36, 0x35, 0x37, 0x35, 0x38, 0x35, 0x39,
|
|
0x36, 0x30, 0x36, 0x31, 0x36, 0x32, 0x36, 0x33, 0x36, 0x34, 0x36, 0x35,
|
|
0x36, 0x36, 0x36, 0x37, 0x36, 0x38, 0x36, 0x39, 0x37, 0x30, 0x37, 0x31,
|
|
0x37, 0x32, 0x37, 0x33, 0x37, 0x34, 0x37, 0x35, 0x37, 0x36, 0x37, 0x37,
|
|
0x37, 0x38, 0x37, 0x39, 0x38, 0x30, 0x38, 0x31, 0x38, 0x32, 0x38, 0x33,
|
|
0x38, 0x34, 0x38, 0x35, 0x38, 0x36, 0x38, 0x37, 0x38, 0x38, 0x38, 0x39,
|
|
0x39, 0x30, 0x39, 0x31, 0x39, 0x32, 0x39, 0x33, 0x39, 0x34, 0x39, 0x35,
|
|
0x39, 0x36, 0x39, 0x37, 0x39, 0x38, 0x39, 0x39,
|
|
};
|
|
} // namespace internal
|
|
|
|
template <typename number_type, typename>
|
|
simdjson_inline void string_builder::append(number_type v) noexcept {
|
|
static_assert(std::is_same<number_type, bool>::value ||
|
|
std::is_integral<number_type>::value ||
|
|
std::is_floating_point<number_type>::value,
|
|
"Unsupported number type");
|
|
// If C++17 is available, we can 'if constexpr' here.
|
|
SIMDJSON_IF_CONSTEXPR(std::is_same<number_type, bool>::value) {
|
|
if (v) {
|
|
constexpr char true_literal[] = "true";
|
|
constexpr size_t true_len = sizeof(true_literal) - 1;
|
|
if (capacity_check(true_len)) {
|
|
std::memcpy(buffer.get() + position, true_literal, true_len);
|
|
position += true_len;
|
|
}
|
|
} else {
|
|
constexpr char false_literal[] = "false";
|
|
constexpr size_t false_len = sizeof(false_literal) - 1;
|
|
if (capacity_check(false_len)) {
|
|
std::memcpy(buffer.get() + position, false_literal, false_len);
|
|
position += false_len;
|
|
}
|
|
}
|
|
}
|
|
else SIMDJSON_IF_CONSTEXPR(std::is_unsigned<number_type>::value) {
|
|
// Process 4 digits at a time instead of 2, reducing store operations
|
|
// and divisions by approximately half for large numbers.
|
|
constexpr size_t max_number_size = 20;
|
|
if (capacity_check(max_number_size)) {
|
|
using unsigned_type = typename std::make_unsigned<number_type>::type;
|
|
unsigned_type pv = static_cast<unsigned_type>(v);
|
|
size_t dc = internal::digit_count(pv);
|
|
char *write_pointer = buffer.get() + position + dc - 1;
|
|
|
|
// Process 4 digits per iteration for large numbers
|
|
while (pv >= 10000) {
|
|
unsigned_type q = pv / 10000;
|
|
unsigned_type r = pv % 10000;
|
|
unsigned_type r_hi = r / 100; // High 2 digits of remainder
|
|
unsigned_type r_lo = r % 100; // Low 2 digits of remainder
|
|
// Write low 2 digits first (rightmost), then high 2 digits
|
|
memcpy(write_pointer - 1, &internal::decimal_table[r_lo * 2], 2);
|
|
memcpy(write_pointer - 3, &internal::decimal_table[r_hi * 2], 2);
|
|
write_pointer -= 4;
|
|
pv = q;
|
|
}
|
|
|
|
// Handle remaining 1-4 digits with original 2-digit loop
|
|
while (pv >= 100) {
|
|
memcpy(write_pointer - 1, &internal::decimal_table[(pv % 100) * 2], 2);
|
|
write_pointer -= 2;
|
|
pv /= 100;
|
|
}
|
|
if (pv >= 10) {
|
|
*write_pointer-- = char('0' + (pv % 10));
|
|
pv /= 10;
|
|
}
|
|
*write_pointer = char('0' + pv);
|
|
position += dc;
|
|
}
|
|
}
|
|
else SIMDJSON_IF_CONSTEXPR(std::is_integral<number_type>::value) {
|
|
// Same 4-digit batching as unsigned path for signed integers
|
|
constexpr size_t max_number_size = 20;
|
|
if (capacity_check(max_number_size)) {
|
|
using unsigned_type = typename std::make_unsigned<number_type>::type;
|
|
bool negative = v < 0;
|
|
unsigned_type pv = static_cast<unsigned_type>(v);
|
|
if (negative) {
|
|
pv = 0 - pv; // the 0 is for Microsoft
|
|
}
|
|
size_t dc = internal::digit_count(pv);
|
|
// by always writing the minus sign, we avoid the branch.
|
|
buffer.get()[position] = '-';
|
|
position += negative ? 1 : 0;
|
|
char *write_pointer = buffer.get() + position + dc - 1;
|
|
|
|
// Process 4 digits per iteration for large numbers
|
|
while (pv >= 10000) {
|
|
unsigned_type q = pv / 10000;
|
|
unsigned_type r = pv % 10000;
|
|
unsigned_type r_hi = r / 100;
|
|
unsigned_type r_lo = r % 100;
|
|
memcpy(write_pointer - 1, &internal::decimal_table[r_lo * 2], 2);
|
|
memcpy(write_pointer - 3, &internal::decimal_table[r_hi * 2], 2);
|
|
write_pointer -= 4;
|
|
pv = q;
|
|
}
|
|
|
|
// Handle remaining 1-4 digits
|
|
while (pv >= 100) {
|
|
memcpy(write_pointer - 1, &internal::decimal_table[(pv % 100) * 2], 2);
|
|
write_pointer -= 2;
|
|
pv /= 100;
|
|
}
|
|
if (pv >= 10) {
|
|
*write_pointer-- = char('0' + (pv % 10));
|
|
pv /= 10;
|
|
}
|
|
*write_pointer = char('0' + pv);
|
|
position += dc;
|
|
}
|
|
}
|
|
else SIMDJSON_IF_CONSTEXPR(std::is_floating_point<number_type>::value) {
|
|
constexpr size_t max_number_size = 24;
|
|
if (capacity_check(max_number_size)) {
|
|
// We could specialize for float.
|
|
char *end = simdjson::internal::to_chars(buffer.get() + position, nullptr,
|
|
double(v));
|
|
position = end - buffer.get();
|
|
}
|
|
}
|
|
}
|
|
|
|
simdjson_inline void
|
|
string_builder::escape_and_append(std::string_view input) noexcept {
|
|
// escaping might turn a control character into \x00xx so 6 characters.
|
|
if (capacity_check(6 * input.size())) {
|
|
position += write_string_escaped(input, buffer.get() + position);
|
|
}
|
|
}
|
|
|
|
simdjson_inline void
|
|
string_builder::escape_and_append_with_quotes(std::string_view input) noexcept {
|
|
// escaping might turn a control character into \x00xx so 6 characters.
|
|
if (capacity_check(2 + 6 * input.size())) {
|
|
buffer.get()[position++] = '"';
|
|
position += write_string_escaped(input, buffer.get() + position);
|
|
buffer.get()[position++] = '"';
|
|
}
|
|
}
|
|
|
|
simdjson_inline void
|
|
string_builder::escape_and_append_with_quotes(char input) noexcept {
|
|
// escaping might turn a control character into \x00xx so 6 characters.
|
|
if (capacity_check(2 + 6 * 1)) {
|
|
buffer.get()[position++] = '"';
|
|
std::string_view cinput(&input, 1);
|
|
position += write_string_escaped(cinput, buffer.get() + position);
|
|
buffer.get()[position++] = '"';
|
|
}
|
|
}
|
|
|
|
simdjson_inline void
|
|
string_builder::escape_and_append_with_quotes(const char *input) noexcept {
|
|
std::string_view cinput(input);
|
|
escape_and_append_with_quotes(cinput);
|
|
}
|
|
#if SIMDJSON_SUPPORTS_CONCEPTS
|
|
template <constevalutil::fixed_string key>
|
|
simdjson_inline void string_builder::escape_and_append_with_quotes() noexcept {
|
|
escape_and_append_with_quotes(constevalutil::string_constant<key>::value);
|
|
}
|
|
#endif
|
|
|
|
simdjson_inline void string_builder::append_raw(const char *c) noexcept {
|
|
size_t len = std::strlen(c);
|
|
append_raw(c, len);
|
|
}
|
|
|
|
simdjson_inline void
|
|
string_builder::append_raw(std::string_view input) noexcept {
|
|
if (capacity_check(input.size())) {
|
|
std::memcpy(buffer.get() + position, input.data(), input.size());
|
|
position += input.size();
|
|
}
|
|
}
|
|
|
|
simdjson_inline void string_builder::append_raw(const char *str,
|
|
size_t len) noexcept {
|
|
if (capacity_check(len)) {
|
|
std::memcpy(buffer.get() + position, str, len);
|
|
position += len;
|
|
}
|
|
}
|
|
#if SIMDJSON_SUPPORTS_CONCEPTS
|
|
// Support for optional types (std::optional, etc.)
|
|
template <concepts::optional_type T>
|
|
requires(!require_custom_serialization<T>)
|
|
simdjson_inline void string_builder::append(const T &opt) {
|
|
if (opt) {
|
|
append(*opt);
|
|
} else {
|
|
append_null();
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
requires(require_custom_serialization<T>)
|
|
simdjson_inline void string_builder::append(T &&val) {
|
|
serialize(*this, std::forward<T>(val));
|
|
}
|
|
|
|
template <typename T>
|
|
requires(std::is_convertible<T, std::string_view>::value ||
|
|
std::is_same<T, const char *>::value)
|
|
simdjson_inline void string_builder::append(const T &value) {
|
|
escape_and_append_with_quotes(value);
|
|
}
|
|
#endif
|
|
|
|
#if SIMDJSON_SUPPORTS_RANGES && SIMDJSON_SUPPORTS_CONCEPTS
|
|
// Support for range-based appending (std::ranges::view, etc.)
|
|
template <std::ranges::range R>
|
|
requires(!std::is_convertible<R, std::string_view>::value && !require_custom_serialization<R>)
|
|
simdjson_inline void string_builder::append(const R &range) noexcept {
|
|
auto it = std::ranges::begin(range);
|
|
auto end = std::ranges::end(range);
|
|
if constexpr (concepts::is_pair<std::ranges::range_value_t<R>>) {
|
|
start_object();
|
|
|
|
if (it == end) {
|
|
end_object();
|
|
return; // Handle empty range
|
|
}
|
|
// Append first item without leading comma
|
|
append_key_value(it->first, it->second);
|
|
++it;
|
|
|
|
// Append remaining items with preceding commas
|
|
for (; it != end; ++it) {
|
|
append_comma();
|
|
append_key_value(it->first, it->second);
|
|
}
|
|
end_object();
|
|
} else {
|
|
start_array();
|
|
if (it == end) {
|
|
end_array();
|
|
return; // Handle empty range
|
|
}
|
|
|
|
// Append first item without leading comma
|
|
append(*it);
|
|
++it;
|
|
|
|
// Append remaining items with preceding commas
|
|
for (; it != end; ++it) {
|
|
append_comma();
|
|
append(*it);
|
|
}
|
|
end_array();
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#if SIMDJSON_EXCEPTIONS
|
|
simdjson_inline string_builder::operator std::string() const noexcept(false) {
|
|
return std::string(operator std::string_view());
|
|
}
|
|
|
|
simdjson_inline string_builder::operator std::string_view() const
|
|
noexcept(false) simdjson_lifetime_bound {
|
|
return view();
|
|
}
|
|
#endif
|
|
|
|
simdjson_inline simdjson_result<std::string_view>
|
|
string_builder::view() const noexcept {
|
|
if (!is_valid) {
|
|
return simdjson::OUT_OF_CAPACITY;
|
|
}
|
|
return std::string_view(buffer.get(), position);
|
|
}
|
|
|
|
simdjson_inline simdjson_result<const char *> string_builder::c_str() noexcept {
|
|
if (capacity_check(1)) {
|
|
buffer.get()[position] = '\0';
|
|
return buffer.get();
|
|
}
|
|
return simdjson::OUT_OF_CAPACITY;
|
|
}
|
|
|
|
simdjson_inline bool string_builder::validate_unicode() const noexcept {
|
|
return simdjson::validate_utf8(buffer.get(), position);
|
|
}
|
|
|
|
simdjson_inline void string_builder::start_object() noexcept {
|
|
if (capacity_check(1)) {
|
|
buffer.get()[position++] = '{';
|
|
}
|
|
}
|
|
|
|
simdjson_inline void string_builder::end_object() noexcept {
|
|
if (capacity_check(1)) {
|
|
buffer.get()[position++] = '}';
|
|
}
|
|
}
|
|
|
|
simdjson_inline void string_builder::start_array() noexcept {
|
|
if (capacity_check(1)) {
|
|
buffer.get()[position++] = '[';
|
|
}
|
|
}
|
|
|
|
simdjson_inline void string_builder::end_array() noexcept {
|
|
if (capacity_check(1)) {
|
|
buffer.get()[position++] = ']';
|
|
}
|
|
}
|
|
|
|
simdjson_inline void string_builder::append_comma() noexcept {
|
|
if (capacity_check(1)) {
|
|
buffer.get()[position++] = ',';
|
|
}
|
|
}
|
|
|
|
simdjson_inline void string_builder::append_colon() noexcept {
|
|
if (capacity_check(1)) {
|
|
buffer.get()[position++] = ':';
|
|
}
|
|
}
|
|
|
|
template <typename key_type, typename value_type>
|
|
simdjson_inline void
|
|
string_builder::append_key_value(key_type key, value_type value) noexcept {
|
|
static_assert(std::is_same<key_type, const char *>::value ||
|
|
std::is_convertible<key_type, std::string_view>::value,
|
|
"Unsupported key type");
|
|
escape_and_append_with_quotes(key);
|
|
append_colon();
|
|
SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, std::nullptr_t>::value) {
|
|
append_null();
|
|
}
|
|
else SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, char>::value) {
|
|
escape_and_append_with_quotes(value);
|
|
}
|
|
else SIMDJSON_IF_CONSTEXPR(
|
|
std::is_convertible<value_type, std::string_view>::value) {
|
|
escape_and_append_with_quotes(value);
|
|
}
|
|
else SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, const char *>::value) {
|
|
escape_and_append_with_quotes(value);
|
|
}
|
|
else {
|
|
append(value);
|
|
}
|
|
}
|
|
|
|
#if SIMDJSON_SUPPORTS_CONCEPTS
|
|
template <constevalutil::fixed_string key, typename value_type>
|
|
simdjson_inline void
|
|
string_builder::append_key_value(value_type value) noexcept {
|
|
escape_and_append_with_quotes<key>();
|
|
append_colon();
|
|
SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, std::nullptr_t>::value) {
|
|
append_null();
|
|
}
|
|
else SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, char>::value) {
|
|
escape_and_append_with_quotes(value);
|
|
}
|
|
else SIMDJSON_IF_CONSTEXPR(
|
|
std::is_convertible<value_type, std::string_view>::value) {
|
|
escape_and_append_with_quotes(value);
|
|
}
|
|
else SIMDJSON_IF_CONSTEXPR(std::is_same<value_type, const char *>::value) {
|
|
escape_and_append_with_quotes(value);
|
|
}
|
|
else {
|
|
append(value);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
} // namespace builder
|
|
} // namespace SIMDJSON_IMPLEMENTATION
|
|
} // namespace simdjson
|
|
|
|
#endif // SIMDJSON_GENERIC_STRING_BUILDER_INL_H
|