Last active
June 13, 2025 15:29
-
-
Save Unbinilium/e3ce76284f2e07f6cf3bbaca09597c13 to your computer and use it in GitHub Desktop.
JSON Serializer (https://unbinilium.github.io/JSerializer/)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// JSerializer - An exceptionally fast, stream-oriented, header-only C++ JSON serialization library. | |
// MIT License Copyright (c) 2025 Unbinilium | |
#pragma once | |
#ifndef JSERIALIZER_HPP | |
#define JSERIALIZER_HPP | |
#include <array> | |
#include <cctype> | |
#include <cerrno> | |
#include <charconv> | |
#include <cmath> | |
#include <cstddef> | |
#include <cstdint> | |
#include <cstring> | |
#include <memory> | |
#include <new> | |
#include <string> | |
#include <string_view> | |
#include <type_traits> | |
#include <utility> | |
#include <variant> | |
#if !defined(__cplusplus) || __cplusplus < 202002L | |
#error "JSerializer requires C++20 or later" | |
#endif | |
#ifdef JS_BUFFER_SIZE_MIN | |
#warning "JS_BUFFER_SIZE_MIN is already defined, changing this is not recommended." | |
#else | |
#define JS_BUFFER_SIZE_MIN 256 | |
#endif | |
#ifdef JS_FMT_BUFFER_SIZE_MIN | |
#warning "JS_FMT_BUFFER_SIZE_MIN is already defined, changing this is not recommended." | |
#else | |
#define JS_FMT_BUFFER_SIZE_MIN 32 | |
#endif | |
#ifndef JS_DEFAULT_BUFFER_SIZE | |
#define JS_DEFAULT_BUFFER_SIZE (1024 * 16) | |
#endif | |
#ifndef JS_DEFAULT_FMT_BUFFER_SIZE | |
#define JS_DEFAULT_FMT_BUFFER_SIZE 64 | |
#endif | |
#ifndef JS_DEFAULT_FLOATING_POINT_PRECISION | |
#define JS_DEFAULT_FLOATING_POINT_PRECISION 3 | |
#endif | |
#ifndef JS_EXPERIMENTAL_SIMD | |
#if __has_include(<experimental/simd>) | |
#define JS_EXPERIMENTAL_SIMD 1 | |
#else | |
#define JS_EXPERIMENTAL_SIMD 0 | |
#endif | |
#endif | |
#if JS_EXPERIMENTAL_SIMD | |
#include <experimental/simd> | |
#if defined(__cpp_lib_experimental_parallel_simd) && __cpp_lib_experimental_parallel_simd >= 201803L | |
namespace stdx = std::experimental; | |
#ifndef JS_DEFAULT_SIMD_ENABLE_LEN_FACTOR | |
#define JS_DEFAULT_SIMD_ENABLE_LEN_FACTOR 3 | |
#endif | |
#else | |
#undef JS_EXPERIMENTAL_SIMD | |
#define JS_EXPERIMENTAL_SIMD 0 | |
#warning "JS_EXPERIMENTAL_SIMD is set to 0, since std::experimental::simd is not available on current compiler." | |
#endif | |
#endif | |
namespace ubn { | |
namespace traits { | |
template<typename, typename = std::void_t<>> | |
struct is_bounded_char_array: std::false_type | |
{ | |
}; | |
template<typename T> | |
struct is_bounded_char_array<T, std::void_t<decltype(std::declval<T>()[0])>> | |
: std::integral_constant<bool, std::is_same_v<std::remove_extent_t<T>, char>> | |
{ | |
}; | |
template<typename T> | |
inline constexpr bool is_bounded_char_array_v = is_bounded_char_array<T>::value; | |
template<typename, typename = std::void_t<>> | |
struct is_stl_container: std::false_type | |
{ | |
}; | |
template<typename T> | |
struct is_stl_container<T, std::void_t<typename T::iterator>>: std::true_type | |
{ | |
}; | |
template<typename T> | |
inline constexpr bool is_stl_container_v = is_stl_container<T>::value; | |
template<typename, typename = std::void_t<>> | |
struct is_stl_map_container: std::false_type | |
{ | |
}; | |
template<typename T> | |
struct is_stl_map_container<T, std::void_t<typename T::value_type, typename T::key_type, typename T::mapped_type>> | |
: std::integral_constant<bool, | |
std::is_same_v<typename T::value_type, std::pair<const typename T::key_type, typename T::mapped_type>>> | |
{ | |
}; | |
template<typename T> | |
inline constexpr bool is_stl_map_container_v = is_stl_map_container<T>::value; | |
template<typename> | |
struct is_variant: std::false_type | |
{ | |
}; | |
template<typename... Args> | |
struct is_variant<std::variant<Args...>>: std::true_type | |
{ | |
}; | |
template<typename T> | |
inline constexpr bool is_variant_v = is_variant<T>::value; | |
template<typename T, typename = std::void_t<>> | |
struct stl_container_value | |
{ | |
using type = void; | |
}; | |
template<typename T> | |
struct stl_container_value<T, std::void_t<typename T::value_type>> | |
{ | |
using type = typename T::value_type; | |
}; | |
template<typename T> | |
using stl_container_value_t = typename stl_container_value<T>::type; | |
} // namespace traits | |
namespace charconv { | |
namespace digits2 { | |
inline constexpr std::array<char, 200> make_digits() noexcept | |
{ | |
alignas(2) std::array<char, 200> data {}; | |
for (size_t i = 0; i < 100; ++i) { | |
data[i * 2] = static_cast<char>('0' + i / 10); | |
data[i * 2 + 1] = static_cast<char>('0' + i % 10); | |
} | |
return data; | |
} | |
alignas(2) inline constexpr auto table = make_digits(); | |
inline constexpr const char* at(size_t index) | |
{ | |
return &table[index]; | |
} | |
} // namespace digits2 | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_unsigned_v<U> && sizeof(U) == sizeof(uint8_t), bool> = true> | |
inline constexpr char* itoa(T&& val, char* buf) noexcept | |
{ | |
#pragma GCC diagnostic push | |
#pragma GCC diagnostic ignored "-Wconversion" | |
if (val < 100) { | |
if (val < 10) { | |
*buf = static_cast<char>('0' | val); | |
return buf + 1; | |
} | |
std::memcpy(buf, digits2::at(val << 1), 2); | |
return buf + 2; | |
} | |
else { | |
uint16_t l0_0 = (static_cast<uint16_t>(val) * static_cast<uint16_t>(41)) >> 12; | |
uint16_t l1_2 = val - (l0_0 * static_cast<uint16_t>(100)); | |
*buf = static_cast<char>('0' | l0_0); | |
std::memcpy(buf + 1, digits2::at(l1_2 << 1), 2); | |
return buf + 3; | |
} | |
#pragma GCC diagnostic pop | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_signed_v<U> && sizeof(U) == sizeof(int8_t), bool> = true> | |
inline constexpr char* itoa(T&& val, char* buf) noexcept | |
{ | |
uint8_t uval = static_cast<uint8_t>(val); | |
size_t sign = val < 0; | |
*buf = '-'; | |
return itoa(sign ? static_cast<uint8_t>(~uval + 1) : uval, buf + sign); | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_unsigned_v<U> && sizeof(U) == sizeof(uint16_t), bool> = true> | |
inline constexpr char* itoa(T&& val, char* buf) noexcept | |
{ | |
#pragma GCC diagnostic push | |
#pragma GCC diagnostic ignored "-Wconversion" | |
if (val < 100) { | |
if (val < 10) { | |
*buf = static_cast<char>('0' | val); | |
return buf + 1; | |
} | |
std::memcpy(buf, digits2::at(val << 1), 2); | |
return buf + 2; | |
} | |
else if (val < 10'000) { | |
uint32_t l0_1 = (static_cast<uint32_t>(val) * static_cast<uint32_t>(5243)) >> 19; | |
uint32_t l2_3 = val - (l0_1 * static_cast<uint32_t>(100)); | |
uint32_t leading_zero = l0_1 < 10; | |
std::memcpy(buf, digits2::at((l0_1 << 1) + leading_zero), 2); | |
#pragma GCC diagnostic push | |
#pragma GCC diagnostic ignored "-Warray-bounds" | |
buf -= leading_zero; | |
#pragma GCC diagnostic pop | |
std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2); | |
return buf + 4; | |
} | |
else { | |
uint32_t l0_1 = static_cast<uint32_t>((static_cast<uint64_t>(val) * static_cast<uint64_t>(107375)) >> 30); | |
uint32_t l2_5 = val - (l0_1 * static_cast<uint32_t>(10'000)); | |
uint32_t l2_3 = (l2_5 * static_cast<uint32_t>(5243)) >> 19; | |
uint32_t l4_5 = l2_5 - (l2_3 * static_cast<uint32_t>(100)); | |
uint32_t leading_zero = l0_1 < 10; | |
std::memcpy(buf, digits2::at((l0_1 << 1) + leading_zero), 2); | |
#pragma GCC diagnostic push | |
#pragma GCC diagnostic ignored "-Warray-bounds" | |
buf -= leading_zero; | |
#pragma GCC diagnostic pop | |
std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2); | |
std::memcpy(buf + 4, digits2::at(l4_5 << 1), 2); | |
return buf + 6; | |
} | |
#pragma GCC diagnostic pop | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_signed_v<U> && sizeof(U) == sizeof(int16_t), bool> = true> | |
inline constexpr char* itoa(T&& val, char* buf) noexcept | |
{ | |
uint16_t uval = static_cast<uint16_t>(val); | |
size_t sign = val < 0; | |
*buf = '-'; | |
return itoa(sign ? static_cast<uint16_t>(~uval + 1) : uval, buf + sign); | |
} | |
inline char* itoa_u32_lt_1e2(uint32_t val, char* buf) noexcept | |
{ | |
if (val < 10) { | |
*buf = static_cast<char>('0' | val); | |
return buf + 1; | |
} | |
std::memcpy(buf, digits2::at(val << 1), 2); | |
return buf + 2; | |
} | |
inline char* itoa_u32_ge_1e2_lt_1e4(uint32_t val, char* buf) noexcept | |
{ | |
uint32_t l0_1 = (val * static_cast<uint32_t>(5243)) >> 19; | |
uint32_t l2_3 = val - (l0_1 * static_cast<uint32_t>(100)); | |
uint32_t leading_zero = l0_1 < 10; | |
std::memcpy(buf, digits2::at((l0_1 << 1) + leading_zero), 2); | |
#pragma GCC diagnostic push | |
#pragma GCC diagnostic ignored "-Warray-bounds" | |
buf -= leading_zero; | |
#pragma GCC diagnostic pop | |
std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2); | |
return buf + 4; | |
} | |
inline char* itoa_u32_ge_1e4_lt_1e6(uint32_t val, char* buf) noexcept | |
{ | |
uint32_t l0_1 = static_cast<uint32_t>((static_cast<uint64_t>(val) * static_cast<uint64_t>(429497)) >> 32); | |
uint32_t l2_5 = val - (l0_1 * static_cast<uint32_t>(10'000)); | |
uint32_t l2_3 = (l2_5 * static_cast<uint32_t>(5243)) >> 19; | |
uint32_t l4_5 = l2_5 - (l2_3 * static_cast<uint32_t>(100)); | |
uint32_t leading_zero = l0_1 < 10; | |
std::memcpy(buf, digits2::at((l0_1 << 1) + leading_zero), 2); | |
#pragma GCC diagnostic push | |
#pragma GCC diagnostic ignored "-Warray-bounds" | |
buf -= leading_zero; | |
#pragma GCC diagnostic pop | |
std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2); | |
std::memcpy(buf + 4, digits2::at(l4_5 << 1), 2); | |
return buf + 6; | |
} | |
inline char* itoa_u32_ge_1e6_lt_1e8(uint32_t val, char* buf) noexcept | |
{ | |
uint32_t l0_3 = static_cast<uint32_t>((static_cast<uint64_t>(val) * static_cast<uint64_t>(109951163)) >> 40); | |
uint32_t l4_7 = val - (l0_3 * static_cast<uint32_t>(10'000)); | |
uint32_t l0_1 = (l0_3 * static_cast<uint32_t>(5243)) >> 19; | |
uint32_t l2_3 = l0_3 - (l0_1 * static_cast<uint32_t>(100)); | |
uint32_t l4_5 = (l4_7 * static_cast<uint32_t>(5243)) >> 19; | |
uint32_t l6_7 = l4_7 - (l4_5 * static_cast<uint32_t>(100)); | |
uint32_t leading_zero = l0_1 < 10; | |
std::memcpy(buf, digits2::at((l0_1 << 1) + leading_zero), 2); | |
#pragma GCC diagnostic push | |
#pragma GCC diagnostic ignored "-Warray-bounds" | |
buf -= leading_zero; | |
#pragma GCC diagnostic pop | |
std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2); | |
std::memcpy(buf + 4, digits2::at(l4_5 << 1), 2); | |
std::memcpy(buf + 6, digits2::at(l6_7 << 1), 2); | |
return buf + 8; | |
} | |
inline char* itoa_u32_ge_1e8_lt_1ea(uint32_t val, char* buf) noexcept | |
{ | |
uint32_t l0_5 = static_cast<uint32_t>((static_cast<uint64_t>(val) * static_cast<uint64_t>(3518437209UL)) >> 45); | |
uint32_t l0_1 = static_cast<uint32_t>((static_cast<uint64_t>(l0_5) * static_cast<uint64_t>(429497)) >> 32); | |
uint32_t l2_5 = l0_5 - (l0_1 * static_cast<uint32_t>(10'000)); | |
uint32_t l2_3 = (l2_5 * static_cast<uint32_t>(5243)) >> 19; | |
uint32_t l4_5 = l2_5 - (l2_3 * static_cast<uint32_t>(100)); | |
uint32_t l6_9 = val - (l0_5 * static_cast<uint32_t>(10'000)); | |
uint32_t l6_7 = (l6_9 * static_cast<uint32_t>(5243)) >> 19; | |
uint32_t l8_9 = l6_9 - (l6_7 * static_cast<uint32_t>(100)); | |
uint32_t leading_zero = l0_1 < 10; | |
std::memcpy(buf, digits2::at((l0_1 << 1) + leading_zero), 2); | |
#pragma GCC diagnostic push | |
#pragma GCC diagnostic ignored "-Warray-bounds" | |
buf -= leading_zero; | |
#pragma GCC diagnostic pop | |
std::memcpy(buf + 2, digits2::at(l2_3 << 1), 2); | |
std::memcpy(buf + 4, digits2::at(l4_5 << 1), 2); | |
std::memcpy(buf + 6, digits2::at(l6_7 << 1), 2); | |
std::memcpy(buf + 8, digits2::at(l8_9 << 1), 2); | |
return buf + 10; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_unsigned_v<U> && sizeof(U) == sizeof(uint32_t), bool> = true> | |
inline constexpr char* itoa(T&& val, char* buf) noexcept | |
{ | |
if (val < 100) { | |
return itoa_u32_lt_1e2(val, buf); | |
} | |
else if (val < 10'000) { | |
return itoa_u32_ge_1e2_lt_1e4(val, buf); | |
} | |
else if (val < 1'000'000) { | |
return itoa_u32_ge_1e4_lt_1e6(val, buf); | |
} | |
else if (val < 100'000'000) { | |
return itoa_u32_ge_1e6_lt_1e8(val, buf); | |
} | |
else { | |
return itoa_u32_ge_1e8_lt_1ea(val, buf); | |
} | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_signed_v<U> && sizeof(U) == sizeof(int32_t), bool> = true> | |
inline constexpr char* itoa(T&& val, char* buf) noexcept | |
{ | |
uint32_t uval = static_cast<uint32_t>(val); | |
size_t sign = val < 0; | |
*buf = '-'; | |
return itoa(sign ? static_cast<uint32_t>(~uval + 1) : uval, buf + sign); | |
} | |
inline char* itoa_u32_lt_1e8(uint32_t val, char* buf) noexcept | |
{ | |
if (val < 100) { | |
return itoa_u32_lt_1e2(val, buf); | |
} | |
else if (val < 10'000) { | |
return itoa_u32_ge_1e2_lt_1e4(val, buf); | |
} | |
else if (val < 1'000'000) { | |
return itoa_u32_ge_1e4_lt_1e6(val, buf); | |
} | |
else { | |
return itoa_u32_ge_1e6_lt_1e8(val, buf); | |
} | |
} | |
inline char* itoa_u32_ge_1e4_lt_1e8(uint32_t val, char* buf) noexcept | |
{ | |
if (val < 1'000'000) { | |
return itoa_u32_ge_1e4_lt_1e6(val, buf); | |
} | |
else { | |
return itoa_u32_ge_1e6_lt_1e8(val, buf); | |
} | |
} | |
inline char* itoa_u64_ge_1e8_lt_1e16(uint64_t val, char* buf) noexcept | |
{ | |
uint64_t l0_7 = val / static_cast<uint64_t>(100'000'000); | |
uint32_t l8_f = static_cast<uint32_t>(val - (l0_7 * static_cast<uint64_t>(100'000'000))); | |
buf = itoa_u32_lt_1e8(static_cast<uint32_t>(l0_7), buf); | |
buf = itoa_u32_ge_1e6_lt_1e8(l8_f, buf); | |
return buf; | |
} | |
inline char* itoa_u64_ge_1e16_lt_1e20(uint64_t val, char* buf) noexcept | |
{ | |
uint64_t l0_b = val / static_cast<uint64_t>(100'000'000); | |
uint32_t r0_7 = static_cast<uint32_t>(val - (l0_b * static_cast<uint64_t>(100'000'000))); | |
uint64_t l0_3 = l0_b / static_cast<uint64_t>(10'000); | |
uint32_t l4_7 = static_cast<uint32_t>(l0_b - (l0_3 * static_cast<uint64_t>(10'000))); | |
buf = itoa_u32_ge_1e4_lt_1e8(static_cast<uint32_t>(l0_3), buf); | |
buf = itoa_u32_ge_1e2_lt_1e4(l4_7, buf); | |
buf = itoa_u32_ge_1e6_lt_1e8(r0_7, buf); | |
return buf; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_unsigned_v<U> && sizeof(U) == sizeof(uint64_t), bool> = true> | |
inline constexpr char* itoa(T&& val, char* buf) noexcept | |
{ | |
if (val < 100'000'000) { | |
return itoa_u32_lt_1e8(static_cast<uint32_t>(val), buf); | |
} | |
else if (val < static_cast<uint64_t>(100'000'000'000'000ULL)) { | |
return itoa_u64_ge_1e8_lt_1e16(val, buf); | |
} | |
else { | |
return itoa_u64_ge_1e16_lt_1e20(val, buf); | |
} | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_signed_v<U> && sizeof(U) == sizeof(int64_t), bool> = true> | |
inline constexpr char* itoa(T&& val, char* buf) noexcept | |
{ | |
uint64_t uval = static_cast<uint64_t>(val); | |
size_t sign = val < 0; | |
*buf = '-'; | |
return itoa(sign ? static_cast<uint64_t>(~uval + 1) : uval, buf + sign); | |
} | |
} // namespace charconv | |
class JSerializer final | |
{ | |
public: | |
using WriteCallback = int (*)(const void*, size_t) noexcept; | |
using FlushCallback = int (*)(int) noexcept; | |
struct StdoutStream final | |
{ | |
static inline constexpr int write(const void* data, size_t size) noexcept | |
{ | |
if (!data || size == 0) [[unlikely]] { | |
return 0; | |
} | |
return static_cast<int>(std::fwrite(data, 1, size, stdout)); | |
} | |
static inline int flush(int) noexcept | |
{ | |
std::fwrite("\n", 1, 1, stdout); | |
return std::fflush(stdout); | |
} | |
}; | |
static inline constexpr const size_t const_buffer_size_min = JS_BUFFER_SIZE_MIN; | |
static inline constexpr const size_t const_fmt_buffer_size_min = JS_FMT_BUFFER_SIZE_MIN; | |
#if JS_EXPERIMENTAL_SIMD | |
static inline constexpr const size_t const_simd_memory_alignment | |
= stdx::memory_alignment_v<stdx::native_simd<unsigned char>>; | |
static inline constexpr const size_t const_simd_memory_alignment_mask = const_simd_memory_alignment - 1; | |
static_assert((const_simd_memory_alignment & const_simd_memory_alignment_mask) == 0, | |
"SIMD memory alignment must be a power of two"); | |
static inline constexpr const int const_simd_size | |
= stdx::simd_size_v<unsigned char, stdx::simd_abi::native<unsigned char>>; | |
#endif | |
static inline constexpr const size_t default_buffer_size = JS_DEFAULT_BUFFER_SIZE; | |
static inline constexpr const size_t default_fmt_buffer_size = JS_DEFAULT_FMT_BUFFER_SIZE; | |
static inline constexpr const int default_floating_point_precision = JS_DEFAULT_FLOATING_POINT_PRECISION; | |
static_assert(default_buffer_size >= const_buffer_size_min, | |
"Default buffer size must be at least const_buffer_size_min bytes"); | |
static_assert(const_fmt_buffer_size_min <= default_fmt_buffer_size && default_fmt_buffer_size < default_buffer_size, | |
"Default format buffer size must be between const_fmt_buffer_size_min and default_buffer_size (exclusive)"); | |
static_assert(default_floating_point_precision < default_fmt_buffer_size, | |
"Default floating point precision must be less than default_fmt_buffer_size"); | |
#if JS_EXPERIMENTAL_SIMD | |
static inline constexpr const size_t default_simd_enable_len_factor = JS_DEFAULT_SIMD_ENABLE_LEN_FACTOR; | |
static inline constexpr const size_t default_simd_enable_len = const_simd_size * default_simd_enable_len_factor; | |
static_assert(default_simd_enable_len_factor > 1, "Default SIMD enable length factor must be greater than 1"); | |
static_assert(default_simd_enable_len <= default_buffer_size, | |
"Default SIMD enable length must be less than or equal to default_buffer_size"); | |
static_assert((default_simd_enable_len - const_simd_size) >= const_simd_memory_alignment, | |
"Default SIMD enable length minus default SIMD size must be greater than or equal to " | |
"const_simd_memory_alignment"); | |
#endif | |
class StringWriter; | |
class ForwardWriter; | |
class BooleanWriter final | |
{ | |
protected: | |
friend class StringWriter; | |
friend class ForwardWriter; | |
inline constexpr explicit BooleanWriter(JSerializer& serializer) noexcept : _serializer(serializer) { } | |
public: | |
template<typename T> | |
static inline constexpr bool is_boolean_v = std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, bool>; | |
~BooleanWriter() noexcept { } | |
inline constexpr void operator<<(bool value) noexcept | |
{ | |
if (value) { | |
_serializer.write("true"); | |
} | |
else { | |
_serializer.write("false"); | |
} | |
} | |
private: | |
JSerializer& _serializer; | |
}; | |
class NumberWriter final | |
{ | |
protected: | |
friend class StringWriter; | |
friend class ForwardWriter; | |
inline constexpr explicit NumberWriter(JSerializer& serializer) noexcept : _serializer(serializer) { } | |
public: | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>> | |
static inline constexpr bool is_number_v | |
= (std::is_integral_v<U> || std::is_floating_point_v<U>) && !std::is_same_v<U, bool>; | |
~NumberWriter() noexcept { } | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_integral_v<U>, bool> = true> | |
inline constexpr void operator<<(T&& value) noexcept | |
{ | |
if (!_serializer.write(std::forward<T>(value))) [[unlikely]] { | |
_serializer.write("null"); | |
} | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_integral_v<U>, bool> = true> | |
inline constexpr void operator+=(T&& value) noexcept | |
{ | |
operator<<(std::forward<T>(value)); | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_floating_point_v<U>, bool> = true> | |
inline constexpr void operator<<(T&& value) noexcept | |
{ | |
if (std::isnan(value) || std::isinf(value)) [[unlikely]] { | |
_serializer.write("null"); | |
return; | |
} | |
else if (!_serializer.write(std::forward<T>(value), JSerializer::default_floating_point_precision)) | |
[[unlikely]] { | |
_serializer.write("null"); | |
} | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_floating_point_v<U>, bool> = true> | |
inline constexpr void operator+=(T&& value) noexcept | |
{ | |
if (std::isnan(value) || std::isinf(value)) [[unlikely]] { | |
_serializer.write("null"); | |
return; | |
} | |
else if (!_serializer.write(std::forward<T>(value))) [[unlikely]] { | |
_serializer.write("null"); | |
} | |
} | |
private: | |
JSerializer& _serializer; | |
}; | |
class StringWriter final | |
{ | |
protected: | |
friend class ForwardWriter; | |
inline constexpr explicit StringWriter(JSerializer& serializer) noexcept : _serializer(serializer) | |
{ | |
_serializer.write('"'); | |
} | |
public: | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>> | |
static inline constexpr bool is_string_v | |
= (std::is_pointer_v<U> && (std::is_same_v<U, const char*> || std::is_same_v<U, char*>)) | |
|| traits::is_bounded_char_array_v<U> || std::is_same_v<U, std::string> | |
|| std::is_same_v<U, std::string_view>; | |
~StringWriter() noexcept | |
{ | |
_serializer.write('"'); | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_pointer_v<U> && (std::is_same_v<U, const char*> || std::is_same_v<U, char*>), bool> | |
= true> | |
inline constexpr StringWriter& operator+=(T&& str) noexcept | |
{ | |
_serializer.write(static_cast<const void*>(str), std::char_traits<char>::length(str)); | |
return *this; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<traits::is_bounded_char_array_v<U>, bool> = true> | |
inline constexpr StringWriter& operator+=(T&& str) noexcept | |
{ | |
_serializer.write(static_cast<const void*>(str), sizeof(str) - 1); | |
return *this; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_same_v<U, std::string> || std::is_same_v<U, std::string_view>, bool> = true> | |
inline constexpr StringWriter& operator+=(T&& str) noexcept | |
{ | |
_serializer.write(static_cast<const void*>(str.data()), str.size()); | |
return *this; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_pointer_v<U> && (std::is_same_v<U, const char*> || std::is_same_v<U, char*>), bool> | |
= true> | |
inline constexpr StringWriter& operator<<(T&& str) noexcept | |
{ | |
_serializer.write(str, std::char_traits<char>::length(str)); | |
return *this; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<traits::is_bounded_char_array_v<U>, bool> = true> | |
inline constexpr StringWriter& operator<<(T&& str) noexcept | |
{ | |
_serializer.write(str, sizeof(str) - 1); | |
return *this; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_same_v<U, std::string> || std::is_same_v<U, std::string_view>, bool> = true> | |
inline constexpr StringWriter& operator<<(T&& str) noexcept | |
{ | |
_serializer.write(str.data(), str.size()); | |
return *this; | |
} | |
template<typename T, std::enable_if_t<BooleanWriter::is_boolean_v<T>, bool> = true> | |
inline constexpr StringWriter& operator<<(T&& value) noexcept | |
{ | |
BooleanWriter { _serializer } << std::forward<T>(value); | |
return *this; | |
} | |
template<typename T, std::enable_if_t<NumberWriter::is_number_v<T>, bool> = true> | |
inline constexpr StringWriter& operator<<(T&& value) noexcept | |
{ | |
NumberWriter { _serializer } << std::forward<T>(value); | |
return *this; | |
} | |
inline constexpr int write(const void* data, size_t size) noexcept | |
{ | |
if (!data || size == 0) [[unlikely]] { | |
return 0; | |
} | |
const int sta = _serializer.state(); | |
if (sta == 0 && _serializer.write(data, size)) [[likely]] { | |
return static_cast<int>(size); | |
} | |
return -sta; | |
} | |
private: | |
JSerializer& _serializer; | |
}; | |
class ObjectWriter; | |
template<typename F, std::enable_if_t<std::is_same_v<F, ForwardWriter>, bool> = true> | |
class ArrayWriter final | |
{ | |
protected: | |
friend class ForwardWriter; | |
inline constexpr explicit ArrayWriter(JSerializer& serializer) noexcept | |
: _serializer(serializer), _forward_writer(_serializer), _multiple_items(false) | |
{ | |
_serializer.write('['); | |
} | |
public: | |
~ArrayWriter() noexcept | |
{ | |
_serializer.write(']'); | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_same_v<U, ObjectWriter> || std::is_same_v<U, ArrayWriter<ForwardWriter>>, bool> | |
= true> | |
inline constexpr U writer() noexcept | |
{ | |
if (_multiple_items) [[likely]] { | |
_serializer.write(','); | |
} | |
else [[unlikely]] { | |
_multiple_items = true; | |
} | |
return U { _serializer }; | |
} | |
template<typename T> | |
inline constexpr ArrayWriter& operator<<(T&& value) noexcept | |
{ | |
if (_multiple_items) [[likely]] { | |
_serializer.write(','); | |
} | |
else [[unlikely]] { | |
_multiple_items = true; | |
} | |
_forward_writer << std::forward<T>(value); | |
return *this; | |
} | |
template<typename T> | |
inline constexpr ArrayWriter& operator+=(T&& value) noexcept | |
{ | |
if (_multiple_items) [[likely]] { | |
_serializer.write(','); | |
} | |
else [[unlikely]] { | |
_multiple_items = true; | |
} | |
_forward_writer += std::forward<T>(value); | |
return *this; | |
} | |
private: | |
JSerializer& _serializer; | |
F _forward_writer; | |
bool _multiple_items; | |
}; | |
class ObjectWriter final | |
{ | |
protected: | |
friend class JSerializer; | |
friend class ForwardWriter; | |
friend class ArrayWriter<ForwardWriter>; | |
inline explicit ObjectWriter(JSerializer& serializer, bool is_base_writer = false) noexcept | |
: _serializer(serializer), _is_base_writer(is_base_writer), _multiple_items(false) | |
{ | |
_serializer.write('{'); | |
} | |
public: | |
~ObjectWriter() noexcept | |
{ | |
_serializer.write('}'); | |
if (_is_base_writer) [[unlikely]] { | |
_serializer.flush(); | |
} | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_pointer_v<U> && (std::is_same_v<U, const char*> || std::is_same_v<U, char*>), bool> | |
= true> | |
inline ForwardWriter operator[](T&& key) noexcept | |
{ | |
write(static_cast<const void*>(key), std::char_traits<char>::length(key)); | |
return ForwardWriter { _serializer }; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<traits::is_bounded_char_array_v<U>, bool> = true> | |
inline ForwardWriter operator[](T&& key) noexcept | |
{ | |
write(static_cast<const void*>(&key), sizeof(key) - 1); | |
return ForwardWriter { _serializer }; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_same_v<U, std::string> || std::is_same_v<U, std::string_view>, bool> = true> | |
inline ForwardWriter operator[](T&& key) noexcept | |
{ | |
write(static_cast<const void*>(key.data()), key.size()); | |
return ForwardWriter { _serializer }; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_integral_v<U>, bool> = true> | |
inline ForwardWriter operator[](T&& key) noexcept | |
{ | |
write(std::forward<T>(key)); | |
return ForwardWriter { _serializer }; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_pointer_v<U> && (std::is_same_v<U, const char*> || std::is_same_v<U, char*>), bool> | |
= true> | |
inline ForwardWriter operator()(T&& key) noexcept | |
{ | |
write(key, std::char_traits<char>::length(key)); | |
return ForwardWriter { _serializer }; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<traits::is_bounded_char_array_v<U>, bool> = true> | |
inline ForwardWriter operator()(T&& key) noexcept | |
{ | |
write(key, sizeof(key) - 1); | |
return ForwardWriter { _serializer }; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_same_v<U, std::string> || std::is_same_v<U, std::string_view>, bool> = true> | |
inline ForwardWriter operator()(T&& key) noexcept | |
{ | |
write(key.data(), key.size()); | |
return ForwardWriter { _serializer }; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<traits::is_stl_map_container_v<U>, bool> = true> | |
inline constexpr ObjectWriter& operator<<(T&& map) noexcept | |
{ | |
for (const auto& [key, value]: map) { | |
operator()(key) << value; | |
} | |
return *this; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<traits::is_stl_map_container_v<U>, bool> = true> | |
inline constexpr ObjectWriter& operator+=(T&& map) noexcept | |
{ | |
for (const auto& [key, value]: map) { | |
operator[](key) += value; | |
} | |
return *this; | |
} | |
private: | |
template<typename T> | |
inline constexpr void write(T&& value, size_t len) noexcept | |
{ | |
if (!len) [[unlikely]] { | |
return; | |
} | |
if (_multiple_items) [[likely]] { | |
_serializer.write(','); | |
} | |
else [[unlikely]] { | |
_multiple_items = true; | |
} | |
_serializer.write('"'); | |
_serializer.write(std::forward<T>(value), len); | |
_serializer.write("\":"); | |
} | |
template<typename T> | |
inline constexpr void write(T&& value) noexcept | |
{ | |
if (_multiple_items) [[likely]] { | |
_serializer.write(','); | |
} | |
else [[unlikely]] { | |
_multiple_items = true; | |
} | |
_serializer.write('"'); | |
_serializer.write(std::forward<T>(value)); | |
_serializer.write("\":"); | |
} | |
JSerializer& _serializer; | |
const bool _is_base_writer; | |
bool _multiple_items; | |
}; | |
class ForwardWriter final | |
{ | |
protected: | |
friend class StringWriter; | |
friend class ObjectWriter; | |
friend class ArrayWriter<ForwardWriter>; | |
inline constexpr explicit ForwardWriter(JSerializer& serializer) noexcept : _serializer(serializer) { } | |
public: | |
~ForwardWriter() noexcept { } | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_same_v<U, StringWriter> || std::is_same_v<U, ObjectWriter> | |
|| std::is_same_v<U, ArrayWriter<ForwardWriter>>, | |
bool> | |
= true> | |
inline constexpr U writer() noexcept | |
{ | |
return U { _serializer }; | |
} | |
inline ObjectWriter operator()() noexcept | |
{ | |
return ObjectWriter { _serializer }; | |
} | |
template<typename T, std::enable_if_t<BooleanWriter::is_boolean_v<T>, bool> = true> | |
inline constexpr void operator<<(T&& value) noexcept | |
{ | |
BooleanWriter { _serializer } << std::forward<T>(value); | |
} | |
template<typename T, std::enable_if_t<BooleanWriter::is_boolean_v<T>, bool> = true> | |
inline constexpr void operator+=(T&& value) noexcept | |
{ | |
BooleanWriter { _serializer } << std::forward<T>(value); | |
} | |
template<typename T, std::enable_if_t<NumberWriter::is_number_v<T>, bool> = true> | |
inline constexpr void operator<<(T&& value) noexcept | |
{ | |
NumberWriter { _serializer } << std::forward<T>(value); | |
} | |
template<typename T, std::enable_if_t<NumberWriter::is_number_v<T>, bool> = true> | |
inline constexpr void operator+=(T&& value) noexcept | |
{ | |
NumberWriter { _serializer } += std::forward<T>(value); | |
} | |
template<typename T, std::enable_if_t<StringWriter::is_string_v<T>, bool> = true> | |
inline StringWriter operator<<(T&& value) noexcept | |
{ | |
StringWriter writer { _serializer }; | |
writer << std::forward<T>(value); | |
return writer; | |
} | |
template<typename T, std::enable_if_t<StringWriter::is_string_v<T>, bool> = true> | |
inline StringWriter operator+=(T&& value) noexcept | |
{ | |
StringWriter writer { _serializer }; | |
writer += std::forward<T>(value); | |
return writer; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<traits::is_variant_v<U>, bool> = true> | |
inline constexpr void operator<<(T&& variant) noexcept | |
{ | |
if (variant.valueless_by_exception()) [[unlikely]] { | |
_serializer.write("null"); | |
return; | |
} | |
std::visit([this](const auto& value) constexpr noexcept { *this << value; }, std::forward<T>(variant)); | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<traits::is_variant_v<U>, bool> = true> | |
inline constexpr void operator+=(T&& variant) noexcept | |
{ | |
if (variant.valueless_by_exception()) [[unlikely]] { | |
_serializer.write("null"); | |
return; | |
} | |
std::visit([this](const auto& value) constexpr noexcept { *this += value; }, std::forward<T>(variant)); | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<traits::is_stl_container_v<U> && !StringWriter::is_string_v<T> | |
&& !traits::is_stl_map_container_v<U>, | |
bool> | |
= true> | |
inline ArrayWriter<ForwardWriter> operator<<(T&& value) noexcept | |
{ | |
ArrayWriter<ForwardWriter> writer { _serializer }; | |
for (const auto& item: value) { | |
writer << item; | |
} | |
return writer; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<traits::is_stl_container_v<U> && !StringWriter::is_string_v<T> | |
&& !traits::is_stl_map_container_v<U>, | |
bool> | |
= true> | |
inline ArrayWriter<ForwardWriter> operator+=(T&& value) noexcept | |
{ | |
ArrayWriter<ForwardWriter> writer { _serializer }; | |
for (const auto& item: value) { | |
writer += item; | |
} | |
return writer; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<traits::is_stl_map_container_v<U>, bool> = true> | |
inline ObjectWriter operator<<(T&& map) noexcept | |
{ | |
ObjectWriter writer { _serializer }; | |
for (const auto& [key, value]: map) { | |
writer(key) << value; | |
} | |
return writer; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<traits::is_stl_map_container_v<U>, bool> = true> | |
inline ObjectWriter operator+=(T&& map) noexcept | |
{ | |
ObjectWriter writer { _serializer }; | |
for (const auto& [key, value]: map) { | |
writer[key] += value; | |
} | |
return writer; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_same_v<U, std::nullptr_t>, bool> = true> | |
inline constexpr void operator<<(T&&) noexcept | |
{ | |
_serializer.write("null"); | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_same_v<U, std::nullptr_t>, bool> = true> | |
inline constexpr void operator+=(T&&) noexcept | |
{ | |
_serializer.write("null"); | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_same_v<U, std::monostate>, bool> = true> | |
inline constexpr void operator<<(T&&) noexcept | |
{ | |
_serializer.write("null"); | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_same_v<U, std::monostate>, bool> = true> | |
inline constexpr void operator+=(T&&) noexcept | |
{ | |
_serializer.write("null"); | |
} | |
private: | |
JSerializer& _serializer; | |
}; | |
friend class BooleanWriter; | |
friend class NumberWriter; | |
friend class StringWriter; | |
friend class ArrayWriter<ForwardWriter>; | |
friend class ObjectWriter; | |
friend class ForwardWriter; | |
template<typename T = std::unique_ptr<JSerializer>, typename U = std::remove_cv_t<std::remove_reference_t<T>>> | |
[[nodiscard]] static constexpr U create(WriteCallback write_callback = StdoutStream::write, | |
FlushCallback flush_callback = StdoutStream::flush, void* buffer = nullptr, size_t size = 0) noexcept | |
{ | |
if (!write_callback) [[unlikely]] { | |
write_callback = StdoutStream::write; | |
} | |
if (!flush_callback) [[unlikely]] { | |
flush_callback = StdoutStream::flush; | |
} | |
size = size ? size : default_buffer_size; | |
if (size < const_buffer_size_min) [[unlikely]] { | |
return U {}; | |
} | |
bool internal_buffer = false; | |
if (!buffer) { | |
buffer = static_cast<void*>(new (std::nothrow) unsigned char[size]); | |
if (!buffer) [[unlikely]] { | |
return U {}; | |
} | |
internal_buffer = true; | |
} | |
U ptr { new (std::nothrow) JSerializer(write_callback, flush_callback, internal_buffer, | |
static_cast<unsigned char*>(buffer), size) }; | |
if (!ptr) [[unlikely]] { | |
if (internal_buffer) [[likely]] { | |
delete[] static_cast<unsigned char*>(buffer); | |
} | |
return U {}; | |
} | |
return ptr; | |
} | |
~JSerializer() noexcept | |
{ | |
if (_internal_buffer && _buffer) [[likely]] { | |
delete[] _buffer; | |
} | |
_buffer = nullptr; | |
} | |
JSerializer(const JSerializer&) = delete; | |
JSerializer& operator=(const JSerializer&) = delete; | |
JSerializer(JSerializer&&) = delete; | |
JSerializer& operator=(JSerializer&&) = delete; | |
[[nodiscard]] inline ObjectWriter writer(WriteCallback write_callback = nullptr, | |
FlushCallback flush_callback = nullptr) noexcept | |
{ | |
if (write_callback) { | |
_write_callback = write_callback; | |
} | |
if (flush_callback) { | |
_flush_callback = flush_callback; | |
} | |
return ObjectWriter { *this, true }; | |
} | |
inline constexpr int state() const noexcept | |
{ | |
return _error_code; | |
} | |
bool reset() noexcept | |
{ | |
_error_code = 0; | |
_buffer = _buffer_s; | |
return _error_code == 0; | |
} | |
protected: | |
explicit JSerializer(WriteCallback write_callback, FlushCallback flush_callback, bool internal_buffer, | |
unsigned char* buffer, size_t size) noexcept | |
: _write_callback(write_callback), _flush_callback(flush_callback), _internal_buffer(internal_buffer), | |
_buffer_size(size), _buffer_s(buffer), _buffer_e(_buffer_s + _buffer_size), | |
_buffer_e_fmt(_buffer_e - default_fmt_buffer_size), _buffer(_buffer_s), _error_code(0) | |
{ | |
} | |
constexpr void error(int error_code) noexcept | |
{ | |
if (_error_code == 0) { | |
_error_code = error_code; | |
} | |
} | |
inline constexpr void sync() noexcept | |
{ | |
const unsigned char* end = _buffer; | |
const unsigned char* ptr = _buffer_s; | |
while (ptr < end) [[likely]] { | |
const int ret = _write_callback(ptr, static_cast<size_t>(end - ptr)); | |
if (ret < 0) [[unlikely]] { | |
error(-ret); | |
break; | |
} | |
ptr += ret; | |
} | |
_buffer = _buffer_s; | |
} | |
inline constexpr void flush() noexcept | |
{ | |
if (_buffer > _buffer_s) [[likely]] { | |
sync(); | |
} | |
const int ret = _flush_callback(_error_code); | |
if (ret < 0) [[unlikely]] { | |
error(-ret); | |
} | |
} | |
inline constexpr void write(const char symbol) noexcept | |
{ | |
unsigned char* buffer = _buffer; | |
if (buffer >= _buffer_e) [[unlikely]] { | |
sync(); | |
buffer = _buffer; | |
} | |
*buffer = static_cast<unsigned char>(symbol); | |
_buffer = buffer + 1; | |
} | |
template<size_t N, std::enable_if_t<N != 1, bool> = true> | |
inline constexpr void write(const char (&symbol)[N]) noexcept | |
{ | |
constexpr const size_t size = N - 1; | |
unsigned char* buffer = _buffer; | |
unsigned char* buffer_e = buffer + size; | |
if (buffer_e > _buffer_e) [[unlikely]] { | |
sync(); | |
buffer = _buffer; | |
buffer_e = buffer + size; | |
} | |
std::memcpy(buffer, symbol, size); | |
_buffer = buffer_e; | |
} | |
template<typename T, typename U = std::remove_cv_t<std::remove_reference_t<T>>, | |
std::enable_if_t<std::is_integral_v<U> | |
&& (sizeof(U) == sizeof(uint8_t) || sizeof(U) == sizeof(uint16_t) | |
|| sizeof(U) == sizeof(uint32_t) || sizeof(U) == sizeof(uint64_t)), | |
bool> | |
= true> | |
inline constexpr bool write(T&& value) noexcept | |
{ | |
if (_buffer > _buffer_e_fmt) [[unlikely]] { | |
sync(); | |
} | |
char* buffer = reinterpret_cast<char*>(_buffer); | |
char* ptr = charconv::itoa(std::forward<T>(value), buffer); | |
_buffer = reinterpret_cast<unsigned char*>(ptr); | |
return true; | |
} | |
template<typename T, | |
std::enable_if_t<std::is_floating_point_v<std::remove_cv_t<std::remove_reference_t<T>>>, bool> = true> | |
inline constexpr bool write(T&& value) noexcept | |
{ | |
if (_buffer > _buffer_e_fmt) [[unlikely]] { | |
sync(); | |
} | |
char* buffer = reinterpret_cast<char*>(_buffer); | |
char* buffer_e = reinterpret_cast<char*>(_buffer_e); | |
const auto [ptr, ec] = std::to_chars(buffer, buffer_e, value, std::chars_format::fixed); | |
if (ec != std::errc()) [[unlikely]] { | |
error(static_cast<int>(ec)); | |
return false; | |
} | |
_buffer = reinterpret_cast<unsigned char*>(ptr); | |
return true; | |
} | |
template<typename T, | |
std::enable_if_t<std::is_floating_point_v<std::remove_cv_t<std::remove_reference_t<T>>>, bool> = true> | |
inline constexpr bool write(T&& value, int precision) noexcept | |
{ | |
if (_buffer > _buffer_e_fmt) [[unlikely]] { | |
sync(); | |
} | |
char* buffer = reinterpret_cast<char*>(_buffer); | |
char* buffer_e = reinterpret_cast<char*>(_buffer_e); | |
const auto [ptr, ec] = std::to_chars(buffer, buffer_e, value, std::chars_format::general, precision); | |
if (ec != std::errc()) [[unlikely]] { | |
error(static_cast<int>(ec)); | |
return false; | |
} | |
_buffer = reinterpret_cast<unsigned char*>(ptr); | |
return true; | |
} | |
#if JS_EXPERIMENTAL_SIMD | |
inline constexpr void vwrite(const char* value, size_t len) noexcept | |
{ | |
const char* value_end = value + len; | |
const char* value_end_simd = value_end - const_simd_size; | |
const char* value_pos = value; | |
while (value < value_end_simd) [[likely]] { | |
const stdx::fixed_size_simd<unsigned char, const_simd_size> chunk { | |
reinterpret_cast<const unsigned char*>(value), stdx::vector_aligned | |
}; | |
const auto escape { (chunk == _simd_escape_quotes) | (chunk == _simd_escape_backslashes) | |
| (chunk == _simd_escape_cntrl_eqb) | (chunk <= _simd_escape_cntrl_leb) }; | |
if (stdx::any_of(escape)) [[unlikely]] { | |
const int bad_s = stdx::find_first_set(escape); | |
const int bad_e = stdx::find_last_set(escape); | |
const char* value_cur = value + bad_s; | |
size_t size = static_cast<size_t>(value_cur - value_pos); | |
if (size) [[likely]] { | |
write(static_cast<const void*>(value_pos), size); | |
} | |
size = static_cast<size_t>((bad_e - bad_s) + 1); | |
swrite(value_cur, size); | |
value_pos = value_cur + size; | |
} | |
value += const_simd_size; | |
} | |
if (value_pos < value_end) [[likely]] { | |
swrite(value_pos, static_cast<size_t>(value_end - value_pos)); | |
} | |
} | |
#endif | |
inline constexpr void swrite(const char* value, size_t len) noexcept | |
{ | |
const char* value_end = value + len; | |
const char* value_pos = value; | |
while (value < value_end) [[likely]] { | |
const char c = *value; | |
if (c == '"') [[unlikely]] { | |
const size_t size = static_cast<size_t>(value - value_pos); | |
if (size) [[likely]] { | |
write(static_cast<const void*>(value_pos), size); | |
} | |
value_pos = ++value; | |
write("\\\""); | |
continue; | |
} | |
else if (c == '\\') [[unlikely]] { | |
const size_t size = static_cast<size_t>(value - value_pos); | |
if (size) [[likely]] { | |
write(static_cast<const void*>(value_pos), size); | |
} | |
value_pos = ++value; | |
write("\\\\"); | |
continue; | |
} | |
else if (std::iscntrl(static_cast<unsigned char>(c))) [[unlikely]] { | |
const size_t size = static_cast<size_t>(value - value_pos); | |
if (size) [[likely]] { | |
write(static_cast<const void*>(value_pos), size); | |
} | |
value_pos = ++value; | |
switch (c) { | |
case '\t': | |
write("\\t"); | |
continue; | |
case '\n': | |
write("\\n"); | |
continue; | |
case '\f': | |
write("\\f"); | |
continue; | |
case '\r': | |
write("\\r"); | |
continue; | |
case '\b': | |
write("\\b"); | |
continue; | |
default: | |
const char unicode[6] | |
= { '\\', 'u', '0', '0', static_cast<char>(_hex_lookup_tbl[c >> 4]), | |
static_cast<char>(_hex_lookup_tbl[c & 0x0F]) }; | |
write(static_cast<const void*>(unicode), sizeof(unicode)); | |
continue; | |
} | |
} | |
++value; | |
} | |
if (value_pos < value_end) [[likely]] { | |
write(static_cast<const void*>(value_pos), static_cast<size_t>(value_end - value_pos)); | |
} | |
} | |
inline constexpr void write(const char* value, size_t len) noexcept | |
{ | |
#if JS_EXPERIMENTAL_SIMD | |
if (len < default_simd_enable_len) [[likely]] { | |
swrite(value, len); | |
return; | |
} | |
const size_t align_shift = reinterpret_cast<std::uintptr_t>(value) & const_simd_memory_alignment_mask; | |
if (align_shift) { | |
const size_t align_len = const_simd_memory_alignment - align_shift; | |
swrite(value, align_len); | |
value += align_len; | |
len -= align_len; | |
} | |
vwrite(value, len); | |
#else | |
swrite(value, len); | |
#endif | |
} | |
inline constexpr bool write(const void* data, size_t size) noexcept | |
{ | |
unsigned char* buffer = _buffer; | |
unsigned char* buffer_e = buffer + size; | |
if (buffer_e <= _buffer_e) { | |
std::memcpy(buffer, data, size); | |
_buffer = buffer_e; | |
return true; | |
} | |
sync(); | |
if (size <= _buffer_size) [[likely]] { | |
buffer = _buffer; | |
std::memcpy(buffer, data, size); | |
_buffer = buffer + size; | |
return true; | |
} | |
const unsigned char* data_ptr = static_cast<const unsigned char*>(data); | |
const unsigned char* data_end = data_ptr + size; | |
while (data_ptr < data_end) [[likely]] { | |
const int ret = _write_callback(data_ptr, static_cast<size_t>(data_end - data_ptr)); | |
if (ret < 0) [[unlikely]] { | |
error(-ret); | |
return false; | |
} | |
data_ptr += ret; | |
} | |
return true; | |
} | |
private: | |
WriteCallback _write_callback; | |
FlushCallback _flush_callback; | |
const bool _internal_buffer; | |
const size_t _buffer_size; | |
unsigned char* _buffer_s; | |
unsigned char* _buffer_e; | |
unsigned char* _buffer_e_fmt; | |
unsigned char* _buffer; | |
int _error_code; | |
private: | |
alignas(std::hardware_destructive_interference_size) static inline constexpr const | |
char _hex_lookup_tbl[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; | |
#if JS_EXPERIMENTAL_SIMD | |
static inline const stdx::fixed_size_simd<unsigned char, const_simd_size> _simd_escape_quotes { | |
static_cast<unsigned char>('"') | |
}; | |
static inline const stdx::fixed_size_simd<unsigned char, const_simd_size> _simd_escape_backslashes { | |
static_cast<unsigned char>('\\') | |
}; | |
static inline const stdx::fixed_size_simd<unsigned char, const_simd_size> _simd_escape_cntrl_leb { | |
static_cast<unsigned char>(0x1F) | |
}; | |
static inline const stdx::fixed_size_simd<unsigned char, const_simd_size> _simd_escape_cntrl_eqb { | |
static_cast<unsigned char>(0x7F) | |
}; | |
#endif | |
}; | |
using StdoutStream = JSerializer::StdoutStream; | |
using BooleanWriter = JSerializer::BooleanWriter; | |
using NumberWriter = JSerializer::NumberWriter; | |
using StringWriter = JSerializer::StringWriter; | |
using ObjectWriter = JSerializer::ObjectWriter; | |
using ForwardWriter = JSerializer::ForwardWriter; | |
using ArrayWriter = JSerializer::ArrayWriter<ForwardWriter>; | |
#undef JS_BUFFER_SIZE_MIN | |
#undef JS_FMT_BUFFER_SIZE_MIN | |
} // namespace ubn | |
#endif // JSERIALIZER_HPP |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment