Files
aza/APP/nexus-remote/node_modules/koffi/lib/native/base/base.hh
2026-03-25 14:14:07 +01:00

6004 lines
172 KiB
C++

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2025 Niels Martignène <niels.martignene@protonmail.com>
#pragma once
#include <algorithm>
#include <atomic>
#include <cmath>
#include <condition_variable>
#include <errno.h>
#include <float.h>
#include <functional>
#include <inttypes.h>
#include <limits.h>
#include <limits>
#include <memory>
#include <mutex>
#include <shared_mutex>
#if __cplusplus >= 202002L && __has_include(<source_location>)
#include <source_location>
#endif
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <thread>
#include <type_traits>
#include <utility>
#if defined(_WIN32)
#if !defined(STDIN_FILENO)
#define STDIN_FILENO 0
#endif
#if !defined(STDOUT_FILENO)
#define STDOUT_FILENO 1
#endif
#if !defined(STDERR_FILENO)
#define STDERR_FILENO 2
#endif
#include <intrin.h>
#else
#include <unistd.h>
#endif
#if defined(_MSC_VER)
#define ENABLE_INTSAFE_SIGNED_FUNCTIONS
#include <intsafe.h>
#pragma intrinsic(_BitScanReverse)
#if defined(_WIN64)
#pragma intrinsic(_BitScanReverse64)
#endif
#if defined(_M_IX86) || defined(_M_X64)
#pragma intrinsic(__rdtsc)
#endif
#endif
struct sigaction;
struct BrotliEncoderStateStruct;
namespace K {
// ------------------------------------------------------------------------
// Config
// ------------------------------------------------------------------------
#if !defined(NDEBUG)
#define K_DEBUG
#endif
#define K_DEFAULT_ALLOCATOR MallocAllocator
#define K_BLOCK_ALLOCATOR_DEFAULT_SIZE Kibibytes(4)
#define K_HEAPARRAY_BASE_CAPACITY 8
#define K_HEAPARRAY_GROWTH_FACTOR 2.0
// Must be a power-of-two
#define K_HASHTABLE_BASE_CAPACITY 8
#define K_HASHTABLE_MAX_LOAD_FACTOR 0.5
#define K_FMT_STRING_BASE_CAPACITY 256
#define K_FMT_STRING_PRINT_BUFFER_SIZE 1024
#define K_LINE_READER_STEP_SIZE 65536
#define K_ASYNC_MAX_THREADS 2048
#define K_ASYNC_MAX_IDLE_TIME 10000
#define K_ASYNC_MAX_PENDING_TASKS 2048
#define K_PROGRESS_MAX_NODES 400
#define K_PROGRESS_USED_NODES 100
#define K_PROGRESS_TEXT_SIZE 64
#define K_COMPLETE_PATH_LIMIT 256
// ------------------------------------------------------------------------
// Utility
// ------------------------------------------------------------------------
class StreamReader;
class StreamWriter;
extern "C" const char *FelixTarget;
extern "C" const char *FelixVersion;
extern "C" const char *FelixCompiler;
extern StreamReader *const StdIn;
extern StreamWriter *const StdOut;
extern StreamWriter *const StdErr;
#if defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64) || __riscv_xlen == 64 || defined(__loongarch64)
typedef int64_t Size;
#define K_SIZE_MAX INT64_MAX
#elif defined(_WIN32) || defined(__APPLE__) || defined(__unix__)
typedef int32_t Size;
#define K_SIZE_MAX INT32_MAX
#elif defined(__thumb__) || defined(__arm__) || defined(__wasm32__)
typedef int32_t Size;
#define K_SIZE_MAX INT32_MAX
#else
#error Machine architecture not supported
#endif
#if defined(_MSC_VER) || __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
// Sane platform
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define K_BIG_ENDIAN
#else
#error This code base is not designed to support platforms with crazy endianness
#endif
#if UINT_MAX != 0xFFFFFFFFu
#error This code base is not designed to support non-32-bits int types
#endif
#if ULLONG_MAX != 0xFFFFFFFFFFFFFFFFull
#error This code base is not designed to support non-64-bits long long types
#endif
static_assert(sizeof(double) == 8, "This code base is not designed to support single-precision double floats");
#define K_STRINGIFY_(a) #a
#define K_STRINGIFY(a) K_STRINGIFY_(a)
#define K_CONCAT_(a, b) a ## b
#define K_CONCAT(a, b) K_CONCAT_(a, b)
#define K_UNIQUE_NAME(prefix) K_CONCAT(prefix, __LINE__)
#define K_FORCE_EXPAND(x) x
#define K_IGNORE (void)!
#if defined(__GNUC__) || defined(__clang__)
#define K_PUSH_NO_WARNINGS \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wall\"") \
_Pragma("GCC diagnostic ignored \"-Wextra\"") \
_Pragma("GCC diagnostic ignored \"-Wconversion\"") \
_Pragma("GCC diagnostic ignored \"-Wsign-conversion\"") \
_Pragma("GCC diagnostic ignored \"-Wunused-function\"") \
_Pragma("GCC diagnostic ignored \"-Wunused-variable\"") \
_Pragma("GCC diagnostic ignored \"-Wunused-but-set-variable\"") \
_Pragma("GCC diagnostic ignored \"-Wunused-parameter\"") \
_Pragma("GCC diagnostic ignored \"-Wzero-as-null-pointer-constant\"") \
_Pragma("GCC diagnostic ignored \"-Winvalid-offsetof\"")
#define K_POP_NO_WARNINGS \
_Pragma("GCC diagnostic pop")
#if !defined(SCNd8)
#define SCNd8 "hhd"
#endif
#if !defined(SCNi8)
#define SCNi8 "hhd"
#endif
#if !defined(SCNu8)
#define SCNu8 "hhu"
#endif
#elif defined(_MSC_VER)
#define K_PUSH_NO_WARNINGS __pragma(warning(push, 0))
#define K_POP_NO_WARNINGS __pragma(warning(pop))
#define __restrict__ __restrict
#else
#error Compiler not supported
#endif
#if defined(__clang__)
#if __has_feature(address_sanitizer)
#define __SANITIZE_ADDRESS__
#endif
#if __has_feature(thread_sanitizer)
#define __SANITIZE_THREAD__
#endif
#if __has_feature(undefined_sanitizer)
#define __SANITIZE_UNDEFINED__
#endif
#endif
extern "C" void AssertMessage(const char *filename, int line, const char *cond);
#if defined(_MSC_VER)
#define K_DEBUG_BREAK() __debugbreak()
#elif defined(__clang__)
#define K_DEBUG_BREAK() __builtin_debugtrap()
#elif defined(__i386__) || defined(__x86_64__)
#define K_DEBUG_BREAK() __asm__ __volatile__("int $0x03")
#elif defined(__thumb__)
#define K_DEBUG_BREAK() __asm__ __volatile__(".inst 0xde01")
#elif defined(__aarch64__)
#define K_DEBUG_BREAK() __asm__ __volatile__(".inst 0xd4200000")
#elif defined(__arm__)
#define K_DEBUG_BREAK() __asm__ __volatile__(".inst 0xe7f001f0")
#elif defined(__riscv)
#define K_DEBUG_BREAK() __asm__ __volatile__("ebreak")
#elif defined(__loongarch64)
#define K_DEBUG_BREAK() __asm__ __volatile__("break 1")
#endif
#if defined(_MSC_VER) || __EXCEPTIONS
#define K_BAD_ALLOC() \
do { \
throw std::bad_alloc(); \
} while (false)
#else
#define K_BAD_ALLOC() \
do { \
PrintLn(StdErr, "Memory allocation failed"); \
abort(); \
} while (false);
#endif
#define K_CRITICAL(Cond, ...) \
do { \
if (!(Cond)) [[unlikely]] { \
PrintLn(StdErr, __VA_ARGS__); \
abort(); \
} \
} while (false)
#if defined(K_DEBUG)
#define K_ASSERT(Cond) \
do { \
if (!(Cond)) [[unlikely]] { \
K::AssertMessage(__FILE__, __LINE__, K_STRINGIFY(Cond)); \
K_DEBUG_BREAK(); \
abort(); \
} \
} while (false)
#else
#define K_ASSERT(Cond) \
do { \
(void)sizeof(Cond); \
} while (false)
#endif
#if defined(K_DEBUG)
#define K_UNREACHABLE() \
do { \
K::AssertMessage(__FILE__, __LINE__, "Reached code marked as UNREACHABLE"); \
K_DEBUG_BREAK(); \
abort(); \
} while (false)
#elif defined(__GNUC__) || defined(__clang__)
#define K_UNREACHABLE() __builtin_unreachable()
#else
#define K_UNREACHABLE() __assume(0)
#endif
#define K_DELETE_COPY(Cls) \
Cls(const Cls&) = delete; \
Cls &operator=(const Cls&) = delete;
constexpr uint16_t MakeUInt16(uint8_t high, uint8_t low)
{ return (uint16_t)(((uint16_t)high << 8) | low); }
constexpr uint32_t MakeUInt32(uint16_t high, uint16_t low) { return ((uint32_t)high << 16) | low; }
constexpr uint64_t MakeUInt64(uint32_t high, uint32_t low) { return ((uint64_t)high << 32) | low; }
constexpr Size Mebibytes(Size len) { return len * 1024 * 1024; }
constexpr Size Kibibytes(Size len) { return len * 1024; }
constexpr Size Megabytes(Size len) { return len * 1000 * 1000; }
constexpr Size Kilobytes(Size len) { return len * 1000; }
#define K_SIZE(Type) ((K::Size)sizeof(Type))
template <typename T, unsigned N>
char (&ComputeArraySize(T const (&)[N]))[N];
#define K_BITS(Type) (8 * K_SIZE(Type))
#define K_LEN(Array) K_SIZE(K::ComputeArraySize(Array))
static constexpr inline uint16_t ReverseBytes(uint16_t u)
{
return (uint16_t)(((u & 0x00FF) << 8) |
((u & 0xFF00) >> 8));
}
static constexpr inline uint32_t ReverseBytes(uint32_t u)
{
return ((u & 0x000000FF) << 24) |
((u & 0x0000FF00) << 8) |
((u & 0x00FF0000) >> 8) |
((u & 0xFF000000) >> 24);
}
static constexpr inline uint64_t ReverseBytes(uint64_t u)
{
return ((u & 0x00000000000000FF) << 56) |
((u & 0x000000000000FF00) << 40) |
((u & 0x0000000000FF0000) << 24) |
((u & 0x00000000FF000000) << 8) |
((u & 0x000000FF00000000) >> 8) |
((u & 0x0000FF0000000000) >> 24) |
((u & 0x00FF000000000000) >> 40) |
((u & 0xFF00000000000000) >> 56);
}
static constexpr inline int16_t ReverseBytes(int16_t i)
{ return (int16_t)ReverseBytes((uint16_t)i); }
static constexpr inline int32_t ReverseBytes(int32_t i)
{ return (int32_t)ReverseBytes((uint32_t)i); }
static constexpr inline int64_t ReverseBytes(int64_t i)
{ return (int64_t)ReverseBytes((uint64_t)i); }
#if defined(K_BIG_ENDIAN)
template <typename T>
constexpr T LittleEndian(T v) { return ReverseBytes(v); }
template <typename T>
constexpr T BigEndian(T v) { return v; }
#else
template <typename T>
constexpr T LittleEndian(T v) { return v; }
template <typename T>
constexpr T BigEndian(T v) { return ReverseBytes(v); }
#endif
#if defined(__GNUC__)
static inline int CountLeadingZeros(uint32_t u)
{
if (!u)
return 32;
return __builtin_clz(u);
}
static inline int CountLeadingZeros(uint64_t u)
{
if (!u)
return 64;
#if UINT64_MAX == ULONG_MAX
return __builtin_clzl(u);
#elif UINT64_MAX == ULLONG_MAX
return __builtin_clzll(u);
#else
#error Neither unsigned long nor unsigned long long is a 64-bit unsigned integer
#endif
}
static inline int CountTrailingZeros(uint32_t u)
{
if (!u)
return 32;
return __builtin_ctz(u);
}
static inline int CountTrailingZeros(uint64_t u)
{
if (!u)
return 64;
#if UINT64_MAX == ULONG_MAX
return __builtin_ctzl(u);
#elif UINT64_MAX == ULLONG_MAX
return __builtin_ctzll(u);
#else
#error Neither unsigned long nor unsigned long long is a 64-bit unsigned integer
#endif
}
static inline int PopCount(uint32_t u)
{
return __builtin_popcount(u);
}
static inline int PopCount(uint64_t u)
{
return __builtin_popcountll(u);
}
#elif defined(_MSC_VER)
static inline int CountLeadingZeros(uint32_t u)
{
unsigned long leading_zero;
if (_BitScanReverse(&leading_zero, u)) {
return (int)(31 - leading_zero);
} else {
return 32;
}
}
static inline int CountLeadingZeros(uint64_t u)
{
unsigned long leading_zero;
#if defined(_WIN64)
if (_BitScanReverse64(&leading_zero, u)) {
return (int)(63 - leading_zero);
} else {
return 64;
}
#else
if (_BitScanReverse(&leading_zero, u >> 32)) {
return (int)(31 - leading_zero);
} else if (_BitScanReverse(&leading_zero, (uint32_t)u)) {
return (int)(63 - leading_zero);
} else {
return 64;
}
#endif
}
static inline int CountTrailingZeros(uint32_t u)
{
unsigned long trailing_zero;
if (_BitScanForward(&trailing_zero, u)) {
return (int)trailing_zero;
} else {
return 32;
}
}
static inline int CountTrailingZeros(uint64_t u)
{
unsigned long trailing_zero;
#if defined(_WIN64)
if (_BitScanForward64(&trailing_zero, u)) {
return (int)trailing_zero;
} else {
return 64;
}
#else
if (_BitScanForward(&trailing_zero, (uint32_t)u)) {
return trailing_zero;
} else if (_BitScanForward(&trailing_zero, u >> 32)) {
return 32 + trailing_zero;
} else {
return 64;
}
#endif
}
static inline int PopCount(uint32_t u)
{
#if defined(_M_ARM64)
uint32_t count;
u = u - ((u >> 1) & 0x55555555);
u = (u & 0x33333333) + ((u >> 2) & 0x33333333);
count = ((u + (u >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
return (int)count;
#else
return __popcnt(u);
#endif
}
static inline int PopCount(uint64_t u)
{
#if defined(_M_X64)
return (int)__popcnt64(u);
#else
int count = PopCount((uint32_t)(u >> 32)) + PopCount((uint32_t)u);
return count;
#endif
}
#else
#error No implementation of CountLeadingZeros(), CountTrailingZeros() and PopCount() for this compiler / toolchain
#endif
#if __cplusplus >= 202002L && (defined(_MSC_VER) || defined(__clang__) || __GNUC__ >= 11)
#define K_CONSTINIT constinit
#else
#define K_CONSTINIT const
#endif
static inline Size AlignLen(Size len, Size align)
{
Size aligned = (len + align - 1) / align * align;
return aligned;
}
template <typename T>
static inline T *AlignUp(T *ptr, Size align)
{
uint8_t *aligned = (uint8_t *)(((uintptr_t)ptr + align - 1) / align * align);
return (T *)aligned;
}
template <typename T>
static inline T *AlignDown(T *ptr, Size align)
{
uint8_t *aligned = (uint8_t *)((uintptr_t)ptr / align * align);
return (T *)aligned;
}
// Calling memcpy (and friends) with a NULL source pointer is undefined behavior
// even if length is 0. This is dumb, work around this.
static inline void *MemCpy(void *__restrict__ dest, const void *__restrict__ src, Size len)
{
K_ASSERT(len >= 0);
#if defined(__clang__)
// LLVM guarantees sane behavior
__builtin_memcpy(dest, src, (size_t)len);
#else
if (len) {
memcpy(dest, src, (size_t)len);
}
#endif
return dest;
}
static inline void *MemMove(void *dest, const void *src, Size len)
{
K_ASSERT(len >= 0);
#if defined(__clang__)
// LLVM guarantees sane behavior
__builtin_memmove(dest, src, (size_t)len);
#else
if (len) {
memmove(dest, src, (size_t)len);
}
#endif
return dest;
}
static inline void *MemSet(void *dest, int c, Size len)
{
K_ASSERT(len >= 0);
#if defined(__clang__)
// LLVM guarantees sane behavior
__builtin_memset(dest, c, (size_t)len);
#else
if (len) {
memset(dest, c, (size_t)len);
}
#endif
return dest;
}
#if defined(_WIN32)
void *MemMem(const void *src, Size src_len, const void *needle, Size needle_len);
#else
static inline void *MemMem(const void *src, Size src_len, const void *needle, Size needle_len)
{
K_ASSERT(src_len >= 0);
K_ASSERT(needle_len > 0);
void *ptr = memmem(src, (size_t)src_len, needle, (size_t)needle_len);
return ptr;
}
#endif
// Implemented for translations, but we need it before we get to this part
const char *T(const char *key);
template <typename T, typename = typename std::enable_if<std::is_enum<T>::value, T>>
typename std::underlying_type<T>::type MaskEnum(T value)
{
auto mask = 1 << static_cast<typename std::underlying_type<T>::type>(value);
return (typename std::underlying_type<T>::type)mask;
}
template <typename Fun>
class DeferGuard {
K_DELETE_COPY(DeferGuard)
Fun f;
bool enabled;
public:
DeferGuard() = delete;
DeferGuard(Fun f_, bool enable = true) : f(std::move(f_)), enabled(enable) {}
~DeferGuard()
{
if (enabled) {
f();
}
}
DeferGuard(DeferGuard &&other)
: f(std::move(other.f)), enabled(other.enabled)
{
other.enabled = false;
}
void Disable() { enabled = false; }
};
// Honestly, I don't understand all the details in there, this comes from Andrei Alexandrescu.
// https://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C
struct DeferGuardHelper {};
template <typename Fun>
DeferGuard<Fun> operator+(DeferGuardHelper, Fun &&f)
{
return DeferGuard<Fun>(std::forward<Fun>(f));
}
// Write 'DEFER { code };' to do something at the end of the current scope, you
// can use DEFER_N(Name) if you need to disable the guard for some reason, and
// DEFER_NC(Name, Captures) if you need to capture values.
#define K_DEFER \
auto K_UNIQUE_NAME(defer) = K::DeferGuardHelper() + [&]()
#define K_DEFER_N(Name) \
auto Name = K::DeferGuardHelper() + [&]()
#define K_DEFER_C(...) \
auto K_UNIQUE_NAME(defer) = K::DeferGuardHelper() + [&, __VA_ARGS__]()
#define K_DEFER_NC(Name, ...) \
auto Name = K::DeferGuardHelper() + [&, __VA_ARGS__]()
template <typename T>
class NoDestroy {
K_DELETE_COPY(NoDestroy);
alignas(T) uint8_t data[K_SIZE(T)];
public:
template <class... Args>
NoDestroy(Args&&... args) { new (data) T(std::forward<Args>(args)...); }
~NoDestroy() = default;
const T *Get() const { return (const T *)(data); }
T *Get() { return (T*)data; }
const T &operator*() const { return *Get(); }
T& operator*() { return *Get(); }
const T *operator->() const { return Get(); }
T *operator->() { return Get(); }
};
// Heavily inspired from FunctionRef in LLVM
template<typename Fn> class FunctionRef;
template<typename Ret, typename ...Params>
class FunctionRef<Ret(Params...)> {
Ret (*callback)(intptr_t callable, Params ...params) = nullptr;
intptr_t callable;
template<typename Callable>
static Ret callback_fn(intptr_t callable, Params ...params)
{ return (*reinterpret_cast<Callable*>(callable))(std::forward<Params>(params)...); }
public:
FunctionRef() = default;
template <typename Callable>
FunctionRef(Callable &&callable,
std::enable_if_t<!std::is_same<std::remove_cv_t<std::remove_reference_t<Callable>>, FunctionRef>::value> * = nullptr,
std::enable_if_t<std::is_void<Ret>::value ||
std::is_convertible<decltype(std::declval<Callable>()(std::declval<Params>()...)),
Ret>::value> * = nullptr)
: callback(callback_fn<typename std::remove_reference<Callable>::type>),
callable(reinterpret_cast<intptr_t>(&callable)) {}
Ret operator()(Params ...params) const
{ return callback(callable, std::forward<Params>(params)...); }
bool IsValid() const { return callback; }
};
template <typename T>
T MultiCmp()
{
return 0;
}
template <typename T, typename... Args>
T MultiCmp(T cmp_value, Args... other_args)
{
if (cmp_value) {
return cmp_value;
} else {
return MultiCmp<T>(other_args...);
}
}
template <typename T, typename U>
T ApplyMask(T value, U mask, bool enable)
{
if (enable) {
return value | (T)mask;
} else {
return value & ~(T)mask;
}
}
template <typename T>
struct Vec2 {
T x;
T y;
};
template <typename T>
struct Vec3 {
T x;
T y;
T z;
};
template <Size N>
class Bitset {
public:
template <typename T>
class Iterator {
public:
typedef std::input_iterator_tag iterator_category;
typedef Size value_type;
typedef Size difference_type;
typedef Iterator *pointer;
typedef Iterator &reference;
T *bitset = nullptr;
Size offset;
size_t bits = 0;
int ctz;
Iterator() = default;
Iterator(T *bitset, Size offset)
: bitset(bitset), offset(offset - 1)
{
operator++();
}
Size operator*() const
{
K_ASSERT(offset <= K_LEN(bitset->data));
if (offset == K_LEN(bitset->data))
return -1;
return offset * K_SIZE(size_t) * 8 + ctz;
}
Iterator &operator++()
{
K_ASSERT(offset <= K_LEN(bitset->data));
while (!bits) {
if (offset == K_LEN(bitset->data) - 1)
return *this;
bits = bitset->data[++offset];
}
ctz = CountTrailingZeros((uint64_t)bits);
bits ^= (size_t)1 << ctz;
return *this;
}
Iterator operator++(int)
{
Iterator ret = *this;
++(*this);
return ret;
}
bool operator==(const Iterator &other) const
{ return bitset == other.bitset && offset == other.offset; }
bool operator!=(const Iterator &other) const { return !(*this == other); }
};
typedef Size value_type;
typedef Iterator<Bitset> iterator_type;
static constexpr Size Bits = N;
size_t data[(N + K_BITS(size_t) - 1) / K_BITS(size_t)] = {};
constexpr Bitset() = default;
constexpr Bitset(std::initializer_list<Size> bits)
{
for (Size idx: bits) {
Size offset = idx / (K_SIZE(size_t) * 8);
size_t mask = (size_t)1 << (idx % (K_SIZE(size_t) * 8));
data[offset] |= mask;
}
}
void Clear()
{
MemSet(data, 0, K_SIZE(data));
}
Iterator<Bitset> begin() { return Iterator<Bitset>(this, 0); }
Iterator<const Bitset> begin() const { return Iterator<const Bitset>(this, 0); }
Iterator<Bitset> end() { return Iterator<Bitset>(this, K_LEN(data)); }
Iterator<const Bitset> end() const { return Iterator<const Bitset>(this, K_LEN(data)); }
Size PopCount() const
{
Size count = 0;
for (size_t bits: data) {
#if K_SIZE_MAX == INT64_MAX
count += K::PopCount((uint64_t)bits);
#else
count += K::PopCount((uint32_t)bits);
#endif
}
return count;
}
inline bool Test(Size idx) const
{
K_ASSERT(idx >= 0 && idx < N);
Size offset = idx / (K_SIZE(size_t) * 8);
size_t mask = (size_t)1 << (idx % (K_SIZE(size_t) * 8));
return data[offset] & mask;
}
inline void Set(Size idx, bool value = true)
{
K_ASSERT(idx >= 0 && idx < N);
Size offset = idx / (K_SIZE(size_t) * 8);
size_t mask = (size_t)1 << (idx % (K_SIZE(size_t) * 8));
data[offset] = ApplyMask(data[offset], mask, value);
}
inline bool TestAndSet(Size idx, bool value = true)
{
K_ASSERT(idx >= 0 && idx < N);
Size offset = idx / (K_SIZE(size_t) * 8);
size_t mask = (size_t)1 << (idx % (K_SIZE(size_t) * 8));
bool ret = data[offset] & mask;
data[offset] = ApplyMask(data[offset], mask, value);
return ret;
}
Bitset &operator&=(const Bitset &other)
{
for (Size i = 0; i < K_LEN(data); i++) {
data[i] &= other.data[i];
}
return *this;
}
Bitset operator&(const Bitset &other)
{
Bitset ret;
for (Size i = 0; i < K_LEN(data); i++) {
ret.data[i] = data[i] & other.data[i];
}
return ret;
}
Bitset &operator|=(const Bitset &other)
{
for (Size i = 0; i < K_LEN(data); i++) {
data[i] |= other.data[i];
}
return *this;
}
Bitset operator|(const Bitset &other)
{
Bitset ret;
for (Size i = 0; i < K_LEN(data); i++) {
ret.data[i] = data[i] | other.data[i];
}
return ret;
}
Bitset &operator^=(const Bitset &other)
{
for (Size i = 0; i < K_LEN(data); i++) {
data[i] ^= other.data[i];
}
return *this;
}
Bitset operator^(const Bitset &other)
{
Bitset ret;
for (Size i = 0; i < K_LEN(data); i++) {
ret.data[i] = data[i] ^ other.data[i];
}
return ret;
}
Bitset &Flip()
{
for (Size i = 0; i < K_LEN(data); i++) {
data[i] = ~data[i];
}
return *this;
}
Bitset operator~()
{
Bitset ret;
for (Size i = 0; i < K_LEN(data); i++) {
ret.data[i] = ~data[i];
}
return ret;
}
// XXX: Shift operators
};
// ------------------------------------------------------------------------
// Memory / Allocator
// ------------------------------------------------------------------------
// I'd love to make Span default to { nullptr, 0 } but unfortunately that makes
// it a non-POD and prevents putting it in a union.
template <typename T>
struct Span {
T *ptr;
Size len;
Span() = default;
constexpr Span(T &value) : ptr(&value), len(1) {}
constexpr Span(std::initializer_list<T> l) : ptr(l.begin()), len((Size)l.size()) {}
constexpr Span(T *ptr_, Size len_) : ptr(ptr_), len(len_) {}
template <Size N>
constexpr Span(T (&arr)[N]) : ptr(arr), len(N) {}
constexpr void Reset()
{
ptr = nullptr;
len = 0;
}
constexpr T *begin() { return ptr; }
constexpr const T *begin() const { return ptr; }
constexpr T *end() { return ptr + len; }
constexpr const T *end() const { return ptr + len; }
constexpr bool IsValid() const { return ptr; }
constexpr T &operator[](Size idx)
{
K_ASSERT(idx >= 0 && idx < len);
return ptr[idx];
}
constexpr const T &operator[](Size idx) const
{
K_ASSERT(idx >= 0 && idx < len);
return ptr[idx];
}
constexpr operator Span<const T>() const { return Span<const T>(ptr, len); }
constexpr bool operator==(const Span &other) const
{
if (len != other.len)
return false;
for (Size i = 0; i < len; i++) {
if (ptr[i] != other.ptr[i])
return false;
}
return true;
}
constexpr bool operator!=(const Span &other) const { return !(*this == other); }
constexpr Span Take(Size offset, Size sub_len) const
{
K_ASSERT(sub_len >= 0 && sub_len <= len);
K_ASSERT(offset >= 0 && offset <= len - sub_len);
Span<T> sub = { ptr + offset, sub_len };
return sub;
}
template <typename U>
constexpr Span<U> As() const { return Span<U>((U *)ptr, len); }
};
// Use strlen() to build Span<const char> instead of the template-based
// array constructor.
template <>
struct Span<const char> {
const char *ptr;
Size len;
Span() = default;
constexpr Span(const char &ch) : ptr(&ch), len(1) {}
constexpr Span(const char *ptr_, Size len_) : ptr(ptr_), len(len_) {}
template <Size N>
Span(const char (&arr)[N]) : ptr(arr), len(strnlen(arr, N)) {}
#if defined(__clang__) || defined(_MSC_VER)
constexpr Span(const char *const &str) : ptr(str), len(str ? (Size)__builtin_strlen(str) : 0) {}
#else
constexpr Span(const char *const &str) : ptr(str), len(str ? (Size)strlen(str) : 0) {}
#endif
constexpr void Reset()
{
ptr = nullptr;
len = 0;
}
constexpr const char *begin() const { return ptr; }
constexpr const char *end() const { return ptr + len; }
constexpr bool IsValid() const { return ptr; }
constexpr char operator[](Size idx) const
{
K_ASSERT(idx >= 0 && idx < len);
return ptr[idx];
}
// The implementation comes later, after TestStr() is available
constexpr bool operator==(Span<const char> other) const;
constexpr bool operator==(const char *other) const;
constexpr bool operator!=(Span<const char> other) const { return !(*this == other); }
constexpr bool operator!=(const char *other) const { return !(*this == other); }
constexpr Span Take(Size offset, Size sub_len) const
{
K_ASSERT(sub_len >= 0 && sub_len <= len);
K_ASSERT(offset >= 0 && offset <= len - sub_len);
Span<const char> sub = { ptr + offset, sub_len };
return sub;
}
template <typename U>
constexpr Span<U> As() const { return Span<U>((U *)ptr, len); }
};
template <typename T>
static constexpr inline Span<T> MakeSpan(T *ptr, Size len)
{
return Span<T>(ptr, len);
}
template <typename T>
static constexpr inline Span<T> MakeSpan(T *ptr, T *end)
{
return Span<T>(ptr, end - ptr);
}
template <typename T, Size N>
static constexpr inline Span<T> MakeSpan(T (&arr)[N])
{
return Span<T>(arr, N);
}
template <typename T>
class Strider {
public:
void *ptr = nullptr;
Size stride;
Strider() = default;
constexpr Strider(T *ptr_) : ptr(ptr_), stride(K_SIZE(T)) {}
constexpr Strider(T *ptr_, Size stride_) : ptr(ptr_), stride(stride_) {}
constexpr bool IsValid() const { return ptr; }
constexpr T &operator[](Size idx) const
{
K_ASSERT(idx >= 0);
return *(T *)((uint8_t *)ptr + (idx * stride));
}
};
template <typename T>
static constexpr inline Strider<T> MakeStrider(T *ptr)
{
return Strider<T>(ptr, K_SIZE(T));
}
template <typename T>
static constexpr inline Strider<T> MakeStrider(T *ptr, Size stride)
{
return Strider<T>(ptr, stride);
}
template <typename T, Size N>
static constexpr inline Strider<T> MakeStrider(T (&arr)[N])
{
return Strider<T>(arr, K_SIZE(T));
}
enum class AllocFlag {
Zero = 1,
Resizable = 2
};
class Allocator {
K_DELETE_COPY(Allocator)
public:
Allocator() = default;
virtual ~Allocator() = default;
virtual void *Allocate(Size size, unsigned int flags = 0) = 0;
virtual void *Resize(void *ptr, Size old_size, Size new_size, unsigned int flags = 0) = 0;
virtual void Release(const void *ptr, Size size) = 0;
};
Allocator *GetDefaultAllocator();
Allocator *GetNullAllocator();
static inline void *AllocateRaw(Allocator *alloc, Size size, unsigned int flags = 0)
{
K_ASSERT(size >= 0);
if (!alloc) {
alloc = GetDefaultAllocator();
}
void *ptr = alloc->Allocate(size, flags);
return ptr;
}
template <typename T>
T *AllocateOne(Allocator *alloc, unsigned int flags = 0)
{
if (!alloc) {
alloc = GetDefaultAllocator();
}
Size size = K_SIZE(T);
T *ptr = (T *)alloc->Allocate(size, flags);
return ptr;
}
template <typename T>
Span<T> AllocateSpan(Allocator *alloc, Size len, unsigned int flags = 0)
{
K_ASSERT(len >= 0);
if (!alloc) {
alloc = GetDefaultAllocator();
}
Size size = len * K_SIZE(T);
T *ptr = (T *)alloc->Allocate(size, flags);
return MakeSpan(ptr, len);
}
static inline void *ResizeRaw(Allocator *alloc, void *ptr, Size old_size, Size new_size,
unsigned int flags = 0)
{
K_ASSERT(new_size >= 0);
if (!alloc) {
alloc = GetDefaultAllocator();
}
ptr = alloc->Resize(ptr, old_size, new_size, flags);
return ptr;
}
template <typename T>
Span<T> ResizeSpan(Allocator *alloc, Span<T> mem, Size new_len,
unsigned int flags = 0)
{
K_ASSERT(new_len >= 0);
if (!alloc) {
alloc = GetDefaultAllocator();
}
Size old_size = mem.len * K_SIZE(T);
Size new_size = new_len * K_SIZE(T);
mem.ptr = (T *)alloc->Resize(mem.ptr, old_size, new_size, flags);
return MakeSpan(mem.ptr, new_len);
}
static inline void ReleaseRaw(Allocator *alloc, const void *ptr, Size size)
{
if (!alloc) {
alloc = GetDefaultAllocator();
}
alloc->Release(ptr, size);
}
template<typename T>
void ReleaseOne(Allocator *alloc, T *ptr)
{
if (!alloc) {
alloc = GetDefaultAllocator();
}
alloc->Release((void *)ptr, K_SIZE(T));
}
template<typename T>
void ReleaseSpan(Allocator *alloc, Span<T> mem)
{
if (!alloc) {
alloc = GetDefaultAllocator();
}
Size size = mem.len * K_SIZE(T);
alloc->Release((void *)mem.ptr, size);
}
class LinkedAllocator final: public Allocator {
struct Bucket {
Bucket *prev;
Bucket *next;
uint8_t data[];
};
Allocator *allocator;
Bucket *list = nullptr;
public:
LinkedAllocator(Allocator *alloc = nullptr) : allocator(alloc) {}
~LinkedAllocator() override { ReleaseAll(); }
LinkedAllocator(LinkedAllocator &&other) { *this = std::move(other); }
LinkedAllocator& operator=(LinkedAllocator &&other);
void ReleaseAll();
void ReleaseAllExcept(void *ptr);
void *Allocate(Size size, unsigned int flags = 0) override;
void *Resize(void *ptr, Size old_size, Size new_size, unsigned int flags = 0) override;
void Release(const void *ptr, Size size) override;
bool IsUsed() const { return list; }
void GiveTo(LinkedAllocator *alloc);
private:
static Bucket *PointerToBucket(void *ptr);
};
class BlockAllocator: public Allocator {
struct Bucket {
Size used;
uint8_t data[];
};
LinkedAllocator allocator;
Size block_size;
Bucket *current_bucket = nullptr;
uint8_t *last_alloc = nullptr;
public:
BlockAllocator(Size block_size = K_BLOCK_ALLOCATOR_DEFAULT_SIZE)
: block_size(block_size)
{
K_ASSERT(block_size > 0);
}
BlockAllocator(BlockAllocator &&other) { *this = std::move(other); }
BlockAllocator& operator=(BlockAllocator &&other);
void Reset();
void ReleaseAll();
void *Allocate(Size size, unsigned int flags = 0) override;
void *Resize(void *ptr, Size old_size, Size new_size, unsigned int flags = 0) override;
void Release(const void *ptr, Size size) override;
bool IsUsed() const { return allocator.IsUsed(); }
void GiveTo(LinkedAllocator *alloc);
void GiveTo(BlockAllocator *alloc) { GiveTo(&alloc->allocator); }
private:
bool AllocateSeparately(Size aligned_size) const { return aligned_size > block_size / 2; }
};
void *AllocateSafe(Size len);
void ReleaseSafe(void *ptr, Size len);
void ZeroSafe(void *ptr, Size len);
// ------------------------------------------------------------------------
// Reference counting
// ------------------------------------------------------------------------
template <typename T>
class RetainPtr {
T *p = nullptr;
public:
RetainPtr() = default;
RetainPtr(T *p, void (*delete_func)(std::remove_const_t<T> *))
: p(p)
{
K_ASSERT(p);
K_ASSERT(delete_func);
K_ASSERT(!p->delete_func || delete_func == p->delete_func);
p->Ref();
p->delete_func = delete_func;
}
RetainPtr(T *p, bool ref = true)
: p(p)
{
if (p) {
K_ASSERT(p->delete_func);
if (ref) {
p->Ref();
}
}
}
~RetainPtr()
{
if (p && !p->Unref()) {
p->delete_func((std::remove_const_t<T> *)p);
}
}
RetainPtr(const RetainPtr &other)
{
p = other.p;
if (p) {
p->Ref();
}
}
RetainPtr &operator=(const RetainPtr &other)
{
if (p && !p->Unref()) {
p->delete_func((std::remove_const_t<T> *)p);
}
p = other.p;
if (p) {
p->Ref();
}
return *this;
}
operator RetainPtr<const T>() const
{
RetainPtr<const T> ptr((const T *)p);
return ptr;
}
bool IsValid() const { return p; }
operator bool() const { return p; }
T &operator*() const
{
K_ASSERT(p);
return *p;
}
T *operator->() const { return p; }
T *GetRaw() const { return p; }
};
template <typename T>
class RetainObject {
mutable void (*delete_func)(T *) = nullptr;
mutable std::atomic_int refcount { 0 };
public:
void Ref() const { refcount++; }
bool Unref() const
{
int new_count = --refcount;
K_ASSERT(new_count >= 0);
return new_count;
}
friend class RetainPtr<T>;
friend class RetainPtr<const T>;
};
// ------------------------------------------------------------------------
// Strings
// ------------------------------------------------------------------------
bool CopyString(const char *str, Span<char> buf);
bool CopyString(Span<const char> str, Span<char> buf);
Span<char> DuplicateString(Span<const char> str, Allocator *alloc);
static constexpr inline bool IsAsciiAlpha(int c)
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}
static constexpr inline bool IsAsciiDigit(int c)
{
return (c >= '0' && c <= '9');
}
static constexpr inline bool IsAsciiAlphaOrDigit(int c)
{
return IsAsciiAlpha(c) || IsAsciiDigit(c);
}
static constexpr inline bool IsAsciiWhite(int c)
{
return c == ' ' || c == '\t' || c == '\v' ||
c == '\n' || c == '\r' || c == '\f';
}
static constexpr inline bool IsAsciiControl(int c)
{
return c == 0x7F || ((uint8_t)c < ' ' && c != '\t');
}
static constexpr inline char UpperAscii(int c)
{
if (c >= 'a' && c <= 'z') {
return (char)(c - 32);
} else {
return (char)c;
}
}
static constexpr inline char LowerAscii(int c)
{
if (c >= 'A' && c <= 'Z') {
return (char)(c + 32);
} else {
return (char)c;
}
}
static constexpr inline bool TestStr(Span<const char> str1, Span<const char> str2)
{
if (str1.len != str2.len)
return false;
for (Size i = 0; i < str1.len; i++) {
if (str1[i] != str2[i])
return false;
}
return true;
}
static constexpr inline bool TestStr(Span<const char> str1, const char *str2)
{
Size i = 0;
for (; i < str1.len && str2[i]; i++) {
if (str1[i] != str2[i])
return false;
}
return (i == str1.len) && !str2[i];
}
static constexpr inline bool TestStr(const char *str1, Span<const char> str2)
{ return TestStr(str2, str1); }
static constexpr inline bool TestStr(const char *str1, const char *str2)
{
#if defined(__GNUC__) || defined(__clang__)
return !__builtin_strcmp(str1, str2);
#else
Size i = 0;
for (; str1[i]; i++) {
if (str2[i] != str1[i])
return false;
}
return !str2[i];
#endif
}
// Allow direct Span<const char> equality comparison
constexpr inline bool Span<const char>::operator==(Span<const char> other) const
{ return TestStr(*this, other); }
constexpr inline bool Span<const char>::operator==(const char *other) const
{ return TestStr(*this, other); }
// Case insensitive (ASCII) versions
static constexpr inline bool TestStrI(Span<const char> str1, Span<const char> str2)
{
if (str1.len != str2.len)
return false;
for (Size i = 0; i < str1.len; i++) {
if (LowerAscii(str1[i]) != LowerAscii(str2[i]))
return false;
}
return true;
}
static constexpr inline bool TestStrI(Span<const char> str1, const char *str2)
{
Size i = 0;
for (; i < str1.len && str2[i]; i++) {
if (LowerAscii(str1[i]) != LowerAscii(str2[i]))
return false;
}
return (i == str1.len) && !str2[i];
}
static constexpr inline bool TestStrI(const char *str1, Span<const char> str2)
{ return TestStrI(str2, str1); }
static constexpr inline bool TestStrI(const char *str1, const char *str2)
{
Size i = 0;
int delta = 0;
do {
delta = LowerAscii(str1[i]) - LowerAscii(str2[i]);
} while (str1[i++] && !delta);
return !delta;
}
static constexpr inline int CmpStr(Span<const char> str1, Span<const char> str2)
{
for (Size i = 0; i < str1.len && i < str2.len; i++) {
int delta = str1[i] - str2[i];
if (delta)
return delta;
}
if (str1.len < str2.len) {
return -str2[str1.len];
} else if (str1.len > str2.len) {
return str1[str2.len];
} else {
return 0;
}
}
static constexpr inline int CmpStr(Span<const char> str1, const char *str2)
{
Size i = 0;
for (; i < str1.len && str2[i]; i++) {
int delta = str1[i] - str2[i];
if (delta)
return delta;
}
if (str1.len == i) {
return -str2[i];
} else {
return str1[i];
}
}
static constexpr inline int CmpStr(const char *str1, Span<const char> str2)
{ return -CmpStr(str2, str1); }
static constexpr inline int CmpStr(const char *str1, const char *str2)
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_strcmp(str1, str2);
#else
Size i = 0;
for (; str1[i]; i++) {
int delta = str1[i] - str2[i];
if (delta)
return delta;
}
return str2[i] - str1[i];
#endif
}
static inline bool StartsWith(Span<const char> str, Span<const char> prefix)
{
Size i = 0;
while (i < str.len && i < prefix.len) {
if (str[i] != prefix[i])
return false;
i++;
}
return (i == prefix.len);
}
static inline bool StartsWith(Span<const char> str, const char *prefix)
{
Size i = 0;
while (i < str.len && prefix[i]) {
if (str[i] != prefix[i])
return false;
i++;
}
return !prefix[i];
}
static inline bool StartsWith(const char *str, Span<const char> prefix)
{
Size i = 0;
while (str[i] && i < prefix.len) {
if (str[i] != prefix[i])
return false;
i++;
}
return (i == prefix.len);
}
static inline bool StartsWith(const char *str, const char *prefix)
{
Size i = 0;
while (str[i] && prefix[i]) {
if (str[i] != prefix[i])
return false;
i++;
}
return !prefix[i];
}
static inline bool StartsWithI(Span<const char> str, Span<const char> prefix)
{
Size i = 0;
while (i < str.len && i < prefix.len) {
if (LowerAscii(str[i]) != LowerAscii(prefix[i]))
return false;
i++;
}
return (i == prefix.len);
}
static inline bool StartsWithI(Span<const char> str, const char *prefix)
{
Size i = 0;
while (i < str.len && prefix[i]) {
if (LowerAscii(str[i]) != LowerAscii(prefix[i]))
return false;
i++;
}
return !prefix[i];
}
static inline bool StartsWithI(const char *str, Span<const char> prefix)
{
Size i = 0;
while (str[i] && i < prefix.len) {
if (LowerAscii(str[i]) != LowerAscii(prefix[i]))
return false;
i++;
}
return (i == prefix.len);
}
static inline bool StartsWithI(const char *str, const char *prefix)
{
Size i = 0;
while (str[i] && prefix[i]) {
if (LowerAscii(str[i]) != LowerAscii(prefix[i]))
return false;
i++;
}
return !prefix[i];
}
static inline bool EndsWith(Span<const char> str, Span<const char> suffix)
{
Size i = str.len - 1;
Size j = suffix.len - 1;
while (i >= 0 && j >= 0) {
if (str[i] != suffix[j])
return false;
i--;
j--;
}
return j < 0;
}
static inline bool EndsWithI(Span<const char> str, Span<const char> suffix)
{
Size i = str.len - 1;
Size j = suffix.len - 1;
while (i >= 0 && j >= 0) {
if (LowerAscii(str[i]) != LowerAscii(suffix[j]))
return false;
i--;
j--;
}
return j < 0;
}
static inline Size FindStr(Span<const char> str, Span<const char> needle)
{
if (!needle.len)
return 0;
if (needle.len > str.len)
return -1;
Size end = str.len - needle.len;
for (Size i = 0; i <= end; i++) {
if (!memcmp(str.ptr + i, needle.ptr, (size_t)needle.len))
return i;
}
return -1;
}
static inline Size FindStr(const char *str, const char *needle)
{
const char *ret = strstr(str, needle);
return ret ? ret - str : -1;
}
static inline Span<char> SplitStr(Span<char> str, char split_char, Span<char> *out_remainder = nullptr)
{
Size part_len = 0;
while (part_len < str.len) {
if (str[part_len] == split_char) {
if (out_remainder) {
*out_remainder = str.Take(part_len + 1, str.len - part_len - 1);
}
return str.Take(0, part_len);
}
part_len++;
}
if (out_remainder) {
*out_remainder = str.Take(str.len, 0);
}
return str;
}
static inline Span<char> SplitStr(char *str, char split_char, char **out_remainder = nullptr)
{
Size part_len = 0;
while (str[part_len]) {
if (str[part_len] == split_char) {
if (out_remainder) {
*out_remainder = str + part_len + 1;
}
return MakeSpan(str, part_len);
}
part_len++;
}
if (out_remainder) {
*out_remainder = str + part_len;
}
return MakeSpan(str, part_len);
}
static inline Span<const char> SplitStr(Span<const char> str, char split_char, Span<const char> *out_remainder = nullptr)
{ return SplitStr(MakeSpan((char *)str.ptr, str.len), split_char, (Span<char> *)out_remainder); }
static inline Span<const char> SplitStr(const char *str, char split_char, const char **out_remainder = nullptr)
{ return SplitStr((char *)str, split_char, (char **)out_remainder); }
static inline Span<char> SplitStr(Span<char> str, Span<const char> split, Span<char> *out_remainder = nullptr)
{
K_ASSERT(split.len);
Size part_len = 0;
while (part_len < str.len) {
if (StartsWith(str.Take(part_len, str.len - part_len), split)) {
if (out_remainder) {
*out_remainder = str.Take(part_len + split.len, str.len - part_len - split.len);
}
return str.Take(0, part_len);
}
part_len++;
}
if (out_remainder) {
*out_remainder = str.Take(str.len, 0);
}
return str;
}
static inline Span<char> SplitStr(char *str, Span<const char> split, char **out_remainder = nullptr)
{
K_ASSERT(split.len);
Size part_len = 0;
while (str[part_len]) {
if (StartsWith(str + part_len, split)) {
if (out_remainder) {
*out_remainder = str + part_len + split.len;
}
return MakeSpan(str, part_len);
}
part_len++;
}
if (out_remainder) {
*out_remainder = str + part_len;
}
return MakeSpan(str, part_len);
}
static inline Span<const char> SplitStr(Span<const char> str, Span<const char> split, Span<const char> *out_remainder = nullptr)
{ return SplitStr(MakeSpan((char *)str.ptr, str.len), split, (Span<char> *)out_remainder); }
static inline Span<const char> SplitStr(const char *str, Span<const char> split, const char **out_remainder = nullptr)
{ return SplitStr((char *)str, split, (char **)out_remainder); }
static inline Span<char> SplitStrLine(Span<char> str, Span<char> *out_remainder = nullptr)
{
Span<char> part = SplitStr(str, '\n', out_remainder);
if (part.len < str.len && part.len && part[part.len - 1] == '\r') {
part.len--;
}
return part;
}
static inline Span<char> SplitStrLine(char *str, char **out_remainder = nullptr)
{
Span<char> part = SplitStr(str, '\n', out_remainder);
if (str[part.len] && part.len && part[part.len - 1] == '\r') {
part.len--;
}
return part;
}
static inline Span<const char> SplitStrLine(Span<const char> str, Span<const char> *out_remainder = nullptr)
{ return SplitStrLine(MakeSpan((char *)str.ptr, str.len), (Span<char> *)out_remainder); }
static inline Span<const char> SplitStrLine(const char *str, const char **out_remainder = nullptr)
{ return SplitStrLine((char *)str, (char **)out_remainder); }
static inline Span<char> SplitStrAny(Span<char> str, const char *split_chars, Span<char> *out_remainder = nullptr)
{
Bitset<256> split_mask;
for (Size i = 0; split_chars[i]; i++) {
uint8_t c = (uint8_t)split_chars[i];
split_mask.Set(c);
}
Size part_len = 0;
while (part_len < str.len) {
uint8_t c = (uint8_t)str[part_len];
if (split_mask.Test(c)) {
if (out_remainder) {
*out_remainder = str.Take(part_len + 1, str.len - part_len - 1);
}
return str.Take(0, part_len);
}
part_len++;
}
if (out_remainder) {
*out_remainder = str.Take(str.len, 0);
}
return str;
}
static inline Span<char> SplitStrAny(char *str, const char *split_chars, char **out_remainder = nullptr)
{
Bitset<256> split_mask;
for (Size i = 0; split_chars[i]; i++) {
uint8_t c = (uint8_t)split_chars[i];
split_mask.Set(c);
}
Size part_len = 0;
while (str[part_len]) {
uint8_t c = (uint8_t)str[part_len];
if (split_mask.Test(c)) {
if (out_remainder) {
*out_remainder = str + part_len + 1;
}
return MakeSpan(str, part_len);
}
part_len++;
}
if (out_remainder) {
*out_remainder = str + part_len;
}
return MakeSpan(str, part_len);
}
static inline Span<const char> SplitStrAny(Span<const char> str, const char *split_chars, Span<const char> *out_remainder = nullptr)
{ return SplitStrAny(MakeSpan((char *)str.ptr, str.len), split_chars, (Span<char> *)out_remainder); }
static inline Span<const char> SplitStrAny(const char *str, const char *split_chars, const char **out_remainder = nullptr)
{ return SplitStrAny((char *)str, split_chars, (char **)out_remainder); }
static inline Span<const char> SplitStrReverse(Span<const char> str, char split_char,
Span<const char> *out_remainder = nullptr)
{
Size remainder_len = str.len - 1;
while (remainder_len >= 0) {
if (str[remainder_len] == split_char) {
if (out_remainder) {
*out_remainder = str.Take(0, remainder_len);
}
return str.Take(remainder_len + 1, str.len - remainder_len - 1);
}
remainder_len--;
}
if (out_remainder) {
*out_remainder = str.Take(0, 0);
}
return str;
}
static inline Span<const char> SplitStrReverse(const char *str, char split_char,
Span<const char> *out_remainder = nullptr)
{ return SplitStrReverse(MakeSpan(str, strlen(str)), split_char, out_remainder); }
static inline Span<const char> SplitStrReverseAny(Span<const char> str, const char *split_chars,
Span<const char> *out_remainder = nullptr)
{
Bitset<256> split_mask;
for (Size i = 0; split_chars[i]; i++) {
uint8_t c = (uint8_t)split_chars[i];
split_mask.Set(c);
}
Size remainder_len = str.len - 1;
while (remainder_len >= 0) {
uint8_t c = (uint8_t)str[remainder_len];
if (split_mask.Test(c)) {
if (out_remainder) {
*out_remainder = str.Take(0, remainder_len);
}
return str.Take(remainder_len + 1, str.len - remainder_len - 1);
}
remainder_len--;
}
if (out_remainder) {
*out_remainder = str.Take(0, 0);
}
return str;
}
static inline Span<const char> SplitStrReverseAny(const char *str, const char *split_chars,
Span<const char> *out_remainder = nullptr)
{ return SplitStrReverseAny(MakeSpan(str, strlen(str)), split_chars, out_remainder); }
static inline Span<char> TrimStrLeft(Span<char> str, char trim_char)
{
while (str.len && str[0] == trim_char && str[0]) {
str.ptr++;
str.len--;
}
return str;
}
static inline Span<char> TrimStrRight(Span<char> str, char trim_char)
{
while (str.len && str[str.len - 1] == trim_char && str[str.len - 1]) {
str.len--;
}
return str;
}
static inline Span<char> TrimStr(Span<char> str, char trim_char)
{
str = TrimStrRight(str, trim_char);
str = TrimStrLeft(str, trim_char);
return str;
}
static inline Span<const char> TrimStrLeft(Span<const char> str, char trim_char)
{ return TrimStrLeft(MakeSpan((char *)str.ptr, str.len), trim_char); }
static inline Span<const char> TrimStrRight(Span<const char> str, char trim_char)
{ return TrimStrRight(MakeSpan((char *)str.ptr, str.len), trim_char); }
static inline Span<const char> TrimStr(Span<const char> str,char trim_char)
{ return TrimStr(MakeSpan((char *)str.ptr, str.len), trim_char); }
static inline Span<char> TrimStrLeft(Span<char> str, const char *trim_chars = " \t\r\n")
{
while (str.len && strchr(trim_chars, str[0]) && str[0]) {
str.ptr++;
str.len--;
}
return str;
}
static inline Span<char> TrimStrRight(Span<char> str, const char *trim_chars = " \t\r\n")
{
while (str.len && strchr(trim_chars, str[str.len - 1]) && str[str.len - 1]) {
str.len--;
}
return str;
}
static inline Span<char> TrimStr(Span<char> str, const char *trim_chars = " \t\r\n")
{
str = TrimStrRight(str, trim_chars);
str = TrimStrLeft(str, trim_chars);
return str;
}
static inline Span<const char> TrimStrLeft(Span<const char> str, const char *trim_chars = " \t\r\n")
{ return TrimStrLeft(MakeSpan((char *)str.ptr, str.len), trim_chars); }
static inline Span<const char> TrimStrRight(Span<const char> str, const char *trim_chars = " \t\r\n")
{ return TrimStrRight(MakeSpan((char *)str.ptr, str.len), trim_chars); }
static inline Span<const char> TrimStr(Span<const char> str, const char *trim_chars = " \t\r\n")
{ return TrimStr(MakeSpan((char *)str.ptr, str.len), trim_chars); }
int CmpNatural(Span<const char> str1, Span<const char> str2);
int CmpNaturalI(Span<const char> str1, Span<const char> str2);
// ------------------------------------------------------------------------
// Collections
// ------------------------------------------------------------------------
template <typename T, Size N, Size AlignAs = alignof(T)>
class LocalArray {
public:
alignas(AlignAs) T data[N];
Size len = 0;
typedef T value_type;
typedef T *iterator_type;
constexpr LocalArray() = default;
constexpr LocalArray(std::initializer_list<T> l)
{
K_ASSERT(l.size() <= N);
for (const T &it: l) {
data[len++] = it;
}
len = (Size)l.size();
}
void Clear()
{
for (Size i = 0; i < len; i++) {
data[i] = T();
}
len = 0;
}
operator Span<T>() { return Span<T>(data, len); }
operator Span<const T>() const { return Span<const T>(data, len); }
T *begin() { return data; }
const T *begin() const { return data; }
T *end() { return data + len; }
const T *end() const { return data + len; }
Size Available() const { return K_LEN(data) - len; }
T &operator[](Size idx)
{
K_ASSERT(idx >= 0 && idx < len);
return data[idx];
}
const T &operator[](Size idx) const
{
K_ASSERT(idx >= 0 && idx < len);
return data[idx];
}
bool operator==(const LocalArray &other) const
{
if (len != other.len)
return false;
for (Size i = 0; i < len; i++) {
if (data[i] != other.data[i])
return false;
}
return true;
}
bool operator!=(const LocalArray &other) const { return !(*this == other); }
T *AppendDefault(Size count = 1)
{
K_ASSERT(len <= N - count);
T *first = data + len;
if constexpr(!std::is_trivial<T>::value) {
for (Size i = 0; i < count; i++) {
new (data + len) T();
len++;
}
} else {
MemSet(first, 0, count * K_SIZE(T));
len += count;
}
return first;
}
T *Append(const T &value)
{
K_ASSERT(len < N);
T *it = data + len;
*it = value;
len++;
return it;
}
T *Append(T &&value)
{
K_ASSERT(len < N);
T *it = data + len;
*it = std::move(value);
len++;
return it;
}
T *Append(Span<const T> values)
{
K_ASSERT(values.len <= N - len);
T *it = data + len;
for (Size i = 0; i < values.len; i++) {
data[len + i] = values[i];
}
len += values.len;
return it;
}
void RemoveFrom(Size first)
{
K_ASSERT(first >= 0 && first <= len);
for (Size i = first; i < len; i++) {
data[i] = T();
}
len = first;
}
void RemoveLast(Size count = 1)
{
K_ASSERT(count >= 0 && count <= len);
RemoveFrom(len - count);
}
Span<T> Take() const { return Span<T>((T *)data, len); }
Span<T> Take(Size offset, Size len) const { return Span<T>((T *)data, N).Take(offset, len); }
Span<T> TakeAvailable() const { return Span<T>((T *)data + len, N - len); }
template <typename U = T>
Span<U> As() const { return Span<U>((U *)data, len); }
};
template <typename T>
class HeapArray {
// StaticAssert(std::is_trivially_copyable<T>::value);
public:
T *ptr = nullptr;
Size len = 0;
Size capacity = 0;
Allocator *allocator = nullptr;
typedef T value_type;
typedef T *iterator_type;
HeapArray() = default;
HeapArray(Allocator *alloc, Size min_capacity = 0) : allocator(alloc)
{ SetCapacity(min_capacity); }
HeapArray(Size min_capacity) { Reserve(min_capacity); }
HeapArray(std::initializer_list<T> l)
{
Reserve(l.size());
for (const T &it: l) {
ptr[len++] = it;
}
}
~HeapArray() { Clear(); }
HeapArray(HeapArray &&other) { *this = std::move(other); }
HeapArray &operator=(HeapArray &&other)
{
Clear();
MemMove(this, &other, K_SIZE(other));
MemSet(&other, 0, K_SIZE(other));
return *this;
}
HeapArray(const HeapArray &other) { *this = other; }
HeapArray &operator=(const HeapArray &other)
{
RemoveFrom(0);
Grow(other.capacity);
if constexpr(!std::is_trivial<T>::value) {
for (Size i = 0; i < other.len; i++) {
ptr[i] = other.ptr[i];
}
} else {
MemCpy(ptr, other.ptr, other.len * K_SIZE(*ptr));
}
len = other.len;
return *this;
}
void Clear()
{
RemoveFrom(0);
SetCapacity(0);
}
operator Span<T>() { return Span<T>(ptr, len); }
operator Span<const T>() const { return Span<const T>(ptr, len); }
T *begin() { return ptr; }
const T *begin() const { return ptr; }
T *end() { return ptr + len; }
const T *end() const { return ptr + len; }
Size Available() const { return capacity - len; }
T &operator[](Size idx)
{
K_ASSERT(idx >= 0 && idx < len);
return ptr[idx];
}
const T &operator[](Size idx) const
{
K_ASSERT(idx >= 0 && idx < len);
return ptr[idx];
}
bool operator==(const HeapArray &other) const
{
if (len != other.len)
return false;
for (Size i = 0; i < len; i++) {
if (ptr[i] != other.ptr[i])
return false;
}
return true;
}
bool operator!=(const HeapArray &other) const { return !(*this == other); }
void SetCapacity(Size new_capacity)
{
K_ASSERT(new_capacity >= 0);
if (new_capacity != capacity) {
if (len > new_capacity) {
for (Size i = new_capacity; i < len; i++) {
ptr[i].~T();
}
len = new_capacity;
}
ptr = (T *)ResizeRaw(allocator, ptr, capacity * K_SIZE(T), new_capacity * K_SIZE(T));
capacity = new_capacity;
}
}
void Reserve(Size min_capacity)
{
if (min_capacity > capacity) {
SetCapacity(min_capacity);
}
}
T *Grow(Size reserve_capacity = 1)
{
K_ASSERT(capacity >= 0);
K_ASSERT(reserve_capacity >= 0);
K_ASSERT((size_t)capacity + (size_t)reserve_capacity <= K_SIZE_MAX);
if (reserve_capacity > capacity - len) {
Size needed = capacity + reserve_capacity;
Size new_capacity;
if (needed <= K_HEAPARRAY_BASE_CAPACITY) {
new_capacity = K_HEAPARRAY_BASE_CAPACITY;
} else {
new_capacity = (Size)((double)(needed - 1) * K_HEAPARRAY_GROWTH_FACTOR);
}
SetCapacity(new_capacity);
}
return ptr + len;
}
void Trim(Size extra_capacity = 0) { SetCapacity(len + extra_capacity); }
T *AppendDefault(Size count = 1)
{
Grow(count);
T *first = ptr + len;
if constexpr(!std::is_trivial<T>::value) {
for (Size i = 0; i < count; i++) {
new (ptr + len) T();
len++;
}
} else {
MemSet(first, 0, count * K_SIZE(T));
len += count;
}
return first;
}
T *Append(const T &value)
{
Grow();
T *first = ptr + len;
if constexpr(!std::is_trivial<T>::value) {
new (ptr + len) T;
}
ptr[len++] = value;
return first;
}
T *Append(T &&value)
{
Grow();
T *first = ptr + len;
if constexpr(!std::is_trivial<T>::value) {
new (ptr + len) T;
}
ptr[len++] = std::move(value);
return first;
}
T *Append(Span<const T> values)
{
Grow(values.len);
T *first = ptr + len;
for (const T &value: values) {
if constexpr(!std::is_trivial<T>::value) {
new (ptr + len) T;
}
ptr[len++] = value;
}
return first;
}
void RemoveFrom(Size first)
{
K_ASSERT(first >= 0 && first <= len);
if constexpr(!std::is_trivial<T>::value) {
for (Size i = first; i < len; i++) {
ptr[i].~T();
}
}
len = first;
}
void RemoveLast(Size count = 1)
{
K_ASSERT(count >= 0 && count <= len);
RemoveFrom(len - count);
}
Span<T> Take() const { return Span<T>(ptr, len); }
Span<T> Take(Size offset, Size len) const { return Span<T>(ptr, this->len).Take(offset, len); }
Span<T> TakeAvailable() const { return Span<T>((T *)ptr + len, capacity - len); }
Span<T> Leak()
{
Span<T> span = *this;
ptr = nullptr;
len = 0;
capacity = 0;
return span;
}
Span<T> TrimAndLeak(Size extra_capacity = 0)
{
Trim(extra_capacity);
return Leak();
}
template <typename U = T>
Span<U> As() const { return Span<U>((U *)ptr, len); }
};
template <typename T, Size BucketSize = 64, typename AllocatorType = BlockAllocator>
class BucketArray {
K_DELETE_COPY(BucketArray)
public:
struct Bucket {
T *values;
AllocatorType allocator;
};
template <typename U>
class Iterator {
public:
typedef std::bidirectional_iterator_tag iterator_category;
typedef Size value_type;
typedef Size difference_type;
typedef Iterator *pointer;
typedef Iterator &reference;
U *queue = nullptr;
Size bucket_idx;
Size bucket_offset;
Bucket *bucket;
Bucket *next_bucket;
Iterator() = default;
Iterator(U *queue, Size bucket_idx, Size bucket_offset)
: queue(queue), bucket_idx(bucket_idx), bucket_offset(bucket_offset),
bucket(GetBucketSafe(bucket_idx)), next_bucket(GetBucketSafe(bucket_idx + 1)) {}
T *operator->() { return &bucket->values[bucket_offset]; }
const T *operator->() const { return &bucket->values[bucket_offset]; }
T &operator*() { return bucket->values[bucket_offset]; }
const T &operator*() const { return bucket->values[bucket_offset]; }
Iterator &operator++()
{
if (++bucket_offset >= BucketSize) {
bucket_idx++;
bucket_offset = 0;
// Allow iterator to go before start temporarily
if (bucket_idx < 0) [[unlikely]]
return *this;
if (next_bucket) {
// We support deletion of all values up to (and including) the current one.
// When the user does that, some or all front buckets may be gone, but we can
// use next_bucket to fix bucket_idx.
while (bucket_idx >= queue->buckets.len ||
queue->buckets[bucket_idx] != next_bucket) {
bucket_idx--;
}
}
bucket = GetBucketSafe(bucket_idx);
next_bucket = GetBucketSafe(bucket_idx + 1);
}
return *this;
}
Iterator operator++(int)
{
Iterator ret = *this;
++(*this);
return ret;
}
Iterator &operator--()
{
if (--bucket_offset < 0) {
bucket_idx--;
bucket_offset = BucketSize - 1;
// Allow iterator to go before start temporarily
if (bucket_idx >= 0) [[unlikely]] {
bucket = (bucket_idx >= 0) ? GetBucketSafe(bucket_idx) : nullptr;
next_bucket = (bucket_idx >= 0) ? GetBucketSafe(bucket_idx + 1) : nullptr;
}
}
return *this;
}
Iterator operator--(int)
{
Iterator ret = *this;
--(*this);
return ret;
}
bool operator==(const Iterator &other) const
{ return queue == other.queue && bucket == other.bucket &&
bucket_offset == other.bucket_offset; }
bool operator!=(const Iterator &other) const { return !(*this == other); }
private:
Bucket *GetBucketSafe(Size idx)
{ return idx < queue->buckets.len ? queue->buckets[idx] : nullptr; }
};
HeapArray<Bucket *> buckets;
Size offset = 0;
Size count = 0;
typedef T value_type;
typedef Iterator<BucketArray> iterator_type;
BucketArray() {}
BucketArray(std::initializer_list<T> l)
{
for (const T &value: l) {
Append(value);
}
}
~BucketArray() { ClearBucketsAndValues(); }
BucketArray(BucketArray &&other) { *this = std::move(other); }
BucketArray &operator=(BucketArray &&other)
{
ClearBucketsAndValues();
MemMove(this, &other, K_SIZE(other));
MemSet(&other, 0, K_SIZE(other));
return *this;
}
void Clear()
{
ClearBucketsAndValues();
offset = 0;
count = 0;
}
iterator_type begin() { return iterator_type(this, 0, offset); }
Iterator<const BucketArray<T, BucketSize>> begin() const { return Iterator<const BucketArray>(this, 0, offset); }
iterator_type end()
{
Size end_idx = offset + count;
Size bucket_idx = end_idx / BucketSize;
Size bucket_offset = end_idx % BucketSize;
return iterator_type(this, bucket_idx, bucket_offset);
}
Iterator<const BucketArray<T, BucketSize>> end() const
{
Size end_idx = offset + count;
Size bucket_idx = end_idx / BucketSize;
Size bucket_offset = end_idx % BucketSize;
return Iterator<const BucketArray>(this, bucket_idx, bucket_offset);
}
const T &operator[](Size idx) const
{
K_ASSERT(idx >= 0 && idx < count);
idx += offset;
Size bucket_idx = idx / BucketSize;
Size bucket_offset = idx % BucketSize;
return buckets[bucket_idx]->values[bucket_offset];
}
T &operator[](Size idx) { return (T &)(*(const BucketArray *)this)[idx]; }
T *AppendDefault(Allocator **out_alloc = nullptr)
{
Size bucket_idx = (offset + count) / BucketSize;
Size bucket_offset = (offset + count) % BucketSize;
if (bucket_idx >= buckets.len) {
Bucket *new_bucket = AllocateOne<Bucket>(buckets.allocator);
new (&new_bucket->allocator) AllocatorType();
new_bucket->values = (T *)AllocateRaw(&new_bucket->allocator, BucketSize * K_SIZE(T));
buckets.Append(new_bucket);
}
T *first = buckets[bucket_idx]->values + bucket_offset;
new (first) T();
count++;
if (out_alloc) {
*out_alloc = &buckets[bucket_idx]->allocator;
}
return first;
}
T *Append(const T &value, Allocator **out_alloc = nullptr)
{
T *it = AppendDefault(out_alloc);
*it = value;
return it;
}
void RemoveFrom(Size from)
{
K_ASSERT(from >= 0 && from <= count);
if (from == count)
return;
if (!from) {
Clear();
return;
}
Size start_idx = offset + from;
Size start_bucket_idx = start_idx / BucketSize;
Size start_bucket_offset = start_idx % BucketSize;
iterator_type from_it(this, start_bucket_idx, start_bucket_offset);
DeleteValues(from_it, end());
Size delete_idx = start_bucket_idx + !!start_bucket_offset;
for (Size i = delete_idx; i < buckets.len; i++) {
DeleteBucket(buckets[i]);
}
buckets.RemoveFrom(delete_idx);
count = from;
}
void RemoveLast(Size n = 1)
{
K_ASSERT(n >= 0 && n <= count);
RemoveFrom(count - n);
}
void RemoveFirst(Size n = 1)
{
K_ASSERT(n >= 0 && n <= count);
if (n == count) {
Clear();
return;
}
Size end_idx = offset + n;
Size end_bucket_idx = end_idx / BucketSize;
Size end_bucket_offset = end_idx % BucketSize;
iterator_type until_it(this, end_bucket_idx, end_bucket_offset);
DeleteValues(begin(), until_it);
if (end_bucket_idx) {
for (Size i = 0; i < end_bucket_idx; i++) {
DeleteBucket(buckets[i]);
}
MemMove(&buckets[0], &buckets[end_bucket_idx],
(buckets.len - end_bucket_idx) * K_SIZE(Bucket *));
buckets.RemoveLast(end_bucket_idx);
}
offset = (offset + n) % BucketSize;
count -= n;
}
void RemoveFrom(const iterator_type &it)
{
if (it == end())
return;
if (it == begin()) {
Clear();
return;
}
DeleteValues(it, end());
Size delete_idx = it.bucket_idx + !!it.bucket_offset;
for (Size i = delete_idx; i < buckets.len; i++) {
DeleteBucket(buckets[i]);
}
buckets.RemoveFrom(delete_idx);
count = (it.bucket_idx * BucketSize) + it.bucket_offset - offset;
K_ASSERT(it == end());
}
void RemoveFrom(const Iterator<const BucketArray<T, BucketSize>> &it) { return RemoveFrom((iterator_type)it); }
void RemoveUntil(const iterator_type &it)
{
if (it == begin())
return;
if (it == end()) {
Clear();
return;
}
DeleteValues(begin(), it);
if (it.bucket_idx) {
for (Size i = 0; i < it.bucket_idx; i++) {
DeleteBucket(buckets[i]);
}
MemMove(&buckets[0], &buckets[it.bucket_idx],
(buckets.len - it.bucket_idx) * K_SIZE(Bucket *));
buckets.RemoveLast(it.bucket_idx);
}
Size count = it.bucket_idx * BucketSize + it.bucket_offset - offset;
offset = (offset + count) % BucketSize;
count -= count;
}
void RemoveUntil(const Iterator<const BucketArray<T, BucketSize>> &it) { return RemoveUntil((iterator_type)it); }
void Trim()
{
buckets.Trim();
}
private:
void ClearBucketsAndValues()
{
DeleteValues(begin(), end());
for (Bucket *bucket: buckets) {
DeleteBucket(bucket);
}
buckets.Clear();
}
void DeleteValues([[maybe_unused]] iterator_type begin,
[[maybe_unused]] iterator_type end)
{
if constexpr(!std::is_trivial<T>::value) {
for (iterator_type it = begin; it != end; ++it) {
it->~T();
}
}
}
void DeleteBucket(Bucket *bucket)
{
bucket->allocator.~AllocatorType();
ReleaseOne(buckets.allocator, bucket);
}
};
template <typename KeyType, typename ValueType,
typename Handler = typename std::remove_pointer<ValueType>::type::HashHandler>
class HashTable {
public:
template <typename T>
class Iterator {
public:
typedef std::forward_iterator_tag iterator_category;
typedef ValueType value_type;
typedef Size difference_type;
typedef Iterator *pointer;
typedef Iterator &reference;
T *table = nullptr;
Size offset;
Iterator() = default;
Iterator(T *table, Size offset)
: table(table), offset(offset - 1) { operator++(); }
ValueType &operator*()
{
K_ASSERT(!table->IsEmpty(offset));
return table->data[offset];
}
const ValueType &operator*() const
{
K_ASSERT(!table->IsEmpty(offset));
return table->data[offset];
}
Iterator &operator++()
{
K_ASSERT(offset < table->capacity);
while (++offset < table->capacity && table->IsEmpty(offset));
return *this;
}
Iterator operator++(int)
{
Iterator ret = *this;
++(*this);
return ret;
}
// Beware, in some cases a previous value may be seen again after this action
void Remove()
{
table->Remove(&table->data[offset]);
offset--;
}
bool operator==(const Iterator &other) const
{ return table == other.table && offset == other.offset; }
bool operator!=(const Iterator &other) const { return !(*this == other); }
};
typedef Size value_type;
typedef Iterator<HashTable> iterator_type;
size_t *used = nullptr;
ValueType *data = nullptr;
Size count = 0;
Size capacity = 0;
Allocator *allocator = nullptr;
HashTable() = default;
HashTable(std::initializer_list<ValueType> l)
{
for (const ValueType &value: l) {
Set(value);
}
}
~HashTable() { Clear(); }
HashTable(HashTable &&other) { *this = std::move(other); }
HashTable &operator=(HashTable &&other)
{
Clear();
MemMove(this, &other, K_SIZE(other));
MemSet(&other, 0, K_SIZE(other));
return *this;
}
HashTable(const HashTable &other) { *this = other; }
HashTable &operator=(const HashTable &other)
{
Clear();
for (const ValueType &value: other) {
Set(value);
}
return *this;
}
void Clear()
{
if constexpr(!std::is_trivial<ValueType>::value) {
for (Size i = 0; i < capacity; i++) {
data[i].~ValueType();
}
}
count = 0;
Rehash(0);
}
void RemoveAll()
{
if constexpr(!std::is_trivial<ValueType>::value) {
for (Size i = 0; i < capacity; i++) {
data[i].~ValueType();
}
}
count = 0;
if (used) {
size_t len = (size_t)(capacity + (K_SIZE(size_t) * 8) - 1) / K_SIZE(size_t);
MemSet(used, 0, len);
}
}
Iterator<HashTable> begin() { return Iterator<HashTable>(this, 0); }
Iterator<const HashTable> begin() const { return Iterator<const HashTable>(this, 0); }
Iterator<HashTable> end() { return Iterator<HashTable>(this, capacity); }
Iterator<const HashTable> end() const { return Iterator<const HashTable>(this, capacity); }
template <typename T = KeyType>
ValueType *Find(const T &key)
{ return (ValueType *)((const HashTable *)this)->Find(key); }
template <typename T = KeyType>
const ValueType *Find(const T &key) const
{
if (!capacity)
return nullptr;
uint64_t hash = Handler::HashKey(key);
Size idx = HashToIndex(hash);
return Find(&idx, key);
}
template <typename T = KeyType>
ValueType FindValue(const T &key, const ValueType &default_value)
{ return (ValueType)((const HashTable *)this)->FindValue(key, default_value); }
template <typename T = KeyType>
const ValueType FindValue(const T &key, const ValueType &default_value) const
{
const ValueType *it = Find(key);
return it ? *it : default_value;
}
ValueType *Set(const ValueType &value)
{
const KeyType &key = Handler::GetKey(value);
bool inserted;
ValueType *ptr = Insert(key, &inserted);
*ptr = value;
return ptr;
}
ValueType *SetDefault(const KeyType &key)
{
bool inserted;
ValueType *ptr = Insert(key, &inserted);
if (!inserted) {
ptr->~ValueType();
}
new (ptr) ValueType();
return ptr;
}
ValueType *InsertOrGet(const ValueType &value, bool *out_inserted = nullptr)
{
const KeyType &key = Handler::GetKey(value);
bool inserted;
ValueType *ptr = Insert(key, &inserted);
if (inserted) {
*ptr = value;
}
if (out_inserted) {
*out_inserted = inserted;
}
return ptr;
}
ValueType *InsertOrGetDefault(const KeyType &key, bool *out_inserted = nullptr)
{
bool inserted;
ValueType *ptr = Insert(key, &inserted);
if (inserted) {
new (ptr) ValueType();
}
if (out_inserted) {
*out_inserted = inserted;
}
return ptr;
}
void Remove(ValueType *it)
{
if (!it)
return;
Size clear_idx = it - data;
K_ASSERT(!IsEmpty(clear_idx));
it->~ValueType();
count--;
MarkEmpty(clear_idx);
// Move following slots if needed
for (Size idx = NextIndex(clear_idx); !IsEmpty(idx); idx = NextIndex(idx)) {
Size real_idx = KeyToIndex(Handler::GetKey(data[idx]));
if (clear_idx <= idx) {
if (clear_idx < real_idx && real_idx <= idx)
continue;
} else {
if (real_idx <= idx || clear_idx < real_idx)
continue;
}
MarkUsed(clear_idx);
MarkEmpty(idx);
MemMove(&data[clear_idx], &data[idx], K_SIZE(*data));
clear_idx = idx;
}
new (&data[clear_idx]) ValueType();
}
template <typename T = KeyType>
void Remove(const T &key) { Remove(Find(key)); }
void Trim()
{
if (count) {
Size new_capacity = (Size)1 << (64 - CountLeadingZeros((uint64_t)count));
if (new_capacity < K_HASHTABLE_BASE_CAPACITY) {
new_capacity = K_HASHTABLE_BASE_CAPACITY;
} else if (count > (double)new_capacity * K_HASHTABLE_MAX_LOAD_FACTOR) {
new_capacity *= 2;
}
Rehash(new_capacity);
} else {
Rehash(0);
}
}
private:
template <typename T = KeyType>
ValueType *Find(Size *idx, const T &key)
{ return (ValueType *)((const HashTable *)this)->Find(idx, key); }
template <typename T = KeyType>
const ValueType *Find(Size *idx, const T &key) const
{
if constexpr(std::is_pointer<ValueType>::value) {
while (data[*idx]) {
const KeyType &it_key = Handler::GetKey(data[*idx]);
if (Handler::TestKeys(it_key, key))
return &data[*idx];
*idx = NextIndex(*idx);
}
return nullptr;
} else {
while (!IsEmpty(*idx)) {
const KeyType &it_key = Handler::GetKey(data[*idx]);
if (Handler::TestKeys(it_key, key))
return &data[*idx];
*idx = NextIndex(*idx);
}
return nullptr;
}
}
ValueType *Insert(const KeyType &key, bool *out_inserted)
{
uint64_t hash = Handler::HashKey(key);
if (capacity) {
Size idx = HashToIndex(hash);
ValueType *it = Find(&idx, key);
if (!it) {
if (count >= (Size)((double)capacity * K_HASHTABLE_MAX_LOAD_FACTOR)) {
Rehash(capacity << 1);
idx = HashToIndex(hash);
while (!IsEmpty(idx)) {
idx = NextIndex(idx);
}
}
count++;
MarkUsed(idx);
*out_inserted = true;
return &data[idx];
} else {
*out_inserted = false;
return it;
}
} else {
Rehash(K_HASHTABLE_BASE_CAPACITY);
Size idx = HashToIndex(hash);
count++;
MarkUsed(idx);
*out_inserted = true;
return &data[idx];
}
}
void Rehash(Size new_capacity)
{
if (new_capacity == capacity)
return;
K_ASSERT(count <= new_capacity);
size_t *old_used = used;
ValueType *old_data = data;
Size old_capacity = capacity;
if (new_capacity) {
used = (size_t *)AllocateRaw(allocator,
(new_capacity + (K_SIZE(size_t) * 8) - 1) / K_SIZE(size_t),
(int)AllocFlag::Zero);
data = (ValueType *)AllocateRaw(allocator, new_capacity * K_SIZE(ValueType));
for (Size i = 0; i < new_capacity; i++) {
new (&data[i]) ValueType();
}
capacity = new_capacity;
for (Size i = 0; i < old_capacity; i++) {
if (!IsEmpty(old_used, i)) {
Size new_idx = KeyToIndex(Handler::GetKey(old_data[i]));
while (!IsEmpty(new_idx)) {
new_idx = NextIndex(new_idx);
}
MarkUsed(new_idx);
data[new_idx] = old_data[i];
}
}
} else {
used = nullptr;
data = nullptr;
capacity = 0;
}
ReleaseRaw(allocator, old_used, (old_capacity + (K_SIZE(size_t) * 8) - 1) / K_SIZE(size_t));
ReleaseRaw(allocator, old_data, old_capacity * K_SIZE(ValueType));
}
inline void MarkUsed(Size idx)
{
used[idx / (K_SIZE(size_t) * 8)] |= (1ull << (idx % (K_SIZE(size_t) * 8)));
}
inline void MarkEmpty(Size idx)
{
used[idx / (K_SIZE(size_t) * 8)] &= ~(1ull << (idx % (K_SIZE(size_t) * 8)));
}
inline bool IsEmpty(size_t *used, Size idx) const
{
bool empty = !(used[idx / (K_SIZE(size_t) * 8)] & (1ull << (idx % (K_SIZE(size_t) * 8))));
return empty;
}
inline bool IsEmpty(Size idx) const { return IsEmpty(used, idx); }
inline Size HashToIndex(uint64_t hash) const
{
return (Size)(hash & (uint64_t)(capacity - 1));
}
inline Size KeyToIndex(const KeyType &key) const
{
uint64_t hash = Handler::HashKey(key);
return HashToIndex(hash);
}
inline Size NextIndex(Size idx) const
{
return (idx + 1) & (capacity - 1);
}
};
template <typename T>
class HashTraits {
public:
static constexpr uint64_t Hash(const T &key) { return key.Hash(); }
static constexpr bool Test(const T &key1, const T &key2) { return key1 == key2; }
};
// Stole the Hash function from Thomas Wang (see here: https://gist.github.com/badboy/6267743)
#define DEFINE_INTEGER_HASH_TRAITS_32(Type, ...) \
template <> \
class HashTraits<Type> { \
public: \
static __VA_ARGS__ uint64_t Hash(Type key) \
{ \
uint32_t hash = (uint32_t)key; \
\
hash = (hash ^ 61) ^ (hash >> 16); \
hash += hash << 3; \
hash ^= hash >> 4; \
hash *= 0x27D4EB2D; \
hash ^= hash >> 15; \
\
return (uint64_t)hash; \
} \
\
static __VA_ARGS__ bool Test(Type key1, Type key2) { return key1 == key2; } \
}
#define DEFINE_INTEGER_HASH_TRAITS_64(Type, ...) \
template <> \
class HashTraits<Type> { \
public: \
static __VA_ARGS__ uint64_t Hash(Type key) \
{ \
uint64_t hash = (uint64_t)key; \
\
hash = (~hash) + (hash << 18); \
hash ^= hash >> 31; \
hash *= 21; \
hash ^= hash >> 11; \
hash += hash << 6; \
hash ^= hash >> 22; \
\
return hash; \
} \
\
static __VA_ARGS__ bool Test(Type key1, Type key2) { return key1 == key2; } \
}
DEFINE_INTEGER_HASH_TRAITS_32(char, constexpr);
DEFINE_INTEGER_HASH_TRAITS_32(unsigned char, constexpr);
DEFINE_INTEGER_HASH_TRAITS_32(short, constexpr);
DEFINE_INTEGER_HASH_TRAITS_32(unsigned short, constexpr);
DEFINE_INTEGER_HASH_TRAITS_32(int, constexpr);
DEFINE_INTEGER_HASH_TRAITS_32(unsigned int, constexpr);
#if defined(__LP64__)
DEFINE_INTEGER_HASH_TRAITS_64(long, constexpr);
DEFINE_INTEGER_HASH_TRAITS_64(unsigned long, constexpr);
#else
DEFINE_INTEGER_HASH_TRAITS_32(long, constexpr);
DEFINE_INTEGER_HASH_TRAITS_32(unsigned long, constexpr);
#endif
DEFINE_INTEGER_HASH_TRAITS_64(long long, constexpr);
DEFINE_INTEGER_HASH_TRAITS_64(unsigned long long, constexpr);
#if K_SIZE_MAX == INT64_MAX
DEFINE_INTEGER_HASH_TRAITS_64(void *);
DEFINE_INTEGER_HASH_TRAITS_64(const void *);
#else
DEFINE_INTEGER_HASH_TRAITS_32(void *);
DEFINE_INTEGER_HASH_TRAITS_32(const void *);
#endif
#undef DEFINE_INTEGER_HASH_TRAITS_32
#undef DEFINE_INTEGER_HASH_TRAITS_64
// MurmurHash2
static constexpr inline uint64_t HashStr(Span<const char> str)
{
const uint64_t Seed = 0;
const uint64_t Mult = (((uint64_t)0xc6a4a793ull) << 32ull) + (uint64_t)0x5bd1e995ull;
const auto unaligned_load =
#if __cplusplus >= 202002L && (__GNUC__ >= 12 || __clang_major__ >= 16)
!std::is_constant_evaluated() ?
[](const char *p) {
uint64_t result;
__builtin_memcpy(&result, p, sizeof(result));
return result;
} :
#endif
[](const char *p) {
#if defined(K_BIG_ENDIAN)
uint64_t result = ((uint64_t)p[0] << 56) |
((uint64_t)p[1] << 48) |
((uint64_t)p[2] << 40) |
((uint64_t)p[3] << 32) |
((uint64_t)p[4] << 24) |
((uint64_t)p[5] << 16) |
((uint64_t)p[6] << 8) |
((uint64_t)p[7] << 0);
#else
uint64_t result = ((uint64_t)p[0] << 0) |
((uint64_t)p[1] << 8) |
((uint64_t)p[2] << 16) |
((uint64_t)p[3] << 24) |
((uint64_t)p[4] << 32) |
((uint64_t)p[5] << 40) |
((uint64_t)p[6] << 48) |
((uint64_t)p[7] << 56);
#endif
return result;
};
const auto load_bytes = [](const char *p, int n) {
uint64_t result = 0;
n--;
do {
result = (result << 8) + (uint8_t)p[n];
} while (--n >= 0);
return result;
};
const auto shift_mix = [](uint64_t v) { return v ^ (v >> 47); };
const char *end = str.ptr + (str.len & ~0x7);
int remain = (int)(str.len & 0x7);
uint64_t hash = Seed ^ (str.len * Mult);
for (const char *p = str.ptr; p != end; p += 8) {
uint64_t u64 = shift_mix(unaligned_load(p) * Mult) * Mult;
hash = (hash ^ u64) * Mult;
}
if (remain) {
uint64_t u64 = load_bytes(end, remain);
hash = (hash ^ u64) * Mult;
}
hash = shift_mix(hash) * Mult;
hash = shift_mix(hash);
return hash;
}
static constexpr inline uint64_t HashStr(const char *str)
{
Span<const char> span = str;
return HashStr(span);
}
template <>
class HashTraits<const char *> {
public:
static constexpr uint64_t Hash(Span<const char> key) { return HashStr(key); }
static constexpr uint64_t Hash(const char *key) { return HashStr(key); }
static constexpr bool Test(const char *key1, const char *key2) { return TestStr(key1, key2); }
static constexpr bool Test(const char *key1, Span<const char> key2) { return key2 == key1; }
};
template <>
class HashTraits<Span<const char>> {
public:
static constexpr uint64_t Hash(Span<const char> key) { return HashStr(key); }
static constexpr uint64_t Hash(const char *key) { return HashStr(key); }
static constexpr bool Test(Span<const char> key1, Span<const char> key2) { return key1 == key2; }
static constexpr bool Test(Span<const char> key1, const char * key2) { return key1 == key2; }
};
#define K_HASHTABLE_HANDLER_EX_N(Name, ValueType, KeyType, KeyMember, HashFunc, TestFunc) \
class Name { \
public: \
static constexpr KeyType GetKey(const ValueType &value) \
{ return (KeyType)(value.KeyMember); } \
static constexpr KeyType GetKey(const ValueType *value) \
{ return (KeyType)(value->KeyMember); } \
template <typename TestKey> \
static constexpr uint64_t HashKey(TestKey key) \
{ return HashFunc(key); } \
template <typename TestKey> \
static constexpr bool TestKeys(KeyType key1, TestKey key2) \
{ return TestFunc((key1), (key2)); } \
}
#define K_HASHTABLE_HANDLER_EX(ValueType, KeyType, KeyMember, HashFunc, TestFunc) \
K_HASHTABLE_HANDLER_EX_N(HashHandler, ValueType, KeyType, KeyMember, HashFunc, TestFunc)
#define K_HASHTABLE_HANDLER(ValueType, KeyMember) \
K_HASHTABLE_HANDLER_EX(ValueType, decltype(ValueType::KeyMember), KeyMember, HashTraits<decltype(ValueType::KeyMember)>::Hash, HashTraits<decltype(ValueType::KeyMember)>::Test)
#define K_HASHTABLE_HANDLER_N(Name, ValueType, KeyMember) \
K_HASHTABLE_HANDLER_EX_N(Name, ValueType, decltype(ValueType::KeyMember), KeyMember, HashTraits<decltype(ValueType::KeyMember)>::Hash, HashTraits<decltype(ValueType::KeyMember)>::Test)
#define K_HASHTABLE_HANDLER_T(ValueType, KeyType, KeyMember) \
K_HASHTABLE_HANDLER_EX(ValueType, KeyType, KeyMember, HashTraits<KeyType>::Hash, HashTraits<KeyType>::Test)
#define K_HASHTABLE_HANDLER_NT(Name, ValueType, KeyType, KeyMember) \
K_HASHTABLE_HANDLER_EX_N(Name, ValueType, KeyType, KeyMember, HashTraits<KeyType>::Hash, HashTraits<KeyType>::Test)
template <typename KeyType, typename ValueType>
class HashMap {
public:
struct Bucket {
KeyType key;
ValueType value;
K_HASHTABLE_HANDLER(Bucket, key);
};
HashTable<KeyType, Bucket> table;
HashMap() = default;
HashMap(std::initializer_list<Bucket> l) : table(l) {}
void Clear() { table.Clear(); }
void RemoveAll() { table.RemoveAll(); }
template <typename T = KeyType>
ValueType *Find(const T &key)
{ return (ValueType *)((const HashMap *)this)->Find(key); }
template <typename T = KeyType>
const ValueType *Find(const T &key) const
{
const Bucket *table_it = table.Find(key);
return table_it ? &table_it->value : nullptr;
}
template <typename T = KeyType>
ValueType FindValue(const T &key, const ValueType &default_value)
{ return (ValueType)((const HashMap *)this)->FindValue(key, default_value); }
template <typename T = KeyType>
const ValueType FindValue(const T &key, const ValueType &default_value) const
{
const ValueType *it = Find(key);
return it ? *it : default_value;
}
ValueType *Set(const KeyType &key, const ValueType &value)
{ return &table.Set({ key, value })->value; }
Bucket *SetDefault(const KeyType &key)
{
Bucket *table_it = table.SetDefault(key);
table_it->key = key;
return table_it;
}
ValueType *InsertOrGet(const KeyType &key, const ValueType &value, bool *out_inserted = nullptr)
{
Bucket *ptr = table.InsertOrGet({ key, value }, out_inserted);
return &ptr->value;
}
Bucket *InsertOrGetDefault(const KeyType &key, bool *out_inserted = nullptr)
{
bool inserted;
Bucket *ptr = table.InsertOrGetDefault(key, &inserted);
if (inserted) {
ptr->key = key;
}
if (out_inserted) {
*out_inserted = inserted;
}
return ptr;
}
void Remove(ValueType *it)
{
if (!it)
return;
table.Remove((Bucket *)((uint8_t *)it - offsetof(Bucket, value)));
}
void Remove(Bucket *it)
{
if (!it)
return;
table.Remove(it);
}
template <typename T = KeyType>
void Remove(const KeyType &key) { Remove(Find(key)); }
void Trim() { table.Trim(); }
};
template <typename ValueType>
class HashSet {
class Handler {
public:
static constexpr ValueType GetKey(const ValueType &value) { return value; }
static constexpr ValueType GetKey(const ValueType *value) { return *value; }
static constexpr uint64_t HashKey(const ValueType &value)
{ return HashTraits<ValueType>::Hash(value); }
static constexpr bool TestKeys(const ValueType &value1, const ValueType &value2)
{ return HashTraits<ValueType>::Test(value1, value2); }
};
public:
HashTable<ValueType, ValueType, Handler> table;
HashSet() = default;
HashSet(std::initializer_list<ValueType> l) : table(l) {}
void Clear() { table.Clear(); }
void RemoveAll() { table.RemoveAll(); }
template <typename T = ValueType>
ValueType *Find(const T &value) { return table.Find(value); }
template <typename T = ValueType>
const ValueType *Find(const T &value) const { return table.Find(value); }
template <typename T = ValueType>
ValueType FindValue(const T &value, const ValueType &default_value)
{ return table.FindValue(value, default_value); }
template <typename T = ValueType>
const ValueType FindValue(const T &value, const ValueType &default_value) const
{ return table.FindValue(value, default_value); }
ValueType *Set(const ValueType &value) { return table.Set(value); }
ValueType *InsertOrGet(const ValueType &value, bool *out_inserted = nullptr)
{ return table.InsertOrGet(value, out_inserted); }
bool InsertOrFail(const ValueType &value)
{
bool inserted;
InsertOrGet(value, &inserted);
return inserted;
}
void Remove(ValueType *it) { table.Remove(it); }
template <typename T = ValueType>
void Remove(const T &value) { Remove(Find(value)); }
void Trim() { table.Trim(); }
private:
};
// XXX: Switch to perfect hashing later on
template <Size N, typename KeyType, typename ValueType>
class ConstMap {
public:
struct Bucket {
KeyType key;
ValueType value;
};
size_t used[(N + (K_SIZE(size_t) * 8) - 1) / K_SIZE(size_t)] = {};
Bucket data[N] = {};
Size count = 0;
constexpr ConstMap(std::initializer_list<Bucket> l)
{
K_CRITICAL(l.size() <= N, "ConstMap<%1> cannot store %2 values", N, l.size());
for (const Bucket &it: l) {
Bucket *bucket = Insert(it.key);
bucket->key = it.key;
bucket->value = it.value;
}
}
template <typename T = KeyType>
ValueType *Find(const T &key)
{ return (ValueType *)((const ConstMap *)this)->Find(key); }
template <typename T = KeyType>
const ValueType *Find(const T &key) const
{
uint64_t hash = HashTraits<KeyType>::Hash(key);
Size idx = HashToIndex(hash);
const Bucket *bucket = Find(&idx, key);
return bucket ? &bucket->value : nullptr;
}
template <typename T = KeyType>
ValueType FindValue(const T &key, const ValueType &default_value)
{ return (ValueType)((const ConstMap *)this)->FindValue(key, default_value); }
template <typename T = KeyType>
const ValueType FindValue(const T &key, const ValueType &default_value) const
{
const ValueType *it = Find(key);
return it ? *it : default_value;
}
private:
template <typename T = KeyType>
constexpr Bucket *Find(Size *idx, const T &key)
{ return (Bucket *)((const ConstMap *)this)->Find(idx, key); }
template <typename T = KeyType>
constexpr const Bucket *Find(Size *idx, const T &key) const
{
while (!IsEmpty(*idx)) {
if (HashTraits<KeyType>::Test(data[*idx].key, key))
return &data[*idx];
*idx = (*idx + 1) & (N - 1);
}
return nullptr;
}
constexpr Bucket *Insert(const KeyType &key)
{
uint64_t hash = HashTraits<KeyType>::Hash(key);
Size idx = HashToIndex(hash);
Bucket *it = Find(&idx, key);
if (!it) {
count++;
MarkUsed(idx);
return &data[idx];
} else {
return it;
}
}
constexpr void MarkUsed(Size idx)
{
used[idx / (K_SIZE(size_t) * 8)] |= (1ull << (idx % (K_SIZE(size_t) * 8)));
}
constexpr bool IsEmpty(Size idx) const
{
bool empty = !(used[idx / (K_SIZE(size_t) * 8)] & (1ull << (idx % (K_SIZE(size_t) * 8))));
return empty;
}
constexpr Size HashToIndex(uint64_t hash) const
{
return (Size)(hash & (uint64_t)(N - 1));
}
};
// ------------------------------------------------------------------------
// Date
// ------------------------------------------------------------------------
union LocalDate {
int32_t value;
struct {
#if defined(K_BIG_ENDIAN)
int16_t year;
int8_t month;
int8_t day;
#else
int8_t day;
int8_t month;
int16_t year;
#endif
} st;
LocalDate() = default;
#if defined(K_BIG_ENDIAN)
LocalDate(int16_t year, int8_t month, int8_t day)
: st({ year, month, day }) { K_ASSERT(IsValid()); }
#else
LocalDate(int16_t year, int8_t month, int8_t day)
: st({ day, month, year }) { K_ASSERT(IsValid()); }
#endif
static inline bool IsLeapYear(int16_t year)
{
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}
static inline int8_t DaysInMonth(int16_t year, int8_t month)
{
static const int8_t DaysPerMonth[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
return (int8_t)(DaysPerMonth[month - 1] + (month == 2 && IsLeapYear(year)));
}
static LocalDate FromJulianDays(int days);
static LocalDate FromCalendarDate(int days) { return LocalDate::FromJulianDays(days + 2440588); }
bool IsValid() const
{
if (st.year < -4712)
return false;
if (st.month < 1 || st.month > 12)
return false;
if (st.day < 1 || st.day > DaysInMonth(st.year, st.month))
return false;
return true;
}
bool operator==(LocalDate other) const { return value == other.value; }
bool operator!=(LocalDate other) const { return value != other.value; }
bool operator>(LocalDate other) const { return value > other.value; }
bool operator>=(LocalDate other) const { return value >= other.value; }
bool operator<(LocalDate other) const { return value < other.value; }
bool operator<=(LocalDate other) const { return value <= other.value; }
int ToJulianDays() const;
int ToCalendarDate() const { return ToJulianDays() - 2440588; }
int GetWeekDay() const;
int operator-(LocalDate other) const
{ return ToJulianDays() - other.ToJulianDays(); }
LocalDate operator+(int days) const
{
if (days < 5 && days > -5) {
LocalDate date = *this;
if (days > 0) {
while (days--) {
++date;
}
} else {
while (days++) {
--date;
}
}
return date;
} else {
return LocalDate::FromJulianDays(ToJulianDays() + days);
}
}
// That'll fail with INT_MAX days but that's far more days than can
// be represented as a date anyway
LocalDate operator-(int days) const { return *this + (-days); }
LocalDate &operator+=(int days) { *this = *this + days; return *this; }
LocalDate &operator-=(int days) { *this = *this - days; return *this; }
LocalDate &operator++();
LocalDate operator++(int) { LocalDate date = *this; ++(*this); return date; }
LocalDate &operator--();
LocalDate operator--(int) { LocalDate date = *this; --(*this); return date; }
uint64_t Hash() const { return HashTraits<int32_t>::Hash(value); }
};
// ------------------------------------------------------------------------
// Time
// ------------------------------------------------------------------------
int64_t GetUnixTime();
struct TimeSpec {
int16_t year;
int8_t month;
int8_t day;
int8_t week_day; // 1 (monday) to 7 (sunday)
int8_t hour;
int8_t min;
int8_t sec;
int16_t msec;
int16_t offset; // minutes
};
TimeSpec DecomposeTimeUTC(int64_t time);
TimeSpec DecomposeTimeLocal(int64_t time);
int64_t ComposeTimeUTC(const TimeSpec &spec);
// ------------------------------------------------------------------------
// Clock
// ------------------------------------------------------------------------
#if defined(_MSC_VER) && !defined(_M_ARM64)
static inline int64_t GetCoreCycles()
{
return (int64_t)__rdtsc();
}
#elif defined(__i386__) || defined(__x86_64__)
static inline int64_t GetCoreCycles()
{
uint32_t counter_low, counter_high;
__asm__ __volatile__ ("cpuid; rdtsc"
: "=a" (counter_low), "=d" (counter_high)
: : "%ebx", "%ecx");
int64_t counter = ((int64_t)counter_high << 32) | counter_low;
return counter;
}
#elif defined(__aarch64__)
static inline int64_t GetCoreCycles()
{
uint64_t counter;
__asm__ __volatile__ ("mrs %0, cntvct_el0" : "=r" (counter));
return counter;
}
#endif
int64_t GetMonotonicClock();
// ------------------------------------------------------------------------
// Format
// ------------------------------------------------------------------------
enum class FmtType {
Str,
PadStr,
RepeatStr,
Char,
Buffer,
Custom,
Bool,
Integer,
Unsigned,
Float,
Double,
Binary,
Octal,
BigHex,
SmallHex,
BigBytes,
SmallBytes,
MemorySize,
DiskSize,
Date,
TimeISO,
TimeNice,
List,
FlagNames,
FlagOptions,
Random,
SafeStr,
SafeChar
};
template <typename T>
class FmtTraits {
public:
static void Format(const T &obj, FunctionRef<void(Span<const char>)> append) { return obj.Format(append); }
};
class FmtCustom {
struct Base {
virtual void Format(FunctionRef<void(Span<const char>)> append) const = 0;
};
template <typename T>
struct Concrete: public Base {
typedef void FuncType(const T &obj, FunctionRef<void(Span<const char>)>);
const T *obj;
FuncType *func;
Concrete(const T *obj, FuncType *func) : obj(obj), func(func) {}
void Format(FunctionRef<void(Span<const char>)> append) const override { func(*obj, append); }
};
// Concrete has 3 pointers (vtable, obj, func), so size should match!
void *raw[3];
public:
FmtCustom() = default;
template <typename T>
explicit FmtCustom(const T &obj)
{
static_assert(K_SIZE(*this) <= K_SIZE(raw));
new (raw) Concrete<T>(&obj, &FmtTraits<T>::Format);
}
void Format(FunctionRef<void(Span<const char>)> append) const
{
const Base *ptr = reinterpret_cast<const Base *>(&raw);
ptr->Format(append);
}
};
class FmtArg {
public:
FmtType type;
union {
Span<const char> str;
struct {
const char *str;
int count;
} repeat;
char buf[32];
char ch;
FmtCustom custom;
bool b;
int64_t i;
uint64_t u;
Span<const uint8_t> hex;
struct {
float value;
int min_prec;
int max_prec;
} f;
struct {
double value;
int min_prec;
int max_prec;
} d;
const void *ptr;
LocalDate date;
struct {
TimeSpec spec;
bool ms;
} time;
struct {
Size len;
const char *chars;
} random;
struct {
uint64_t flags;
union {
Span<const char *const> names;
Span<const struct OptionDesc> options;
} u;
const char *separator;
} list;
} u;
int pad = 0;
char padding = 0;
FmtArg() = default;
FmtArg(const FmtArg &other) = default;
FmtArg(std::nullptr_t) : FmtArg(FmtType::Str) { u.str = "(null)"; }
FmtArg(const char *str) : FmtArg(FmtType::Str) { u.str = str ? str : "(null)"; }
FmtArg(Span<const char> str) : FmtArg(FmtType::Str) { u.str = str; }
FmtArg(char c) : FmtArg(FmtType::Char) { u.ch = c; }
FmtArg(const FmtCustom &custom) : FmtArg(FmtType::Custom) { u.custom = custom; }
FmtArg(bool b) : FmtArg(FmtType::Bool) { u.b = b; }
FmtArg(unsigned char i) : FmtArg(FmtType::Unsigned) { u.u = i; }
FmtArg(short i) : FmtArg(FmtType::Integer) { u.i = i; }
FmtArg(unsigned short i) : FmtArg(FmtType::Unsigned) { u.u = i; }
FmtArg(int i) : FmtArg(FmtType::Integer) { u.i = i; }
FmtArg(unsigned int i) : FmtArg(FmtType::Unsigned) { u.u = i; }
FmtArg(long i) : FmtArg(FmtType::Integer) { u.i = i; }
FmtArg(unsigned long i) : FmtArg(FmtType::Unsigned) { u.u = i; }
FmtArg(long long i) : FmtArg(FmtType::Integer) { u.i = i; }
FmtArg(unsigned long long i) : FmtArg(FmtType::Unsigned) { u.u = i; }
FmtArg(float f) : FmtArg(FmtType::Float) { u.f = { f, 0, INT_MAX }; }
FmtArg(double d) : FmtArg(FmtType::Double) { u.d = { d, 0, INT_MAX }; }
FmtArg(const void *ptr) : FmtArg(FmtType::BigHex) { u.u = (uint64_t)ptr; }
FmtArg(const LocalDate &date) : FmtArg(FmtType::Date) { u.date = date; }
protected:
FmtArg(FmtType type) : type(type) {}
};
class FmtSafe: public FmtArg {
public:
FmtSafe() = default;
FmtSafe(FmtArg arg) : FmtArg(arg) {}
FmtSafe(std::nullptr_t) : FmtArg(FmtType::Str) { u.str = "(null)"; }
FmtSafe(const char *str) : FmtArg(FmtType::SafeStr) { u.str = str ? str : "(null)"; } // safe
FmtSafe(Span<const char> str) : FmtArg(FmtType::SafeStr) { u.str = str; } // safe
FmtSafe(char c) : FmtArg(FmtType::SafeChar) { u.ch = c; } // safe
FmtSafe(const FmtCustom &custom) : FmtArg(custom) {}
FmtSafe(bool b) : FmtArg(b) {}
FmtSafe(unsigned char i) : FmtArg(i) {}
FmtSafe(short i) : FmtArg(i) {}
FmtSafe(unsigned short i) : FmtArg(i) {}
FmtSafe(int i) : FmtArg(i) {}
FmtSafe(unsigned int i) : FmtArg(i) {}
FmtSafe(long i) : FmtArg(i) {}
FmtSafe(unsigned long i) : FmtArg(i) {}
FmtSafe(long long i) : FmtArg(i) {}
FmtSafe(unsigned long long i) : FmtArg(i) {}
FmtSafe(float f) : FmtArg(f) {}
FmtSafe(double d) : FmtArg(d) {}
FmtSafe(const void *ptr) : FmtArg(ptr) {}
FmtSafe(const LocalDate &date) : FmtArg(date) {}
};
static inline FmtArg FmtInt(long long i, int pad = 0, char padding = '0')
{
FmtArg arg;
arg.type = FmtType::Integer;
arg.u.i = i;
arg.pad = pad;
arg.padding = padding;
return arg;
}
static inline FmtArg FmtInt(unsigned long long u, int pad = 0, char padding = '0')
{
FmtArg arg;
arg.type = FmtType::Unsigned;
arg.u.u = u;
arg.pad = pad;
arg.padding = padding;
return arg;
}
static inline FmtArg FmtInt(unsigned char u, int pad = 0, char padding = '0') { return FmtInt((unsigned long long)u, pad, padding); }
static inline FmtArg FmtInt(short i, int pad = 0, char padding = '0') { return FmtInt((long long)i, pad, padding); }
static inline FmtArg FmtInt(unsigned short u, int pad = 0, char padding = '0') { return FmtInt((unsigned long long)u, pad, padding); }
static inline FmtArg FmtInt(int i, int pad = 0, char padding = '0') { return FmtInt((long long)i, pad, padding); }
static inline FmtArg FmtInt(unsigned int u, int pad = 0, char padding = '0') { return FmtInt((unsigned long long)u, pad, padding); }
static inline FmtArg FmtInt(long i, int pad = 0, char padding = '0') { return FmtInt((long long)i, pad, padding); }
static inline FmtArg FmtInt(unsigned long u, int pad = 0, char padding = '0') { return FmtInt((unsigned long long)u, pad, padding); }
static inline FmtArg FmtBin(uint64_t u, int pad = 0, char padding = '0')
{
FmtArg arg;
arg.type = FmtType::Binary;
arg.u.u = u;
arg.pad = pad;
arg.padding = padding;
return arg;
}
static inline FmtArg FmtOctal(uint64_t u, int pad = 0, char padding = '0')
{
FmtArg arg;
arg.type = FmtType::Octal;
arg.u.u = u;
arg.pad = pad;
arg.padding = padding;
return arg;
}
static inline FmtArg FmtHex(uint64_t u, int pad = 0, char padding = '0')
{
FmtArg arg;
arg.type = FmtType::BigHex;
arg.u.u = u;
arg.pad = pad;
arg.padding = padding;
return arg;
}
static inline FmtArg FmtHexSmall(uint64_t u, int pad = 0, char padding = '0')
{
FmtArg arg;
arg.type = FmtType::SmallHex;
arg.u.u = u;
arg.pad = pad;
arg.padding = padding;
return arg;
}
static inline FmtArg FmtFloat(float f, int min_prec, int max_prec)
{
FmtArg arg;
arg.type = FmtType::Float;
arg.u.f.value = f;
arg.u.f.min_prec = min_prec;
arg.u.f.max_prec = max_prec;
return arg;
}
static inline FmtArg FmtFloat(float f, int prec) { return FmtFloat(f, prec, prec); }
static inline FmtArg FmtFloat(float f) { return FmtFloat(f, 0, INT_MAX); }
static inline FmtArg FmtDouble(double d, int min_prec, int max_prec)
{
FmtArg arg;
arg.type = FmtType::Double;
arg.u.d.value = d;
arg.u.d.min_prec = min_prec;
arg.u.d.max_prec = max_prec;
return arg;
}
static inline FmtArg FmtDouble(double d, int prec) { return FmtDouble(d, prec, prec); }
static inline FmtArg FmtDouble(double d) { return FmtDouble(d, 0, INT_MAX); }
static inline FmtArg FmtMemSize(int64_t size)
{
FmtArg arg;
arg.type = FmtType::MemorySize;
arg.u.i = size;
return arg;
}
static inline FmtArg FmtDiskSize(int64_t size)
{
FmtArg arg;
arg.type = FmtType::DiskSize;
arg.u.i = size;
return arg;
}
static inline FmtArg FmtTimeISO(TimeSpec spec, bool ms = false)
{
FmtArg arg;
arg.type = FmtType::TimeISO;
arg.u.time.spec = spec;
arg.u.time.ms = ms;
return arg;
}
static inline FmtArg FmtTimeNice(TimeSpec spec, bool ms = false)
{
FmtArg arg;
arg.type = FmtType::TimeNice;
arg.u.time.spec = spec;
arg.u.time.ms = ms;
return arg;
}
static inline FmtArg FmtList(Span<const char *const> names, const char *sep = ", ")
{
FmtArg arg;
arg.type = FmtType::List;
arg.u.list.u.names = names;
arg.u.list.separator = sep;
return arg;
}
static inline FmtArg FmtFlags(uint64_t flags, Span<const char *const> names, const char *sep = ", ")
{
FmtArg arg;
arg.type = FmtType::FlagNames;
arg.u.list.flags = flags & ((1ull << names.len) - 1);
arg.u.list.u.names = names;
arg.u.list.separator = sep;
return arg;
}
static inline FmtArg FmtFlags(uint64_t flags, Span<const struct OptionDesc> options, const char *sep = ", ")
{
FmtArg arg;
arg.type = FmtType::FlagOptions;
arg.u.list.flags = flags & ((1ull << options.len) - 1);
arg.u.list.u.options = options;
arg.u.list.separator = sep;
return arg;
}
static inline FmtArg FmtPad(Span<const char> str, int pad, char padding = ' ')
{
FmtArg arg;
arg.type = FmtType::PadStr;
arg.u.str = str;
arg.pad = pad;
arg.padding = padding;
return arg;
}
static inline FmtArg FmtRepeat(const char *str, int count)
{
FmtArg arg;
arg.type = FmtType::RepeatStr;
arg.u.repeat.str = str;
arg.u.repeat.count = count;
return arg;
}
static inline FmtArg FmtHex(Span<const uint8_t> buf)
{
FmtArg arg;
arg.type = FmtType::BigBytes;
arg.u.hex = buf;
return arg;
}
static inline FmtArg FmtHexSmall(Span<const uint8_t> buf)
{
FmtArg arg;
arg.type = FmtType::SmallBytes;
arg.u.hex = buf;
return arg;
}
static inline FmtArg FmtRandom(Size len, const char *chars = nullptr)
{
K_ASSERT(len < 256);
len = std::min(len, (Size)256);
FmtArg arg;
arg.type = FmtType::Random;
arg.u.random.len = len;
arg.u.random.chars = chars;
return arg;
}
class FmtUpperAscii {
Span<const char> str;
public:
FmtUpperAscii(Span<const char> str) : str(str) {}
void Format(FunctionRef<void(Span<const char>)> append) const;
operator FmtArg() const { return FmtCustom(*this); }
};
class FmtLowerAscii {
Span<const char> str;
public:
FmtLowerAscii(Span<const char> str) : str(str) {}
void Format(FunctionRef<void(Span<const char>)> append) const;
operator FmtArg() const { return FmtCustom(*this); }
};
class FmtUrlSafe {
Span<const char> str;
const char *passthrough;
public:
FmtUrlSafe(Span<const char> str, const char *passthrough)
: str(str), passthrough(passthrough) {}
void Format(FunctionRef<void(Span<const char>)> append) const;
operator FmtArg() const { return FmtCustom(*this); }
};
class FmtHtmlSafe {
Span<const char> str;
public:
FmtHtmlSafe(Span<const char> str) : str(str) {}
void Format(FunctionRef<void(Span<const char>)> append) const;
operator FmtArg() const { return FmtCustom(*this); }
};
class FmtEscape {
Span<const char> str;
char quote;
public:
FmtEscape(Span<const char> str, char quote) : str(str), quote(quote) {}
void Format(FunctionRef<void(Span<const char>)> append) const;
operator FmtArg() const { return FmtCustom(*this); }
};
FmtArg FmtVersion(int64_t version, int parts, int by);
enum class LogLevel {
Debug,
Info,
Warning,
Error
};
Span<char> FmtFmt(const char *fmt, Span<const FmtArg> args, bool vt100, Span<char> out_buf);
Span<char> FmtFmt(const char *fmt, Span<const FmtArg> args, bool vt100, HeapArray<char> *out_buf);
Span<char> FmtFmt(const char *fmt, Span<const FmtArg> args, bool vt100, Allocator *alloc);
void FmtFmt(const char *fmt, Span<const FmtArg> args, bool vt100, FunctionRef<void(Span<const char>)> append);
void PrintFmt(const char *fmt, Span<const FmtArg> args, StreamWriter *out_st);
void PrintLnFmt(const char *fmt, Span<const FmtArg> args, StreamWriter *out_st);
#define DEFINE_FMT_VARIANT(Ret, Type) \
static inline Ret Fmt(Type out, const char *fmt) \
{ \
return FmtFmt(fmt, {}, false, out); \
} \
static inline Ret Fmt(Type out, bool vt100, const char *fmt) \
{ \
return FmtFmt(fmt, {}, vt100, out); \
} \
template <typename... Args> \
Ret Fmt(Type out, const char *fmt, Args... args) \
{ \
const FmtArg fmt_args[] = { FmtArg(args)... }; \
return FmtFmt(fmt, fmt_args, false, out); \
} \
template <typename... Args> \
Ret Fmt(Type out, bool vt100, const char *fmt, Args... args) \
{ \
const FmtArg fmt_args[] = { FmtArg(args)... }; \
return FmtFmt(fmt, fmt_args, vt100, out); \
}
#define DEFINE_PRINT_VARIANT(Name, Ret, Type) \
static inline Ret Name(Type out, const char *fmt) \
{ \
return Name##Fmt(fmt, {}, out); \
} \
template <typename... Args> \
Ret Name(Type out, const char *fmt, Args... args) \
{ \
const FmtArg fmt_args[] = { FmtArg(args)... }; \
return Name##Fmt(fmt, fmt_args, out); \
}
DEFINE_FMT_VARIANT(Span<char>, Span<char>)
DEFINE_FMT_VARIANT(Span<char>, HeapArray<char> *)
DEFINE_FMT_VARIANT(Span<char>, Allocator *)
DEFINE_FMT_VARIANT(void, FunctionRef<void(Span<const char>)>)
DEFINE_PRINT_VARIANT(Print, void, StreamWriter *)
DEFINE_PRINT_VARIANT(PrintLn, void, StreamWriter *)
#undef DEFINE_FMT_VARIANT
#undef DEFINE_PRINT_VARIANT
// Print formatted strings to stdout
template <typename... Args>
void Print(const char *fmt, Args... args)
{
Print(StdOut, fmt, args...);
}
template <typename... Args>
void PrintLn(const char *fmt, Args... args)
{
PrintLn(StdOut, fmt, args...);
}
// PrintLn variants without format strings
void PrintLn(StreamWriter *out_st);
void PrintLn();
// ------------------------------------------------------------------------
// Debug and errors
// ------------------------------------------------------------------------
typedef void LogFunc(LogLevel level, const char *ctx, const char *msg);
typedef void LogFilterFunc(LogLevel level, const char *ctx, const char *msg,
FunctionRef<LogFunc> func);
const char *GetEnv(const char *name);
bool GetDebugFlag(const char *name);
void LogFmt(LogLevel level, const char *ctx, const char *fmt, Span<const FmtArg> args);
static inline void Log(LogLevel level, const char *ctx)
{
LogFmt(level, ctx, "", {});
}
static inline void Log(LogLevel level, const char *ctx, const char *fmt)
{
LogFmt(level, ctx, fmt, {});
}
template <typename... Args>
static inline void Log(LogLevel level, const char *ctx, const char *fmt, Args... args)
{
const FmtArg fmt_args[] = { FmtSafe(args)... };
LogFmt(level, ctx, fmt, fmt_args);
}
#if defined(K_DEBUG) && __cplusplus >= 202002L && __has_include(<source_location>)
struct LogContext {
const char *fmt;
char str[56] = {};
consteval LogContext(const char *fmt, const std::source_location &location = std::source_location::current())
: fmt(fmt)
{
Span<const char> filename = location.file_name();
int line = (int)location.line();
int digits = 1 + (line >= 10) + (line >= 100) + (line >= 1000) +
(line >= 10000) + (line >= 100000) + (line >= 1000000) +
(line >= 10000000) + (line >= 100000000) + (line >= 1000000000);
// Cut long filenames to make sure is 32 characters long at most
Size treshold = 25 - digits;
Size offset = 0;
str[offset++] = '[';
if (filename.len > treshold) {
filename = filename.Take(filename.len - treshold, treshold);
str[offset++] = '.';
str[offset++] = '.';
str[offset++] = '.';
}
for (char c: filename) {
str[offset++] = c;
}
str[offset++] = ':';
offset += digits;
for (int i = 0; i < digits; i++) {
str[offset - i - 1] = '0' + (line % 10);
line /= 10;
}
str[offset++] = ']';
str[offset++] = ' ';
}
};
template <typename... Args>
static inline void LogDebug(LogContext ctx, Args... args) { Log(LogLevel::Debug, ctx.str, ctx.fmt, args...); }
template <typename... Args>
static inline void LogInfo() { Log(LogLevel::Info, nullptr); }
template <typename... Args>
static inline void LogInfo(const char *fmt, Args... args) { Log(LogLevel::Info, nullptr, fmt, args...); }
template <typename... Args>
static inline void LogWarning(LogContext ctx, Args... args) { Log(LogLevel::Warning, ctx.str, ctx.fmt, args...); }
template <typename... Args>
static inline void LogError(LogContext ctx, Args... args) { Log(LogLevel::Error, ctx.str, ctx.fmt, args...); }
#else
#if defined(K_DEBUG)
template <typename... Args>
static inline void LogDebug(Args... args) { Log(LogLevel::Debug, "Debug: ", args...); }
#else
template <typename... Args>
static inline void LogDebug(Args...) {}
#endif
template <typename... Args>
static inline void LogInfo(Args... args) { Log(LogLevel::Info, nullptr, args...); }
template <typename... Args>
static inline void LogWarning(Args... args) { Log(LogLevel::Warning, T("Warning: "), args...); }
template <typename... Args>
static inline void LogError(Args... args) { Log(LogLevel::Error, T("Error: "), args...); }
#endif
void SetLogHandler(const std::function<LogFunc> &func, bool vt100);
void DefaultLogHandler(LogLevel level, const char *ctx, const char *msg);
void PushLogFilter(const std::function<LogFilterFunc> &func);
void PopLogFilter();
#if defined(_WIN32)
bool RedirectLogToWindowsEvents(const char *name);
#endif
// ------------------------------------------------------------------------
// Progress
// ------------------------------------------------------------------------
#if !defined(__wasi__)
struct ProgressNode;
struct ProgressInfo {
Span<const char> text;
bool determinate;
int64_t value;
int64_t min;
int64_t max;
};
typedef void ProgressFunc(Span<const ProgressInfo> states);
class ProgressHandle {
char text[K_PROGRESS_TEXT_SIZE] = {};
std::atomic<ProgressNode *> node = nullptr;
public:
ProgressHandle() {}
ProgressHandle(Span<const char> str) { CopyText(str, text); }
~ProgressHandle();
void Set(int64_t value, int64_t min, int64_t max);
void Set(int64_t value, int64_t max) { Set(value, 0, max); }
void Set() { Set(0, 0, 0); }
void Set(int64_t value, int64_t min, int64_t max, Span<const char> text);
void Set(int64_t value, int64_t max, Span<const char> text) { Set(value, 0, max, text); }
void Set(Span<const char> text) { Set(0, 0, 0, text); }
template<typename... Args>
void SetFmt(int64_t value, int64_t min, int64_t max, const char *fmt, Args... args)
{
char buf[K_PROGRESS_TEXT_SIZE];
Fmt(buf, fmt, args...);
Set(value, min, max, (const char *)buf);
}
template<typename... Args>
void SetFmt(int64_t value, int64_t max, const char *fmt, Args... args) { SetFmt(value, 0, max, fmt, args...); }
template<typename... Args>
void SetFmt(const char *fmt, Args... args) { SetFmt(1ll, 0ll, fmt, args...); }
template<typename... Args>
void operator()(int64_t value, int64_t min, int64_t max) { Set(value, min, max); }
void operator()(int64_t value, int64_t max) { Set(value, 0, max); }
void operator()() { Set(0, 0); }
private:
ProgressNode *AcquireNode();
void CopyText(Span<const char> text, char out[K_PROGRESS_TEXT_SIZE]);
};
void SetProgressHandler(const std::function<ProgressFunc> &func);
void DefaultProgressHandler(Span<const ProgressInfo> bars);
#endif
// ------------------------------------------------------------------------
// System
// ------------------------------------------------------------------------
#if defined(_WIN32)
#define K_PATH_SEPARATORS "\\/"
#define K_PATH_DELIMITER ';'
#define K_EXECUTABLE_EXTENSION ".exe"
#define K_SHARED_LIBRARY_EXTENSION ".dll"
#else
#define K_PATH_SEPARATORS "/"
#define K_PATH_DELIMITER ':'
#define K_EXECUTABLE_EXTENSION ""
#define K_SHARED_LIBRARY_EXTENSION ".so"
#endif
#if defined(_WIN32)
bool IsWin32Utf8();
Size ConvertUtf8ToWin32Wide(Span<const char> str, Span<wchar_t> out_str_w);
Size ConvertWin32WideToUtf8(const wchar_t *str_w, Span<char> out_str);
char *GetWin32ErrorString(uint32_t error_code = UINT32_MAX);
#endif
static inline bool IsPathSeparator(char c)
{
#if defined(_WIN32)
return c == '/' || c == '\\';
#else
return c == '/';
#endif
}
enum class CompressionType {
None,
Zlib,
Gzip,
Brotli,
LZ4,
Zstd
};
static const char *const CompressionTypeNames[] = {
"None",
"Zlib",
"Gzip",
"Brotli",
"LZ4",
"Zstd"
};
static const char *const CompressionTypeExtensions[] = {
nullptr,
".zz",
".gz",
".br",
".lz4",
".zst"
};
Span<const char> GetPathDirectory(Span<const char> filename);
Span<const char> GetPathExtension(Span<const char> filename,
CompressionType *out_compression_type = nullptr);
enum class NormalizeFlag {
EndWithSeparator = 1 << 0,
ForceSlash = 1 << 1,
NoExpansion = 1 << 2
};
Span<char> NormalizePath(Span<const char> path, Span<const char> root_directory, unsigned int flags, Allocator *alloc);
static inline Span<char> NormalizePath(Span<const char> path, Span<const char> root_directory, Allocator *alloc)
{ return NormalizePath(path, root_directory, 0, alloc); }
static inline Span<char> NormalizePath(Span<const char> path, unsigned int flags, Allocator *alloc)
{ return NormalizePath(path, {}, flags, alloc); }
static inline Span<char> NormalizePath(Span<const char> path, Allocator *alloc)
{ return NormalizePath(path, {}, 0, alloc); }
bool PathIsAbsolute(const char *path);
bool PathIsAbsolute(Span<const char> path);
bool PathContainsDotDot(const char *path);
bool PathContainsDotDot(Span<const char> path);
enum class StatFlag {
SilentMissing = 1 << 0,
FollowSymlink = 1 << 1
};
enum class FileType {
Directory,
File,
Link,
Device,
Pipe,
Socket
};
static const char *const FileTypeNames[] = {
"Directory",
"File",
"Link",
"Device",
"Pipe",
"Socket"
};
struct FileInfo {
FileType type;
int64_t size;
int64_t mtime;
int64_t ctime;
int64_t atime;
int64_t btime;
unsigned int mode;
uint32_t uid;
uint32_t gid;
};
enum class StatResult {
Success,
MissingPath,
AccessDenied,
OtherError
};
StatResult StatFile(int fd, const char *filename, unsigned int flags, FileInfo *out_info);
static inline StatResult StatFile(int fd, const char *filename, FileInfo *out_info)
{ return StatFile(fd, filename, 0, out_info); }
static inline StatResult StatFile(const char *filename, unsigned int flags, FileInfo *out_info)
{ return StatFile(-1, filename, flags, out_info); }
static inline StatResult StatFile(const char *filename, FileInfo *out_info)
{ return StatFile(-1, filename, 0, out_info); }
enum class RenameFlag {
Overwrite = 1 << 0,
Sync = 1 << 1
};
enum class RenameResult {
Success = 0,
AlreadyExists = 1 << 0,
OtherError = 1 << 1
};
// Sync failures are logged but not reported as errors
RenameResult RenameFile(const char *src_filename, const char *dest_filename, unsigned int silent, unsigned int flags);
static inline RenameResult RenameFile(const char *src_filename, const char *dest_filename, unsigned int flags)
{ return RenameFile(src_filename, dest_filename, 0, flags); }
bool ResizeFile(int fd, const char *filename, int64_t len);
#if !defined(_WIN32)
bool SetFileMode(int fd, const char *filename, uint32_t mode);
static inline bool SetFileMode(const char *filename, uint32_t mode)
{ return SetFileMode(-1, filename, mode); }
bool SetFileOwner(int fd, const char *filename, uint32_t uid, uint32_t gid);
static inline bool SetFileOwner(const char *filename, uint32_t uid, uint32_t gid)
{ return SetFileOwner(-1, filename, uid, gid); }
#endif
bool SetFileTimes(int fd, const char *filename, int64_t mtime, int64_t ctime);
static inline bool SetFileTimes(const char *filename, int64_t mtime, int64_t ctime)
{ return SetFileTimes(-1, filename, mtime, ctime); }
struct VolumeInfo {
int64_t total;
int64_t available;
};
#if !defined(__wasm__)
bool GetVolumeInfo(const char *dirname, VolumeInfo *out_volume);
#endif
enum class EnumResult {
Success,
MissingPath,
AccessDenied,
PartialEnum,
CallbackFail,
OtherError
};
EnumResult EnumerateDirectory(const char *dirname, const char *filter, Size max_files,
FunctionRef<bool(const char *, FileType)> func);
EnumResult EnumerateDirectory(const char *dirname, const char *filter, Size max_files,
FunctionRef<bool(const char *, const FileInfo &)> func);
#if !defined(_WIN32) && !defined(__APPLE__)
EnumResult EnumerateDirectory(int fd, const char *dirname, const char *filter, Size max_files,
FunctionRef<bool(const char *, FileType)> func);
EnumResult EnumerateDirectory(int fd, const char *dirname, const char *filter, Size max_files,
FunctionRef<bool(const char *, const FileInfo &)> func);
#endif
bool EnumerateFiles(const char *dirname, const char *filter, Size max_depth, Size max_files,
Allocator *str_alloc, HeapArray<const char *> *out_files);
bool IsDirectoryEmpty(const char *dirname);
bool TestFile(const char *filename);
bool TestFile(const char *filename, FileType type);
bool IsDirectory(const char *filename);
#if defined(_WIN32)
bool MatchPathName(const char *path, const char *spec, bool case_sensitive = false);
bool MatchPathSpec(const char *path, const char *spec, bool case_sensitive = false);
#else
bool MatchPathName(const char *path, const char *spec, bool case_sensitive = true);
bool MatchPathSpec(const char *path, const char *spec, bool case_sensitive = true);
#endif
bool FindExecutableInPath(const char *path, const char *name,
Allocator *alloc = nullptr, const char **out_path = nullptr);
bool FindExecutableInPath(const char *name, Allocator *alloc = nullptr,
const char **out_path = nullptr);
bool SetWorkingDirectory(const char *directory);
const char *GetWorkingDirectory();
const char *GetApplicationExecutable(); // Can be NULL (EmSDK)
const char *GetApplicationDirectory(); // Can be NULL (EmSDK)
bool MakeDirectory(const char *directory, bool error_if_exists = true);
bool MakeDirectoryRec(Span<const char> directory);
bool UnlinkDirectory(const char *directory, bool error_if_missing = false);
bool UnlinkFile(const char *filename, bool error_if_missing = false);
bool EnsureDirectoryExists(const char *filename);
enum class OpenFlag {
Read = 1 << 0,
Write = 1 << 1,
Append = 1 << 2,
Keep = 1 << 3,
Exists = 1 << 4,
Exclusive = 1 << 5,
NoFollow = 1 << 6,
Directory = 1 << 7
};
enum class OpenResult {
Success = 0,
MissingPath = 1 << 0,
FileExists = 1 << 1,
AccessDenied = 1 << 2,
OtherError = 1 << 3
};
OpenResult OpenFile(const char *filename, unsigned int flags, unsigned int silent, int *out_fd);
static inline OpenResult OpenFile(const char *filename, unsigned int flags, int *out_fd)
{ return OpenFile(filename, flags, 0, out_fd); }
static inline int OpenFile(const char *filename, unsigned int flags)
{
int fd = -1;
if (OpenFile(filename, flags, &fd) != OpenResult::Success)
return -1;
return fd;
}
void CloseDescriptor(int fd);
bool FlushFile(int fd, const char *filename);
bool SpliceFile(int src_fd, const char *src_filename, int64_t src_offset,
int dest_fd, const char *dest_filename, int64_t dest_offset, int64_t size,
FunctionRef<void(int64_t, int64_t)> progress = [](int64_t, int64_t) {});
static inline bool SpliceFile(int src_fd, const char *src_filename, int dest_fd, const char *dest_filename, int64_t size,
FunctionRef<void(int64_t, int64_t)> progress = [](int64_t, int64_t) {})
{ return SpliceFile(src_fd, src_filename, 0, dest_fd, dest_filename, 0, size, progress); }
bool FileIsVt100(int fd);
#if !defined(__wasi__)
#if defined(_WIN32)
enum class PipeMode {
Byte,
Message
};
bool CreateOverlappedPipe(bool overlap0, bool overlap1, PipeMode mode, void *out_handles[2]); // HANDLE
void CloseHandleSafe(void **handle_ptr); // HANDLE
#else
void SetSignalHandler(int signal, void (*func)(int), struct sigaction *prev = nullptr);
bool CreatePipe(bool block, int out_pfd[2]);
void CloseDescriptorSafe(int *fd_ptr);
#endif
struct ExecuteInfo {
struct KeyValue {
const char *key;
const char *value;
};
const char *work_dir = nullptr;
bool reset_env = false;
Span<const KeyValue> env_variables = {};
};
bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
FunctionRef<Span<const uint8_t>()> in_func,
FunctionRef<void(Span<uint8_t> buf)> out_func, int *out_code);
bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
Span<const uint8_t> in_buf, Size max_len,
HeapArray<uint8_t> *out_buf, int *out_code);
// Simple variants
static inline bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info, int *out_code)
{ return ExecuteCommandLine(cmd_line, info, {}, {}, out_code); }
static inline bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
Span<const uint8_t> in_buf,
FunctionRef<void(Span<uint8_t> buf)> out_func, int *out_code)
{
const auto read_once = [&]() {
Span<const uint8_t> buf = in_buf;
in_buf = {};
return buf;
};
return ExecuteCommandLine(cmd_line, info, read_once, out_func, out_code);
}
// Char variants
static inline bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
Span<const char> in_buf,
FunctionRef<void(Span<char> buf)> out_func, int *out_code)
{
const auto write = [&](Span<uint8_t> buf) { out_func(buf.As<char>()); };
return ExecuteCommandLine(cmd_line, info, in_buf.As<const uint8_t>(), write, out_code);
}
static inline bool ExecuteCommandLine(const char *cmd_line, const ExecuteInfo &info,
Span<const char> in_buf, Size max_len,
HeapArray<char> *out_buf, int *out_code)
{
return ExecuteCommandLine(cmd_line, info, in_buf.As<const uint8_t>(), max_len,
(HeapArray<uint8_t> *)out_buf, out_code);
}
Size ReadCommandOutput(const char *cmd_line, Span<char> out_output);
bool ReadCommandOutput(const char *cmd_line, HeapArray<char> *out_output);
#endif
void WaitDelay(int64_t delay);
#if !defined(__wasi__)
enum class WaitResult {
Ready,
Timeout,
Interrupt,
Message,
Exit
};
struct WaitSource {
#if defined(_WIN32)
// Special-cased on Windows: set to NULL to wait for the Win32 message pump too
void *handle; // HANDLE
int timeout;
#else
int fd;
int timeout;
int events = 0;
#endif
};
// After WaitEvents() has been called once (even with timeout 0), a few signals (such as SIGINT, SIGHUP)
// and their Windows equivalent will be permanently ignored.
// Only the main thread (running main) will get WaitResult::Message events (and SIGUSR1).
WaitResult WaitEvents(Span<const WaitSource> sources, int64_t timeout, uint64_t *out_ready = nullptr);
WaitResult WaitEvents(int64_t timeout);
void PostWaitMessage();
void PostTerminate();
#endif
int GetCoreCount();
#if !defined(_WIN32) && !defined(__wasi__)
bool RaiseMaximumOpenFiles(int limit = -1);
bool DropRootIdentity();
#endif
#if defined(__linux__)
bool NotifySystemd();
#endif
#define K_RESTART_EINTR(CallCode, ErrorCond) \
([&]() { \
decltype(CallCode) ret; \
while ((ret = (CallCode)) ErrorCond && errno == EINTR); \
return ret; \
})()
class InitHelper {
public:
const char *name;
InitHelper *next = nullptr;
InitHelper(const char *name);
virtual void Run() = 0;
};
class FinalizeHelper {
public:
const char *name;
FinalizeHelper *next = nullptr;
FinalizeHelper(const char *name);
virtual void Run() = 0;
};
#define K_INIT_(ClassName, Name) \
class ClassName: public InitHelper { \
public: \
ClassName(): InitHelper(Name) {} \
void Run() override; \
}; \
static ClassName K_UNIQUE_NAME(init); \
void ClassName::Run()
#define K_INIT(Name) K_INIT_(K_CONCAT(K_UNIQUE_NAME(InitHelper), Name), K_STRINGIFY(Name))
#define K_FINALIZE_(ClassName, Name) \
class ClassName: public FinalizeHelper { \
public: \
ClassName(): FinalizeHelper(Name) {} \
void Run() override; \
}; \
static ClassName K_UNIQUE_NAME(finalize); \
void ClassName::Run()
#define K_FINALIZE(Name) K_FINALIZE_(K_CONCAT(K_UNIQUE_NAME(FinalizeHelper), Name), K_STRINGIFY(Name))
#define K_EXIT_(ClassName) \
class ClassName { \
public: \
~ClassName(); \
}; \
static ClassName K_UNIQUE_NAME(exit); \
ClassName::~ClassName()
#define K_EXIT(Name) K_EXIT_(K_CONCAT(K_UNIQUE_NAME(ExitHelper), Name))
void InitApp();
void ExitApp();
int Main(int argc, char **argv);
static inline int RunApp(int argc, char **argv)
{
K_CRITICAL(argc >= 1, "First argument is missing");
InitApp();
K_DEFER { ExitApp(); };
return Main(argc, argv);
}
// ------------------------------------------------------------------------
// Standard paths
// ------------------------------------------------------------------------
const char *GetUserConfigPath(const char *name, Allocator *alloc); // Can return NULL
const char *GetUserCachePath(const char *name, Allocator *alloc); // Can return NULL
const char *GetSystemConfigPath(const char *name, Allocator *alloc);
const char *GetTemporaryDirectory();
enum class FindConfigFlag {
IgnoreAppDir = 1 << 0
};
const char *FindConfigFile(const char *directory, Span<const char *const> names,
Allocator *alloc, HeapArray<const char *> *out_possibilities = nullptr);
static inline const char *FindConfigFile(Span<const char *const> names, Allocator *alloc,
HeapArray<const char *> *out_possibilities = nullptr)
{ return FindConfigFile(nullptr, names, alloc, out_possibilities); }
const char *CreateUniqueFile(Span<const char> directory, const char *prefix, const char *extension,
Allocator *alloc, int *out_fd = nullptr);
const char *CreateUniqueDirectory(Span<const char> directory, const char *prefix, Allocator *alloc);
// ------------------------------------------------------------------------
// Parsing
// ------------------------------------------------------------------------
enum class ParseFlag {
Log = 1 << 0,
Validate = 1 << 1,
End = 1 << 2
};
#define K_DEFAULT_PARSE_FLAGS ((int)ParseFlag::Log | (int)ParseFlag::Validate | (int)ParseFlag::End)
template <typename T>
bool ParseInt(Span<const char> str, T *out_value, unsigned int flags = K_DEFAULT_PARSE_FLAGS,
Span<const char> *out_remaining = nullptr)
{
if (!str.len) [[unlikely]] {
if (flags & (int)ParseFlag::Log) {
LogError("Cannot convert empty string to integer");
}
return false;
}
uint64_t value = 0;
Size pos = 0;
uint64_t neg = 0;
if (str.len >= 2) {
if (std::numeric_limits<T>::min() < 0 && str[0] == '-') {
pos = 1;
neg = UINT64_MAX;
} else if (str[0] == '+') {
pos = 1;
}
}
for (; pos < str.len; pos++) {
unsigned int digit = (unsigned int)(str[pos] - '0');
if (digit > 9) [[unlikely]] {
if (!pos || flags & (int)ParseFlag::End) {
if (flags & (int)ParseFlag::Log) {
LogError("Malformed integer number '%1'", str);
}
return false;
} else {
break;
}
}
uint64_t new_value = (value * 10) + digit;
if (new_value < value) [[unlikely]]
goto overflow;
value = new_value;
}
if (value > (uint64_t)std::numeric_limits<T>::max()) [[unlikely]]
goto overflow;
value = ((value ^ neg) - neg);
if (out_remaining) {
*out_remaining = str.Take(pos, str.len - pos);
}
*out_value = (T)value;
return true;
overflow:
if (flags & (int)ParseFlag::Log) {
LogError("Integer overflow for number '%1' (max = %2)", str,
std::numeric_limits<T>::max());
}
return false;
}
bool ParseBool(Span<const char> str, bool *out_value, unsigned int flags = K_DEFAULT_PARSE_FLAGS,
Span<const char> *out_remaining = nullptr);
bool ParseSize(Span<const char> str, int64_t *out_size, unsigned int flags = K_DEFAULT_PARSE_FLAGS,
Span<const char> *out_remaining = nullptr);
#if K_SIZE_MAX < INT64_MAX
static inline bool ParseSize(Span<const char> str, Size *out_size,
unsigned int flags = K_DEFAULT_PARSE_FLAGS, Span<const char> *out_remaining = nullptr)
{
int64_t size = 0;
if (!ParseSize(str, &size, flags, out_remaining))
return false;
if (size > K_SIZE_MAX) [[unlikely]] {
if (flags & (int)ParseFlag::Log) {
LogError("Size value is too high");
}
return false;
}
*out_size = (Size)size;
return true;
}
#endif
bool ParseDate(Span<const char> date_str, LocalDate *out_date, unsigned int flags = K_DEFAULT_PARSE_FLAGS,
Span<const char> *out_remaining = nullptr);
bool ParseDuration(Span<const char> str, int64_t *out_duration, unsigned int flags = K_DEFAULT_PARSE_FLAGS,
Span<const char> *out_remaining = nullptr);
static inline bool ParseDuration(Span<const char> str, int *out_duration,
unsigned int flags = K_DEFAULT_PARSE_FLAGS, Span<const char> *out_remaining = nullptr)
{
int64_t duration = 0;
if (!ParseDuration(str, &duration, flags, out_remaining))
return false;
if (duration > INT_MAX) [[unlikely]] {
if (flags & (int)ParseFlag::Log) {
LogError("Duration value is too high");
}
return false;
}
*out_duration = (int)duration;
return true;
}
bool ParseVersion(Span<const char> str, int parts, int multiplier, int64_t *out_duration,
unsigned int flags = K_DEFAULT_PARSE_FLAGS, Span<const char> *out_remaining = nullptr);
// ------------------------------------------------------------------------
// Random
// ------------------------------------------------------------------------
void InitChaCha20(uint32_t state[16], const uint8_t key[32], const uint8_t iv[8], const uint8_t counter[8] = nullptr);
void RunChaCha20(uint32_t state[16], uint8_t out_buf[64]);
void FillRandomSafe(void *buf, Size len);
static inline void FillRandomSafe(Span<uint8_t> buf) { FillRandomSafe(buf.ptr, buf.len); }
class FastRandom {
uint64_t state[4];
public:
FastRandom();
FastRandom(uint64_t seed);
uint64_t Next();
void Fill(void *buf, Size len);
void Fill(Span<uint8_t> buf) { Fill(buf.ptr, buf.len); }
int GetInt(int min, int max);
int64_t GetInt64(int64_t min, int64_t max);
};
template <typename T>
class FastRandomRNG {
FastRandom rng;
public:
typedef T result_type;
static constexpr T min() { return std::numeric_limits<T>::min(); }
static constexpr T max() { return std::numeric_limits<T>::max(); }
T operator()()
{
T value;
rng.Fill(&value, K_SIZE(value));
return value;
}
};
uint64_t GetRandom();
int GetRandomInt(int min, int max);
int64_t GetRandomInt64(int64_t min, int64_t max);
// ------------------------------------------------------------------------
// Sockets
// ------------------------------------------------------------------------
#if !defined(__wasi__)
enum class SocketType {
Dual,
IPv4,
IPv6,
Unix
};
static const char *const SocketTypeNames[] = {
"Dual",
"IPv4",
"IPv6",
"Unix"
};
#if defined(_WIN32)
#define SOCK_OVERLAPPED 256
#endif
#if defined(_WIN32)
bool InitWinsock();
#endif
int CreateSocket(SocketType type, int flags);
bool BindIPSocket(int sock, SocketType type, const char *addr, int port);
bool BindUnixSocket(int sock, const char *path);
bool ConnectIPSocket(int sock, const char *addr, int port);
bool ConnectUnixSocket(int sock, const char *path);
// Only for sockets on Windows
void SetDescriptorNonBlock(int fd, bool enable);
void SetDescriptorRetain(int fd, bool retain);
void CloseSocket(int fd);
#endif
// ------------------------------------------------------------------------
// Tasks
// ------------------------------------------------------------------------
class Async {
K_DELETE_COPY(Async)
#if !defined(__wasi__)
std::atomic_bool success { true };
std::atomic_int remaining_tasks { 0 };
class AsyncPool *pool;
#else
bool success = true;
#endif
public:
Async(int threads = -1);
Async(Async *parent);
~Async();
void Run(const std::function<bool()> &f);
void Run(int worker, const std::function<bool()> &f);
bool Sync();
bool SyncSoon();
bool Wait(int timeout);
bool IsSuccess() const { return success; }
int GetWorkerCount();
static bool IsTaskRunning();
static int GetWorkerIdx();
friend class AsyncPool;
};
// ------------------------------------------------------------------------
// Streams
// ------------------------------------------------------------------------
enum class CompressionSpeed {
Default,
Slow,
Fast
};
class StreamDecoder;
class StreamEncoder;
class StreamReader {
K_DELETE_COPY(StreamReader)
enum class SourceType {
Memory,
File,
Function
};
const char *filename = nullptr;
bool error = true;
int64_t read_total = 0;
int64_t read_max = -1;
struct {
SourceType type = SourceType::Memory;
union U {
struct {
Span<const uint8_t> buf;
Size pos;
} memory;
struct {
int fd;
bool owned;
} file;
std::function<Size(Span<uint8_t> buf)> func;
// StreamReader deals with func destructor
U() {}
~U() {}
} u;
bool eof = false;
} source;
#if !defined(__wasm__)
std::mutex mutex;
#endif
StreamDecoder *decoder = nullptr;
int64_t raw_len = -1;
Size raw_read = 0;
bool eof = false;
BlockAllocator str_alloc;
public:
StreamReader() { Close(true); }
StreamReader(Span<const uint8_t> buf, const char *filename, CompressionType compression_type = CompressionType::None)
: StreamReader() { Open(buf, filename, compression_type); }
StreamReader(int fd, const char *filename, CompressionType compression_type = CompressionType::None)
: StreamReader() { Open(fd, filename, compression_type); }
StreamReader(const char *filename, CompressionType compression_type = CompressionType::None)
: StreamReader() { Open(filename, compression_type); }
StreamReader(const std::function<Size(Span<uint8_t>)> &func, const char *filename,
CompressionType compression_type = CompressionType::None)
: StreamReader() { Open(func, filename, compression_type); }
~StreamReader() { Close(true); }
// Call before Open. Takes ownership and deletes the decoder at the end.
void SetDecoder(StreamDecoder *decoder);
bool Open(Span<const uint8_t> buf, const char *filename, CompressionType compression_type = CompressionType::None);
bool Open(int fd, const char *filename, CompressionType compression_type = CompressionType::None);
OpenResult Open(const char *filename, CompressionType compression_type = CompressionType::None);
bool Open(const std::function<Size(Span<uint8_t>)> &func, const char *filename,
CompressionType compression_type = CompressionType::None);
bool Close() { return Close(false); }
// File-specific
bool Rewind();
const char *GetFileName() const { return filename; }
int64_t GetReadLimit() { return read_max; }
bool IsValid() const { return filename && !error; }
bool IsEOF() const { return eof; }
int GetDescriptor() const;
void SetDescriptorOwned(bool owned);
void SetReadLimit(int64_t limit) { read_max = limit; }
// Thread safe methods
Size Read(Span<uint8_t> out_buf);
Size Read(Span<char> out_buf) { return Read(out_buf.As<uint8_t>()); }
// Thread safe methods
Size ReadFill(Span<uint8_t> out_buf);
Size ReadFill(Span<char> out_buf) { return ReadFill(out_buf.As<uint8_t>()); }
Size ReadFill(Size buf_len, void *out_buf) { return ReadFill(MakeSpan((uint8_t *)out_buf, buf_len)); }
Size ReadAll(Size max_len, HeapArray<uint8_t> *out_buf);
Size ReadAll(Size max_len, HeapArray<char> *out_buf)
{ return ReadAll(max_len, (HeapArray<uint8_t> *)out_buf); }
int64_t ComputeRawLen();
int64_t GetRawRead() const { return raw_read; }
private:
bool Close(bool implicit);
bool InitDecompressor(CompressionType type);
Size ReadRaw(Size max_len, void *out_buf);
friend class StreamDecoder;
};
static inline Size ReadFile(const char *filename, Span<uint8_t> out_buf)
{
StreamReader st(filename);
return st.ReadFill(out_buf);
}
static inline Size ReadFile(const char *filename, Span<char> out_buf)
{
StreamReader st(filename);
return st.ReadFill(out_buf);
}
static inline Size ReadFile(const char *filename, Size max_len, HeapArray<uint8_t> *out_buf)
{
StreamReader st(filename);
return st.ReadAll(max_len, out_buf);
}
static inline Size ReadFile(const char *filename, Size max_len, HeapArray<char> *out_buf)
{
StreamReader st(filename);
return st.ReadAll(max_len, out_buf);
}
class StreamDecoder {
protected:
StreamReader *reader;
public:
StreamDecoder(StreamReader *reader) : reader(reader) {}
virtual ~StreamDecoder() {}
virtual Size Read(Size max_len, void *out_buf) = 0;
protected:
const char *GetFileName() const { return reader->filename; }
bool IsValid() const { return reader->IsValid(); }
bool IsSourceEOF() const { return reader->source.eof; }
Size ReadRaw(Size max_len, void *out_buf) { return reader->ReadRaw(max_len, out_buf); }
void SetEOF(bool eof) { reader->eof = eof; }
};
typedef StreamDecoder *CreateDecompressorFunc(StreamReader *reader, CompressionType type);
class StreamDecompressorHelper {
public:
StreamDecompressorHelper(CompressionType type, CreateDecompressorFunc *func);
};
#define K_REGISTER_DECOMPRESSOR(Type, Cls) \
static StreamDecoder *K_UNIQUE_NAME(CreateDecompressor)(StreamReader *reader, CompressionType type) \
{ \
StreamDecoder *decompressor = new Cls(reader, type); \
return decompressor; \
} \
static StreamDecompressorHelper K_UNIQUE_NAME(CreateDecompressorHelper)((Type), K_UNIQUE_NAME(CreateDecompressor))
class LineReader {
K_DELETE_COPY(LineReader)
HeapArray<char> buf;
Span<char> view = {};
StreamReader *st;
bool error;
bool eof = false;
Span<char> line = {};
int line_number = 0;
public:
LineReader(StreamReader *st) : st(st), error(!st->IsValid()) {}
const char *GetFileName() const { return st->GetFileName(); }
int GetLineNumber() const { return line_number; }
bool IsValid() const { return !error; }
bool IsEOF() const { return eof; }
bool Next(Span<char> *out_line);
bool Next(Span<const char> *out_line) { return Next((Span<char> *)out_line); }
void PushLogFilter();
};
enum class StreamWriterFlag {
Exclusive = 1 << 0, // Only for files
Atomic = 1 << 1, // Only for files
NoBuffer = 1 << 2, // Only for files and descriptors
LineBuffer = 1 << 3 // Only for files and descriptors
};
class StreamWriter {
K_DELETE_COPY(StreamWriter)
enum class DestinationType {
Memory,
DirectFile,
LineFile,
BufferedFile,
Function
};
const char *filename = nullptr;
bool error = true;
struct {
DestinationType type = DestinationType::Memory;
union U {
struct {
HeapArray<uint8_t> *memory;
Size start;
} mem;
struct {
int fd;
bool owned;
Span<uint8_t> buf;
Size buf_used;
bool exclusive;
bool atomic;
bool unlink_on_error;
const char *tmp_filename;
} file;
std::function<bool(Span<const uint8_t>)> func;
// StreamWriter deals with func destructor
U() {}
~U() {}
} u;
bool vt100;
} dest;
#if !defined(__wasm__)
std::mutex mutex;
#endif
StreamEncoder *encoder = nullptr;
int64_t raw_written = 0;
BlockAllocator str_alloc { Kibibytes(8) };
public:
StreamWriter() { Close(true); }
StreamWriter(HeapArray<uint8_t> *mem, const char *filename, unsigned int flags = 0,
CompressionType compression_type = CompressionType::None,
CompressionSpeed compression_speed = CompressionSpeed::Default)
: StreamWriter() { Open(mem, filename, flags, compression_type, compression_speed); }
StreamWriter(HeapArray<char> *mem, const char *filename, unsigned int flags = 0,
CompressionType compression_type = CompressionType::None,
CompressionSpeed compression_speed = CompressionSpeed::Default)
: StreamWriter() { Open(mem, filename, flags, compression_type, compression_speed); }
StreamWriter(int fd, const char *filename, unsigned int flags = 0,
CompressionType compression_type = CompressionType::None,
CompressionSpeed compression_speed = CompressionSpeed::Default)
: StreamWriter() { Open(fd, filename, flags, compression_type, compression_speed); }
StreamWriter(const char *filename, unsigned int flags = 0,
CompressionType compression_type = CompressionType::None,
CompressionSpeed compression_speed = CompressionSpeed::Default)
: StreamWriter() { Open(filename, flags, compression_type, compression_speed); }
StreamWriter(const std::function<bool(Span<const uint8_t>)> &func, const char *filename, unsigned int flags = 0,
CompressionType compression_type = CompressionType::None,
CompressionSpeed compression_speed = CompressionSpeed::Default)
: StreamWriter() { Open(func, filename, flags, compression_type, compression_speed); }
~StreamWriter() { Close(true); }
// Call before Open. Takes ownership and deletes the encoder at the end.
void SetEncoder(StreamEncoder *encoder);
bool Open(HeapArray<uint8_t> *mem, const char *filename, unsigned int flags = 0,
CompressionType compression_type = CompressionType::None,
CompressionSpeed compression_speed = CompressionSpeed::Default);
bool Open(HeapArray<char> *mem, const char *filename, unsigned int flags = 0,
CompressionType compression_type = CompressionType::None,
CompressionSpeed compression_speed = CompressionSpeed::Default)
{ return Open((HeapArray<uint8_t> *)mem, filename, flags, compression_type, compression_speed); }
bool Open(int fd, const char *filename, unsigned int flags = 0,
CompressionType compression_type = CompressionType::None,
CompressionSpeed compression_speed = CompressionSpeed::Default);
bool Open(const char *filename, unsigned int flags = 0,
CompressionType compression_type = CompressionType::None,
CompressionSpeed compression_speed = CompressionSpeed::Default);
bool Open(const std::function<bool(Span<const uint8_t>)> &func, const char *filename, unsigned int flags = 0,
CompressionType compression_type = CompressionType::None,
CompressionSpeed compression_speed = CompressionSpeed::Default);
bool Close() { return Close(false); }
// File-specific
bool Rewind();
// For compressed streams, Flush may not be complete and only Close() can finalize the file.
// Thread safe method
bool Flush();
const char *GetFileName() const { return filename; }
bool IsVt100() const { return dest.vt100; }
bool IsValid() const { return filename && !error; }
int GetDescriptor() const;
void SetDescriptorOwned(bool owned);
// Thread safe methods
bool Write(Span<const uint8_t> buf);
bool Write(Span<const char> buf) { return Write(buf.As<const uint8_t>()); }
bool Write(char buf) { return Write(MakeSpan(&buf, 1)); }
bool Write(const void *buf, Size len) { return Write(MakeSpan((const uint8_t *)buf, len)); }
int64_t GetRawWritten() const { return raw_written; }
private:
bool Close(bool implicit);
void InitFile(unsigned int flags);
bool FlushBuffer();
bool InitCompressor(CompressionType type, CompressionSpeed speed);
bool WriteRaw(Span<const uint8_t> buf);
friend class StreamEncoder;
};
static inline bool WriteFile(Span<const uint8_t> buf, const char *filename, unsigned int flags = 0)
{
StreamWriter st(filename, flags);
st.Write(buf);
return st.Close();
}
static inline bool WriteFile(Span<const char> buf, const char *filename, unsigned int flags = 0)
{
StreamWriter st(filename, flags);
st.Write(buf);
return st.Close();
}
class StreamEncoder {
protected:
StreamWriter *writer;
public:
StreamEncoder(StreamWriter *writer) : writer(writer) {}
virtual ~StreamEncoder() {}
virtual bool Write(Span<const uint8_t> buf) = 0;
virtual bool Finalize() = 0;
protected:
const char *GetFileName() const { return writer->filename; }
bool IsValid() const { return writer->IsValid(); }
bool WriteRaw(Span<const uint8_t> buf) { return writer->WriteRaw(buf); }
};
typedef StreamEncoder *CreateCompressorFunc(StreamWriter *writer, CompressionType type, CompressionSpeed speed);
class StreamCompressorHelper {
public:
StreamCompressorHelper(CompressionType type, CreateCompressorFunc *func);
};
#define K_REGISTER_COMPRESSOR(Type, Cls) \
static StreamEncoder *K_UNIQUE_NAME(CreateCompressor)(StreamWriter *writer, CompressionType type, CompressionSpeed speed) \
{ \
StreamEncoder *compressor = new Cls(writer, type, speed); \
return compressor; \
} \
static StreamCompressorHelper K_UNIQUE_NAME(CreateCompressorHelper)((Type), K_UNIQUE_NAME(CreateCompressor))
bool SpliceStream(StreamReader *reader, int64_t max_len, StreamWriter *writer, Span<uint8_t> buf,
FunctionRef<void(int64_t, int64_t)> progress = [](int64_t, int64_t) {});
template<Size S = 65535>
bool SpliceStream(StreamReader *reader, int64_t max_len, StreamWriter *writer,
FunctionRef<void(int64_t, int64_t)> progress = [](int64_t, int64_t) {})
{
static_assert(S >= Kibibytes(2) && S <= Kibibytes(96));
// The default size happens to be the maximum chunk size (0xFFFF) in our HTTP chunk
// transfer implementation. musl now defaults to 128k stack size, and we ask
// for 1 MiB with PT_GNU_STACK (using linker flag -z stack-size) anyway.
uint8_t buf[S];
return SpliceStream(reader, max_len, writer, buf, progress);
}
bool IsCompressorAvailable(CompressionType compression_type);
bool IsDecompressorAvailable(CompressionType compression_type);
// ------------------------------------------------------------------------
// INI
// ------------------------------------------------------------------------
struct IniProperty {
Span<const char> section;
Span<const char> key;
Span<const char> value;
};
class IniParser {
K_DELETE_COPY(IniParser)
HeapArray<char> current_section;
enum class LineType {
Section,
KeyValue,
Exit
};
LineReader reader;
bool eof = false;
bool error = false;
public:
IniParser(StreamReader *st) : reader(st) {}
const char *GetFileName() const { return reader.GetFileName(); }
bool IsValid() const { return !error; }
bool IsEOF() const { return eof; }
bool Next(IniProperty *out_prop);
bool NextInSection(IniProperty *out_prop);
void PushLogFilter() { reader.PushLogFilter(); }
private:
LineType FindNextLine(IniProperty *out_prop);
};
// ------------------------------------------------------------------------
// Assets
// ------------------------------------------------------------------------
// Keep in sync with version in packer.cc
struct AssetInfo {
const char *name;
CompressionType compression_type;
Span<const uint8_t> data;
K_HASHTABLE_HANDLER(AssetInfo, name);
};
#if defined(FELIX_HOT_ASSETS)
bool ReloadAssets();
Span<const AssetInfo> GetEmbedAssets();
const AssetInfo *FindEmbedAsset(const char *name);
#else
// Reserved for internal use
void InitEmbedMap(Span<const AssetInfo> assets);
extern "C" const Span<const AssetInfo> EmbedAssets;
extern HashTable<const char *, const AssetInfo *> EmbedAssetsMap;
// By definining functions here (with static inline), we don't nee EmbedAssets
// to exist unless these functions are called. It also allows the compiler to remove
// calls to ReloadAssets in non-debug builds (LTO or not).
static inline bool ReloadAssets()
{
return false;
}
static inline Span<const AssetInfo> GetEmbedAssets()
{
return EmbedAssets;
}
static inline const AssetInfo *FindEmbedAsset(const char *name)
{
InitEmbedMap(EmbedAssets);
return EmbedAssetsMap.FindValue(name, nullptr);
}
#endif
// These functions won't win any beauty or speed contest but whatever
bool PatchFile(StreamReader *reader, StreamWriter *writer,
FunctionRef<void(Span<const char>, StreamWriter *)> func);
bool PatchFile(Span<const uint8_t> data, StreamWriter *writer,
FunctionRef<void(Span<const char>, StreamWriter *)> func);
bool PatchFile(const AssetInfo &asset, StreamWriter *writer,
FunctionRef<void(Span<const char>, StreamWriter *)> func);
Span<const uint8_t> PatchFile(Span<const uint8_t> data, Allocator *alloc,
FunctionRef<void(Span<const char> key, StreamWriter *)> func);
Span<const uint8_t> PatchFile(const AssetInfo &asset, Allocator *alloc,
FunctionRef<void(Span<const char> key, StreamWriter *)> func);
Span<const char> PatchFile(Span<const char> data, Allocator *alloc,
FunctionRef<void(Span<const char> key, StreamWriter *)> func);
// ------------------------------------------------------------------------
// Translations
// ------------------------------------------------------------------------
struct TranslationTable {
struct Pair {
const char *key;
const char *value;
};
const char *language;
Span<Pair> messages;
K_HASHTABLE_HANDLER(TranslationTable, language);
};
extern "C" const Span<const TranslationTable> TranslationTables;
void InitLocales(Span<const TranslationTable> tables, const char *default_lang);
// Resets the locale to the process default if lang is NULL or is unknown
void ChangeThreadLocale(const char *name);
const char *GetThreadLocale();
// ------------------------------------------------------------------------
// Options
// ------------------------------------------------------------------------
struct OptionDesc {
const char *name;
const char *help;
};
enum class OptionMode {
Rotate,
Skip,
Stop
};
enum class OptionType {
NoValue,
Value,
OptionalValue
};
class OptionParser {
K_DELETE_COPY(OptionParser)
Span<const char *> args;
OptionMode mode;
Size pos = 0;
Size limit;
Size smallopt_offset = 0;
char buf[80];
bool test_failed = false;
public:
const char *current_option = nullptr;
const char *current_value = nullptr;
OptionParser(Span<const char *> args, OptionMode mode = OptionMode::Rotate)
: args(args), mode(mode), limit(args.len) {}
OptionParser(int argc, char **argv, OptionMode mode = OptionMode::Rotate)
: args((const char **)argv, argc), mode(mode), pos(1), limit(args.len) {}
Size GetPosition() const { return pos; }
const char *Next();
const char *ConsumeValue();
const char *ConsumeNonOption();
void ConsumeNonOptions(HeapArray<const char *> *non_options);
Span<const char *> GetRemainingArguments() const { return args.Take(pos, args.len - pos); }
bool Test(const char *test1, const char *test2, OptionType type = OptionType::NoValue);
bool Test(const char *test1, OptionType type = OptionType::NoValue)
{ return Test(test1, nullptr, type); }
bool TestHasFailed() const { return test_failed; }
void LogUnknownError() const;
void LogUnusedArguments() const;
};
template <typename T>
bool OptionToEnum(Span<const char *const> options, Span<const char> str, T *out_value)
{
static_assert(std::is_enum<T>::value);
for (Size i = 0; i < options.len; i++) {
const char *opt = options[i];
if (TestStr(opt, str)) {
*out_value = (T)i;
return true;
}
}
return false;
}
template <typename T>
bool OptionToEnum(Span<const OptionDesc> options, Span<const char> str, T *out_value)
{
static_assert(std::is_enum<T>::value);
for (Size i = 0; i < options.len; i++) {
const OptionDesc &desc = options[i];
if (TestStr(desc.name, str)) {
*out_value = (T)i;
return true;
}
}
return false;
}
template <typename T>
bool OptionToEnumI(Span<const char *const> options, Span<const char> str, T *out_value)
{
static_assert(std::is_enum<T>::value);
for (Size i = 0; i < options.len; i++) {
const char *opt = options[i];
if (TestStrI(opt, str)) {
*out_value = (T)i;
return true;
}
}
return false;
}
template <typename T>
bool OptionToEnumI(Span<const OptionDesc> options, Span<const char> str, T *out_value)
{
static_assert(std::is_enum<T>::value);
for (Size i = 0; i < options.len; i++) {
const OptionDesc &desc = options[i];
if (TestStrI(desc.name, str)) {
*out_value = (T)i;
return true;
}
}
return false;
}
template <typename T>
bool OptionToFlag(Span<const char *const> options, Span<const char> str, T *out_flags, bool enable = true)
{
for (Size i = 0; i < options.len; i++) {
const char *opt = options[i];
if (TestStr(opt, str)) {
*out_flags = ApplyMask(*out_flags, 1u << i, enable);
return true;
}
}
return false;
}
template <typename T>
bool OptionToFlag(Span<const OptionDesc> options, Span<const char> str, T *out_flags, bool enable = true)
{
for (Size i = 0; i < options.len; i++) {
const OptionDesc &desc = options[i];
if (TestStr(desc.name, str)) {
*out_flags = ApplyMask(*out_flags, 1u << i, enable);
return true;
}
}
return false;
}
template <typename T>
bool OptionToFlagI(Span<const char *const> options, Span<const char> str, T *out_flags, bool enable = true)
{
for (Size i = 0; i < options.len; i++) {
const char *opt = options[i];
if (TestStrI(opt, str)) {
*out_flags = ApplyMask(*out_flags, 1u << i, enable);
return true;
}
}
return false;
}
template <typename T>
bool OptionToFlagI(Span<const OptionDesc> options, Span<const char> str, T *out_flags, bool enable = true)
{
for (Size i = 0; i < options.len; i++) {
const OptionDesc &desc = options[i];
if (TestStrI(desc.name, str)) {
*out_flags = ApplyMask(*out_flags, 1u << i, enable);
return true;
}
}
return false;
}
// ------------------------------------------------------------------------
// Console prompter (simplified readline)
// ------------------------------------------------------------------------
struct PromptChoice {
const char *str;
char c;
};
enum class CompleteResult {
Success,
TooMany,
Error
};
struct CompleteChoice {
const char *name;
const char *value;
};
typedef CompleteResult CompleteFunc(Span<const char> value, Allocator *alloc, HeapArray<CompleteChoice> *out_choices);
class ConsolePrompter {
int prompt_columns = 0;
HeapArray<HeapArray<char>> entries;
Size entry_idx = 0;
Size str_offset = 0;
int columns = 0;
int rows = 0;
int rows_with_extra = 0;
int x = 0;
int y = 0;
const char *fake_input = "";
#if defined(_WIN32)
uint32_t surrogate_buf = 0;
#endif
public:
const char *prompt = ">>>";
const char *mask = nullptr;
std::function<CompleteFunc> complete = {};
HeapArray<char> str;
ConsolePrompter();
bool Read(Span<const char> *out_str = nullptr);
Size ReadEnum(Span<const PromptChoice> choices, Size value = 0);
void Commit();
private:
bool ReadRaw(Span<const char> *out_str);
bool ReadBuffered(Span<const char> *out_str);
Size ReadRawEnum(Span<const PromptChoice> choices, Size value);
Size ReadBufferedEnum(Span<const PromptChoice> choices);
void ChangeEntry(Size new_idx);
Size SkipForward(Size offset, Size count);
Size SkipBackward(Size offset, Size count);
Size FindForward(Size offset, const char *chars);
Size FindBackward(Size offset, const char *chars);
void Delete(Size start, Size end);
void FormatChoices(Span<const PromptChoice> choices, Size value);
void RenderRaw();
void RenderBuffered();
Vec2<int> GetConsoleSize();
int32_t ReadChar();
void EnsureNulTermination();
};
const char *Prompt(const char *prompt, const char *default_value, const char *mask, Allocator *alloc);
static inline const char *Prompt(const char *prompt, Allocator *alloc)
{ return Prompt(prompt, nullptr, nullptr, alloc); }
Size PromptEnum(const char *prompt, Span<const PromptChoice> choices, Size value = 0);
Size PromptEnum(const char *prompt, Span<const char *const> strings, Size value = 0);
// Returns -1 if cancelled, otherwise it's 1 for Yes and or 0 for No
int PromptYN(const char *prompt);
const char *PromptPath(const char *prompt, const char *default_path, Span<const char> root_directory, Allocator *alloc);
static inline const char *PromptPath(const char *prompt, Allocator *alloc)
{ return PromptPath(prompt, nullptr, GetWorkingDirectory(), alloc); }
// ------------------------------------------------------------------------
// Mime types
// ------------------------------------------------------------------------
const char *GetMimeType(Span<const char> extension, const char *default_type = "application/octet-stream");
bool CanCompressFile(const char *filename);
// ------------------------------------------------------------------------
// Unicode
// ------------------------------------------------------------------------
static inline int CountUtf8Bytes(char c)
{
int ones = CountLeadingZeros((uint32_t)~c << 24);
return std::min(std::max(ones, 1), 4);
}
static constexpr inline Size DecodeUtf8(const char *str, int32_t *out_c)
{
K_ASSERT(str[0]);
#define BYTE(Idx) ((uint8_t)str[Idx])
if (BYTE(0) < 0x80) {
*out_c = BYTE(0);
return 1;
} else if (BYTE(0) - 0xC2 > 0xF4 - 0xC2) [[unlikely]] {
return 0;
} else if (BYTE(1)) [[likely]] {
if (BYTE(0) < 0xE0 && (BYTE(1) & 0xC0) == 0x80) {
*out_c = ((BYTE(0) & 0x1F) << 6) | (BYTE(1) & 0x3F);
return 2;
} else if (BYTE(2)) [[likely]] {
if (BYTE(0) < 0xF0 &&
(BYTE(1) & 0xC0) == 0x80 &&
(BYTE(2) & 0xC0) == 0x80) {
*out_c = ((BYTE(0) & 0xF) << 12) | ((BYTE(1) & 0x3F) << 6) | (BYTE(2) & 0x3F);
return 3;
} else if (BYTE(3)) [[likely]] {
if ((BYTE(1) & 0xC0) == 0x80 &&
(BYTE(2) & 0xC0) == 0x80 &&
(BYTE(3) & 0xC0) == 0x80) {
*out_c = ((BYTE(0) & 0x7) << 18) | ((BYTE(1) & 0x3F) << 12) | ((BYTE(2) & 0x3F) << 6) | (BYTE(3) & 0x3F);
return 4;
}
}
}
}
#undef BYTE
return 0;
}
static constexpr inline Size DecodeUtf8(Span<const char> str, Size offset, int32_t *out_c)
{
K_ASSERT(offset < str.len);
str = str.Take(offset, str.len - offset);
#define BYTE(Idx) ((uint8_t)str[Idx])
if (BYTE(0) < 0x80) {
*out_c = BYTE(0);
return 1;
} else if (BYTE(0) - 0xC2 > 0xF4 - 0xC2) [[unlikely]] {
return 0;
} else if (BYTE(0) < 0xE0 && str.len >= 2 && (BYTE(1) & 0xC0) == 0x80) {
*out_c = ((BYTE(0) & 0x1F) << 6) | (BYTE(1) & 0x3F);
return 2;
} else if (BYTE(0) < 0xF0 && str.len >= 3 && (BYTE(1) & 0xC0) == 0x80 &&
(BYTE(2) & 0xC0) == 0x80) {
*out_c = ((BYTE(0) & 0xF) << 12) | ((BYTE(1) & 0x3F) << 6) | (BYTE(2) & 0x3F);
return 3;
} else if (str.len >= 4 && (BYTE(1) & 0xC0) == 0x80 &&
(BYTE(2) & 0xC0) == 0x80 &&
(BYTE(3) & 0xC0) == 0x80) {
*out_c = ((BYTE(0) & 0x7) << 18) | ((BYTE(1) & 0x3F) << 12) | ((BYTE(2) & 0x3F) << 6) | (BYTE(3) & 0x3F);
return 4;
} else {
return 0;
}
#undef BYTE
}
static constexpr inline int32_t DecodeUtf8(const char *str)
{
int32_t uc = -1;
DecodeUtf8(str, &uc);
return uc;
}
static inline Size EncodeUtf8(int32_t c, char out_buf[4])
{
if (c < 0x80) {
out_buf[0] = (char)c;
return 1;
} else if (c < 0x800) {
out_buf[0] = (char)(0xC0 | (c >> 6));
out_buf[1] = (char)(0x80 | (c & 0x3F));
return 2;
} else if (c >= 0xD800 && c < 0xE000) {
return 0;
} else if (c < 0x10000) {
out_buf[0] = (char)(0xE0 | (c >> 12));
out_buf[1] = (char)(0x80 | ((c >> 6) & 0x3F));
out_buf[2] = (char)(0x80 | (c & 0x3F));
return 3;
} else if (c < 0x110000) {
out_buf[0] = (char)(0xF0 | (c >> 18));
out_buf[1] = (char)(0x80 | ((c >> 12) & 0x3F));
out_buf[2] = (char)(0x80 | ((c >> 6) & 0x3F));
out_buf[3] = (char)(0x80 | (c & 0x3F));
return 4;
} else {
return 0;
}
}
bool IsValidUtf8(Span<const char> str);
int ComputeUnicodeWidth(Span<const char> str);
bool IsXidStart(int32_t uc);
bool IsXidContinue(int32_t uc);
// ------------------------------------------------------------------------
// CRC
// ------------------------------------------------------------------------
uint32_t CRC32(uint32_t state, Span<const uint8_t> buf);
uint32_t CRC32C(uint32_t state, Span<const uint8_t> buf);
uint64_t CRC64xz(uint64_t state, Span<const uint8_t> buf);
uint64_t CRC64nvme(uint64_t state, Span<const uint8_t> buf);
}