Last active
January 8, 2026 07:05
-
-
Save namandixit/22d61e7e416f7e4637730d3e5ff2a479 to your computer and use it in GitHub Desktop.
Personal C Standard Library
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Creator: Naman Dixit | |
| * Notice: © Copyright 2024 Naman Dixit | |
| * License: BSD Zero Clause License | |
| * SPDX: 0BSD (https://spdx.org/licenses/0BSD.html) | |
| */ | |
| #if !defined(STD_H_INCLUDE_GUARD) | |
| /* Compiler **************************************************************************/ | |
| #if defined(_MSC_VER) | |
| # if defined(__clang__) | |
| # define ENV_COMPILER_CLANG | |
| # define ENV_COMPILER_CLANG_WITH_MSVC | |
| # else | |
| # define ENV_COMPILER_MSVC | |
| # endif | |
| #elif defined (__GNUC__) | |
| # if defined(__clang__) | |
| # define ENV_COMPILER_CLANG | |
| # define ENV_COMPILER_CLANG_WITH_GCC | |
| # else | |
| # define ENV_COMPILER_GCC | |
| # endif | |
| #elif defined(__clang__) | |
| # define ENV_COMPILER_CLANG | |
| #else | |
| # error Compiler not supported | |
| #endif | |
| /* Operating System ******************************************************************/ | |
| #if defined(_WIN32) | |
| # define ENV_OS_WINDOWS | |
| #elif defined(__linux__) | |
| # define ENV_OS_LINUX | |
| #else | |
| # error Operating system not supported | |
| #endif | |
| /* CPU Architecture ******************************************************************/ | |
| #if defined(ENV_COMPILER_MSVC) || defined(ENV_COMPILER_CLANG_WITH_MSVC) | |
| # if defined(_M_IX86) | |
| # define ENV_ARCH_X86 | |
| # elif defined(_M_X64) | |
| # define ENV_ARCH_X64 | |
| # endif | |
| #elif defined(ENV_COMPILER_CLANG) || defined(ENV_COMPILER_GCC) | |
| # if defined(__i386__) | |
| # define ENV_ARCH_X86 | |
| # elif defined(__x86_64__) | |
| # define ENV_ARCH_X64 | |
| # endif | |
| #endif | |
| #if !defined(ENV_ARCH_X64) // && !defined(ENV_ARCH_X86) | |
| # error Architecture not supported | |
| #endif | |
| /* Word Bit-width ********************************************************************/ | |
| #if defined(ENV_ARCH_X86) | |
| # define ENV_BITWIDTH_32 | |
| # error "Bitwidth not supported" | |
| #elif defined(ENV_ARCH_X64) | |
| # define ENV_BITWIDTH_64 | |
| #else | |
| # error "Bitwidth not supported" | |
| #endif | |
| /* Programming Language **************************************************************/ | |
| #if defined(ENV_COMPILER_MSVC) | |
| # if defined(__cplusplus) | |
| # if __cplusplus == 199711L | |
| # define ENV_LANG_CXX 1998 | |
| # elif __cplusplus == 201402L | |
| # define ENV_LANG_CXX 2014 | |
| # elif __cplusplus == 201703L | |
| # define ENV_LANG_CXX 2017 | |
| # elif __cplusplus == 202002L | |
| # define ENV_LANG_CXX 2020 | |
| # else | |
| # define ENV_LANG_CXX 2017 // A future C++, assume the last best supported | |
| # endif | |
| # elif defined(__STDC_VERSION__) | |
| # if (__STDC_VERSION__ == 201112L) || (__STDC_VERSION__ == 201710L) | |
| # define ENV_LANG_C 2011 | |
| # elif (__STDC_VERSION__ == 202311L) || (__STDC_VERSION__ == 202312L) // 202312L is with /std:clatest before official release of C23 support | |
| # define ENV_LANG_C 2023 | |
| # else | |
| # define ENV_LANG_C 2011 // Earliest C version for which MSVC supports __STDC_VERSION__ | |
| # endif | |
| # elif defined(__STDC__) // All Microsoft extensions are off [/Za (Disable Language Extensions), similar to pedantic] | |
| # define ENV_LANG_C 1989 | |
| # else // /Za (Disable Language Extensions) is not provided, but MS never properly supported C99. | |
| # define ENV_LANG_C 1989 | |
| # endif | |
| #elif defined(ENV_COMPILER_CLANG) || defined(ENV_COMPILER_GCC) | |
| # if defined(__cplusplus) | |
| # if defined(__OBJC__) | |
| # define ENV_LANG_OBJCPP 1 | |
| # endif | |
| # if __cplusplus == 199711L | |
| # define ENV_LANG_CXX 1998 | |
| # elif __cplusplus == 201103L | |
| # define ENV_LANG_CXX 2011 | |
| # elif __cplusplus == 201402L | |
| # define ENV_LANG_CXX 2014 | |
| # elif __cplusplus == 201703L | |
| # define ENV_LANG_CXX 2017 | |
| # elif __cplusplus == 202002L | |
| # define ENV_LANG_CXX 2020 | |
| # elif __cplusplus == 202302L | |
| # define ENV_LANG_CXX 2023 | |
| # else | |
| # define ENV_LANG_CXX 2020 // A future C++, assume the last best supported | |
| # endif | |
| # elif defined(__STDC_VERSION__) // Using C Language >= 1994 (1989) | |
| # if defined(__OBJC__) | |
| # define ENV_LANG_OBJC 1 | |
| # endif | |
| # if (__STDC_VERSION__ == 199409L) | |
| # define ENV_LANG_C 1989 | |
| # elif (__STDC_VERSION__ == 199901L) | |
| # define ENV_LANG_C 1999 | |
| # elif (__STDC_VERSION__ == 201112L) || (__STDC_VERSION__ == 201710L) | |
| # define ENV_LANG_C 2011 | |
| # elif (__STDC_VERSION__ == 202311L) | |
| # define ENV_LANG_C 2023 | |
| # else | |
| # define ENV_LANG_C 1999 // At least C99 (__STDC_VERSION__ is defined, C94 is too old to fallback on) | |
| # endif | |
| # elif defined(__STDC__) && !defined(__STDC_VERSION__) | |
| # if defined(__OBJC__) | |
| # define ENV_LANG_OBJC 1 | |
| # endif | |
| # define ENV_LANG_C 1989 // Since C89 doesn't require definition of __STDC_VERSION__ | |
| # endif | |
| #endif | |
| #if !defined(ENV_LANG_C) && !defined(ENV_LANG_CXX) | |
| # error Language not supported | |
| #endif | |
| /* Endianness ************************************************************************/ | |
| #if defined(ENV_OS_WINDOWS) | |
| # if defined(ENV_ARCH_X86) || defined(ENV_ARCH_X64) | |
| # define ENV_ENDIAN_LITTLE | |
| # else | |
| # error Could not determine endianness on Windows | |
| # endif | |
| #elif defined(ENV_OS_LINUX) | |
| # include <endian.h> | |
| # if __BYTE_ORDER == __LITTLE_ENDIAN | |
| # define ENV_ENDIAN_LITTLE | |
| # elif __BYTE_ORDER == __BIG_ENDIAN | |
| # define ENV_ENDIAN_BIG | |
| # else | |
| # error Could not determine endianness on Linux | |
| # endif | |
| #else | |
| # error Can not determine endianness, unknown environment | |
| #endif | |
| /* Pragmas ***************************************************************************/ | |
| // Unique symbol generator | |
| #define macro_gensym2_(prefix, suffix) macro_gensym_cat_(prefix, suffix) | |
| #define macro_gensym_cat_(prefix, suffix) prefix ## suffix | |
| //#define macro_gensym_uniq(prefix) macro_gensym2_(prefix, __COUNTER__) // Commented since I am going to use __COUNTER__ for profile events | |
| #define macro_gensym_line(prefix) macro_gensym2_(prefix, __LINE__) | |
| #define macro_gensym_func(prefix) macro_gensym2_(prefix, __func__) | |
| #define macro_gensym_file(prefix) macro_gensym2_(prefix, __FILE__) | |
| //#define eat_semicolon() static_assert(1, "") /* Doesn't work before C23 */ | |
| #define eat_semicolon() struct macro_gensym_line(dummy_struct_designed_for_eating_semicolon) | |
| /* Less noisy pragmas */ | |
| #if defined(ENV_COMPILER_CLANG) | |
| # define pragma_msvc(P) eat_semicolon() | |
| # define pragma_clang(P) _Pragma(P) eat_semicolon() | |
| # define pragma_gcc(P) eat_semicolon() | |
| #elif defined(ENV_COMPILER_GCC) | |
| # define pragma_msvc(P) eat_semicolon() | |
| # define pragma_clang(P) eat_semicolon() | |
| # define pragma_gcc(P) _Pragma(P) eat_semicolon() | |
| #elif defined(ENV_COMPILER_MSVC) | |
| # define pragma_msvc(P) _Pragma(P) eat_semicolon() | |
| # define pragma_clang(P) eat_semicolon() | |
| # define pragma_gcc(P) eat_semicolon() | |
| #endif | |
| /* Compiler-specific tokens */ | |
| #if defined(ENV_COMPILER_CLANG) | |
| # define with_msvc(P) | |
| # define with_clang(P) P | |
| # define with_gcc(P) | |
| #elif defined(ENV_COMPILER_GCC) | |
| # define with_msvc(P) | |
| # define with_clang(P) | |
| # define with_gcc(P) P | |
| #elif defined(ENV_COMPILER_MSVC) | |
| # define with_msvc(P) P | |
| # define with_clang(P) | |
| # define with_gcc(P) | |
| #endif | |
| #define with_clang_gcc(P) with_clang(P) with_gcc(P) | |
| #define with_clang_msvc(P) with_clang(P) with_msvc(P) | |
| /* Warnings **************************************************************************/ | |
| pragma_clang("clang diagnostic push"); | |
| #if defined(ENV_LANG_C) | |
| pragma_clang("clang diagnostic ignored \"-Wdeclaration-after-statement\""); | |
| pragma_clang("clang diagnostic ignored \"-Wpre-c23-compat\""); | |
| pragma_clang("clang diagnostic ignored \"-Wcovered-switch-default\""); | |
| pragma_clang("clang diagnostic ignored \"-Wunsafe-buffer-usage\""); | |
| #elif defined(ENV_LANG_CXX) | |
| pragma_clang("clang diagnostic ignored \"-Wc++98-compat-pedantic\""); | |
| pragma_clang("clang diagnostic ignored \"-Wcovered-switch-default\""); | |
| pragma_clang("clang diagnostic ignored \"-Wc99-extensions\""); | |
| pragma_clang("clang diagnostic ignored \"-Wgnu-anonymous-struct\""); | |
| pragma_clang("clang diagnostic ignored \"-Wnested-anon-types\""); | |
| pragma_clang("clang diagnostic ignored \"-Wmissing-field-initializers\""); | |
| pragma_clang("clang diagnostic ignored \"-Wmicrosoft-goto\""); | |
| pragma_clang("clang diagnostic ignored \"-Wunsafe-buffer-usage\""); | |
| #endif | |
| pragma_msvc("warning ( push )"); | |
| pragma_msvc("warning ( disable: 4200 )"); // nonstandard extension used: zero-sized array in struct/union | |
| #if defined(ENV_LANG_C) | |
| pragma_msvc("warning ( disable: 6387 )"); // Analyze: INVALID_PARAM_VALUE_1 | |
| #elif defined(ENV_LANG_CXX) | |
| pragma_msvc("warning ( disable: 4127 )"); // conditional expression is constant | |
| pragma_msvc("warning ( disable: 4201 )"); // nonstandard extension used: nameless struct/union | |
| pragma_msvc("warning ( disable: 6386 )"); // Analyze: WRITE_OVERRUN | |
| #endif | |
| /* Constants *************************************************************************/ | |
| #define KiB(x) ( (x) * 1024ULL) | |
| #define MiB(x) (KiB(x) * 1024ULL) | |
| #define GiB(x) (MiB(x) * 1024ULL) | |
| #define TiB(x) (GiB(x) * 1024ULL) | |
| #define HUNDRED 100L | |
| #define THOUSAND 1000L | |
| #define MILLION 1000000L | |
| #define BILLION 1000000000L | |
| /* Attributes ************************************************************************/ | |
| #if defined(ENV_LANG_C) && (ENV_LANG_C < 2023) | |
| # define nullptr NULL | |
| #endif | |
| #if (defined(ENV_LANG_C) && ENV_LANG_C == 2011) | |
| # define static_assert(...) _Static_assert(__VA_ARGS__) | |
| #elif (defined(ENV_LANG_C) && ENV_LANG_C < 2011) | |
| # if defined(ENV_COMPILER_GCC) || defined(ENV_COMPILER_CLANG) | |
| # define static_assert__HELPER(expr, msg) \ | |
| (!!sizeof (struct { unsigned int macro_gensym_line(STATIC_ASSERTION__): (expr) ? 1 : -1; })) | |
| # define static_assert(expr, msg) \ | |
| extern int (*assert_function__(void)) [static_assert__HELPER(expr, msg)] | |
| # elif defined(ENV_COMPILER_MSVC) | |
| # define static_assert(expr, msg) \ | |
| extern char macro_gensym_line(STATIC_ASSERTION__1__)[1]; extern char macro_gensym_line(STATIC_ASSERTION__2__)[(expr)?1:2] | |
| # endif | |
| #endif | |
| #define global_variable static | |
| #define global_immutable static const | |
| #define persistent_variable static | |
| #define persistent_immutable static const | |
| #define internal_function static | |
| #define header_function static inline | |
| #if defined(ENV_LANG_C) && defined(ENV_COMPILER_MSVC) | |
| # define nullptr NULL | |
| #endif | |
| #if defined(ENV_COMPILER_MSVC) || defined(ENV_COMPILER_CLANG_WITH_MSVC) | |
| # if defined(BUILD_DLL) | |
| # define exported_function __declspec(dllexport) | |
| # else | |
| # define exported_function __declspec(dllimport) | |
| # endif | |
| #elif defined(ENV_COMPILER_GCC) || defined(ENV_COMPILER_CLANG) | |
| # define exported_function __attribute__((visibility("default"))) | |
| #endif | |
| #if defined(ENV_COMPILER_MSVC) || defined(ENV_COMPILER_CLANG_WITH_MSVC) | |
| # define exported_data __declspec(dllexport) | |
| #elif defined(ENV_COMPILER_GCC) || defined(ENV_COMPILER_CLANG) | |
| # define exported_data __attribute__((visibility("default"))) | |
| #endif | |
| #if defined(ENV_COMPILER_GCC) || defined(ENV_COMPILER_CLANG) | |
| #define packed_data(...) __VA_ARGS__ __attribute__((__packed__)) | |
| #elif defined(ENV_COMPILER_MSVC) | |
| #define packed_data(...) __pragma( pack(push, 1) ) __VA_ARGS__ __pragma( pack(pop)) | |
| #endif | |
| #if defined(__has_c_attribute) | |
| # if __has_c_attribute(fallthrough) | |
| # define switch_fallthrough [[fallthrough]] | |
| # endif | |
| #elif defined(__cplusplus) && defined(__has_cpp_attribute) | |
| # if __has_cpp_attribute(fallthrough) | |
| # define switch_fallthrough [[fallthrough]] | |
| # endif | |
| #endif | |
| #if !defined(switch_fallthrough) | |
| # if defined(ENV_COMPILER_GCC) && __GNUC__ >= 7 | |
| # define switch_fallthrough __attribute__ ((fallthrough)) | |
| # elif defined(ENV_COMPILER_CLANG) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 9)) | |
| # define switch_fallthrough __attribute__ ((fallthrough)) | |
| # else | |
| # define switch_fallthrough | |
| # endif | |
| #endif | |
| /* Macros ****************************************************************************/ | |
| #define elemin(array) (sizeof(array)/sizeof((array)[0])) | |
| // Type-casts | |
| #if defined(ENV_LANG_C) | |
| # define cast_bit(T, m) (*((T*)(&(m)))) /* Reading float as int, float has to be stored in a variable */ | |
| # define cast_ptr(T, v) ((T)(v)) /* Casting float* to int* */ | |
| # define cast_val(T, v) ((T)(v)) /* Convering float to int */ | |
| # define cast_const(T, v) ((T)(v)) /* Removing const */ | |
| #elif defined(ENV_LANG_CXX) | |
| template<class To, class From> | |
| To cast_bit_func(const From& src) noexcept { | |
| static_assert(sizeof(To) == sizeof(From)); | |
| union { From f; To t; } u; | |
| u.f = src; | |
| return u.t; // implementation-defined/UB by strict standard, but works on many compilers | |
| } | |
| # define cast_bit(T, m) (cast_bit_func<T>(m)) /* Reading float as int, float has to be stored in a variable */ | |
| # define cast_ptr(T, v) (reinterpret_cast<T>(v)) /* Casting float* to int* */ | |
| # define cast_val(T, v) (static_cast<T>(v)) /* Convering float to int */ | |
| # define cast_const(T, v) (const_cast<T>(v)) /* Removing const */ | |
| #endif | |
| #if defined(ENV_LANG_CXX) | |
| template<typename T> struct typeof_remove_reference { using type = T; }; | |
| template<typename T> struct typeof_remove_reference<T&> { using type = T; }; | |
| # if defined(ENV_LANG_CXX) && (ENV_LANG_CXX >= 2011) | |
| template<typename T> struct typeof_remove_reference<T&&> { using type = T; }; | |
| # endif | |
| template<typename T> | |
| using typeof_remove_reference_t = typename typeof_remove_reference<T>::type; | |
| # define typeof(...) typeof_remove_reference_t<decltype(__VA_ARGS__)> | |
| #endif | |
| // Compound Literal | |
| #if defined(ENV_LANG_C) | |
| # define compound_init(T, ...) (T) __VA_ARGS__ | |
| #elif defined(ENV_LANG_CXX) | |
| # define compound_init(T, ...) T __VA_ARGS__ | |
| #endif | |
| #define containerof(ptr, type, member) (cast_val(type*, cast_val(void*, (cast_ptr(Byte*, ptr)) - offsetof(type, member)))) | |
| #define unused_variable(var) (void)var | |
| // Likely/unlikely for better branch prediction | |
| #if defined(ENV_COMPILER_MSVC) | |
| # define likely(x) (x) | |
| # define unlikely(x) (x) | |
| #elif defined(ENV_COMPILER_CLANG) || defined(ENV_COMPILER_GCC) | |
| # define likely(x) __builtin_expect(!!(x), 1) | |
| # define unlikely(x) __builtin_expect(!!(x), 0) | |
| #endif | |
| // Defer-like macros | |
| #define macro_entail(...) \ | |
| goto macro_gensym_line(jump_to_else); \ | |
| \ | |
| for (;;) \ | |
| if (true) { \ | |
| __VA_ARGS__; \ | |
| break; \ | |
| } else macro_gensym_line(jump_to_else): | |
| #define macro_envelop(cloak_arg_pre_action, cloak_arg_post_action) \ | |
| cloak_arg_pre_action; \ | |
| goto macro_gensym_line(jump_to_else); \ | |
| \ | |
| for (;;) \ | |
| if (true) { \ | |
| cloak_arg_post_action; \ | |
| break; \ | |
| } else macro_gensym_line(jump_to_else): | |
| /* Error handling macros | |
| * | |
| * Sample Code: | |
| * | |
| * T *ta = nullptr; *tb = nullptr; | |
| * err_try(doing_stuff) { | |
| * ta = tCreate(); | |
| * if (!ta) err_throw(doing_stuff); | |
| * tb = tCreate(); | |
| * if (!tb) err_throw(doing_stuff); | |
| * stuffDo(ta, tb); | |
| * } err_catch(doing_stuff) { | |
| * if (ta) tDelete(ta); | |
| * if (tb) tDelete(tb); | |
| * } | |
| */ | |
| #define err_try(fail) Bool fail = false; do | |
| #define err_throw(fail) {fail=true;break;}(void)0 | |
| #define err_catch(fail) while (0); if (fail) | |
| #if defined(ENV_LANG_C) && ENV_LANG_C >= 1999 | |
| # define static_array_size(size) with_clang_gcc(static size) | |
| #else | |
| # define static_array_size(size) (size) | |
| #endif | |
| /* Disable all warnings in headers */ | |
| #define disable_warnings() \ | |
| pragma_clang("clang diagnostic push"); \ | |
| pragma_clang("clang diagnostic ignored \"-Weverything\""); \ | |
| pragma_msvc("warning ( push , 0 )") | |
| #define enable_warnings() \ | |
| pragma_clang("clang diagnostic pop"); \ | |
| pragma_msvc("warning ( pop )") | |
| #define allow_padding_in_type(...) \ | |
| pragma_msvc("warning ( push )"); \ | |
| pragma_msvc("warning ( disable: 4820 )"); \ | |
| pragma_clang("clang diagnostic push"); \ | |
| pragma_clang("clang diagnostic ignored \"-Wpadded\""); \ | |
| __VA_ARGS__ \ | |
| pragma_clang("clang diagnostic pop"); \ | |
| pragma_msvc("warning ( pop )"); | |
| /* Stringify contents of a macro */ | |
| #define stringify(a) stringify_helper_(a) | |
| #define stringify_helper_(a) #a | |
| /* Header Files **********************************************************************/ | |
| // Function-less C89 headers | |
| #include <float.h> | |
| #include <limits.h> | |
| #include <stdarg.h> | |
| #include <stddef.h> | |
| // Function-less C99 headers | |
| #if (defined(ENV_LANG_C) && (ENV_LANG_C >= 1999)) || (defined(ENV_LANG_CXX) && (ENV_LANG_CXX >= 2011)) | |
| # include <inttypes.h> | |
| # include <stdbool.h> | |
| # include <stdint.h> | |
| #endif | |
| // Function-less C11 headers | |
| // stdalign | |
| #if defined(ENV_COMPILER_GCC) || defined(ENV_COMPILER_CLANG) | |
| # if (defined(ENV_LANG_C) && (ENV_LANG_C >= 2011)) || (defined(ENV_LANG_CXX) && (ENV_LANG_CXX >= 2011)) | |
| # include <stdalign.h> | |
| # endif | |
| #elif defined(ENV_COMPILER_MSVC) | |
| # if defined(ENV_LANG_C) && (ENV_LANG_C >= 2011) | |
| // Copied from Clang's headers | |
| typedef double max_align_t; | |
| # define alignof(...) __alignof(__VA_ARGS__) | |
| # elif defined(ENV_LANG_CXX) && (ENV_LANG_CXX >= 2011) | |
| # include <cstddef> | |
| typedef std::max_align_t max_align_t; | |
| # endif | |
| #endif | |
| #if defined(ENV_ARCH_X64) || defined(ENV_ARCH_X86) | |
| # include <pmmintrin.h> // SSE3, 100% support as per Steam Hardware Survey | |
| #endif | |
| #if defined(ENV_COMPILER_MSVC) | |
| // WARN(naman): intrin0.inl.h is an internal header. This might break. | |
| // NOTE(naman): We include intrin0.inl.h instead of the documented intrin.h because intrin.h includes | |
| // EVERY possible intrinsic, and we don't want that (the whole point of only include headers | |
| // SSE3 above). | |
| # include <intrin0.inl.h> | |
| #else | |
| # if defined(ENV_ARCH_X64) || defined(ENV_ARCH_X86) | |
| # include <x86intrin.h> // Add with carry | |
| # endif | |
| #endif | |
| #if defined(ENV_COMPILER_CLANG) || defined(ENV_COMPILER_GCC) | |
| # if defined(BUILD_SANITIZERS) && (__has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)) | |
| disable_warnings(); | |
| # define _DISABLE_STL_ANNOTATION | |
| # include <sanitizer/asan_interface.h> | |
| # undef _DISABLE_STL_ANNOTATION | |
| enable_warnings(); | |
| # define ENV_SANITIZER_ADDRESS | |
| # endif | |
| #endif | |
| #if defined(ENV_COMPILER_MSVC) | |
| # if defined(BUILD_SANITIZERS) && defined(__SANITIZE_ADDRESS__) | |
| disable_warnings(); | |
| # define _DISABLE_STL_ANNOTATION | |
| # include <sanitizer/asan_interface.h> | |
| # undef _DISABLE_STL_ANNOTATION | |
| enable_warnings(); | |
| # define ENV_SANITIZER_ADDRESS | |
| # endif | |
| #endif | |
| #if defined(ENV_SANITIZER_ADDRESS) | |
| # define asan_block(_ptr, _sz) ASAN_POISON_MEMORY_REGION((_ptr), (_sz)) | |
| # define asan_allow(_ptr, _sz) ASAN_UNPOISON_MEMORY_REGION((_ptr), (_sz)) | |
| #else | |
| // NOTE(naman): Sanitizers are not required for tools since them crashing could take down the | |
| // debugging infrastructure itself. But of course it can be enabled for any of them if needed. | |
| # if defined(BUILD_INTERNAL) && !defined(BUILD_TOOL) && !defined(ENV_COMPILER_MSVC) /* FIXME(naman): MSVC always runs next error */ | |
| # error Sanitizers should always be enabled | |
| # endif | |
| # define asan_block(_ptr, _sz) do { (void)(_ptr); (void)(_sz); } while (0) | |
| # define asan_allow(_ptr, _sz) do { (void)(_ptr); (void)(_sz); } while (0) | |
| #endif | |
| /* Primitive Types *******************************************************************/ | |
| typedef int Sint; | |
| typedef unsigned int Uint; | |
| #include <stdint.h> | |
| typedef int8_t Sint8; | |
| typedef int16_t Sint16; | |
| typedef int32_t Sint32; | |
| typedef int64_t Sint64; | |
| typedef uint8_t Uint8; | |
| typedef uint16_t Uint16; | |
| typedef uint32_t Uint32; | |
| typedef uint64_t Uint64; | |
| typedef float Float32; | |
| typedef double Float64; | |
| typedef size_t Size; | |
| #if defined(ENV_OS_LINUX) | |
| typedef ssize_t SSize; | |
| #elif defined(ENV_OS_WINDOWS) | |
| # include <basetsd.h> | |
| typedef SSIZE_T SSize; | |
| #endif | |
| typedef uintptr_t Uptr; | |
| typedef intptr_t Sptr; | |
| typedef ptrdiff_t Dptr; | |
| typedef unsigned char Byte; | |
| typedef char Char; | |
| #if defined(ENV_LANG_C) && (ENV_LANG_C >= 1999) | |
| typedef _Bool Bool; | |
| #elif defined(ENV_LANG_C) && (ENV_LANG_C == 2011) | |
| typedef _Bool Bool; | |
| # else | |
| typedef bool Bool; | |
| #endif | |
| // NOTE(naman): We define our own true & false because by default, they are defined as 1 & 0 and that doesn't play well with _Generic. | |
| pragma_clang("clang diagnostic push"); | |
| pragma_clang("clang diagnostic ignored \"-Wkeyword-macro\""); | |
| #if defined(ENV_LANG_C) | |
| # undef true | |
| # define true ((Bool)+1) | |
| # undef false | |
| # define false ((Bool)+0) | |
| #endif | |
| pragma_clang("clang diagnostic pop"); | |
| typedef Float32 Float32x2[2]; | |
| typedef Float32 Float32x3[3]; | |
| typedef Float32 Float32x4[4]; | |
| /* Helper Stuff **********************************************************************/ | |
| #if defined(ENV_LANG_C) | |
| # define maximum(x, y) ((x) > (y) ? (x) : (y)) | |
| # define minimum(x, y) ((x) < (y) ? (x) : (y)) | |
| #elif defined(ENV_LANG_CXX) | |
| template<typename T> | |
| const T& maximum (const T& a, const T& b) | |
| { | |
| return (a < b) ? b : a; | |
| } | |
| template<typename T> | |
| const T& minimum (const T& a, const T& b) | |
| { | |
| return (a > b) ? b : a; | |
| } | |
| #endif | |
| #define clamp_between(_val, _min, _max) maximum(minimum(_val, _max), _min) | |
| #define memzero(_x) memset((_x), 0, sizeof(*(_x))) | |
| #if defined(ENV_LANG_C) | |
| # define valzero(_t) (_t){0} | |
| #elif defined(ENV_LANG_CXX) | |
| # define valzero(_t) _t{} | |
| #endif | |
| #if defined(BUILD_INTERNAL) | |
| # if defined(ENV_OS_WINDOWS) | |
| # define breakpoint() __debugbreak() | |
| # elif defined(ENV_OS_LINUX) | |
| # if defined(ENV_ARCH_X86) || defined(ENV_ARCH_X64) | |
| # if defined(ENV_COMPILER_GCC) || defined(ENV_COMPILER_CLANG) | |
| # define breakpoint() __asm__ volatile("int $0x03") | |
| # endif // !GCC && !Clang | |
| # endif // !x86 && !x64 | |
| # endif // !window && !linux | |
| #else | |
| # define breakpoint() do {} while (0) | |
| #endif | |
| #if !defined(debugBreak) | |
| # define debugBreak() breakpoint() | |
| #endif | |
| global_immutable Bool debug_assert_non_constant = true; | |
| #define debugAssert(...) do { if (debug_assert_non_constant && !(__VA_ARGS__)) { debugBreak(); } } while (0) | |
| #if defined(BUILD_INTERNAL) | |
| global_immutable Bool debug_unreachable_code_executed = false; | |
| # define debugUnreachable() debugAssert(debug_unreachable_code_executed == true /* To prevent warnings */) | |
| #else | |
| # if defined(ENV_COMPILER_MSVC) | |
| # define debugUnreachable() __assume(false) | |
| # elif defined(ENV_COMPILER_GCC) || defined(ENV_COMPILER_CLANG) | |
| # define debugUnreachable() __builtin_unreachable() | |
| # endif | |
| #endif | |
| #define COMPARE_FUNC(name) Sint name (const void *a, const void *b) | |
| #if defined(ENV_OS_WINDOWS) | |
| # if defined(ENV_LANG_CXX) | |
| extern "C" { | |
| # endif | |
| void * __cdecl memset ( void *dest, Sint ch, Size count); _Pragma("intrinsic(memset)") | |
| void * __cdecl memcpy ( void *dest, const void *src, Size count); _Pragma("intrinsic(memcpy)") | |
| int __cdecl memcmp (const void *lhs, const void *rhs, Size count); _Pragma("intrinsic(memcmp)") | |
| Size __cdecl strlen (const char *str); _Pragma("intrinsic(strlen)") | |
| // Many of these functions are copied from https://github.com/DevSolar/pdclib (https://pdclib.rootdirectory.de/) | |
| header_function | |
| Sint strncmp (const Char *s1, const Char *s2, Size n) | |
| { | |
| while (n && *s1 && (*s1 == *s2)) { | |
| ++s1; | |
| ++s2; | |
| --n; | |
| } | |
| if (n == 0) { | |
| return 0; | |
| } else { | |
| return (*cast_ptr(const Byte*, s1) - *cast_ptr(const Byte*, s2)); | |
| } | |
| } | |
| header_function | |
| int strcmp (const Char *s1, const Char *s2) | |
| { | |
| while (*s1 && (*s1 == *s2)) { | |
| ++s1; | |
| ++s2; | |
| } | |
| return (*cast_ptr(const Byte*, s1) - *cast_ptr(const Byte*,s2)); | |
| } | |
| header_function | |
| Bool strleq (const Char *a, Size n, const Char *b) | |
| { | |
| return strncmp(a, b, n) == 0; | |
| } | |
| header_function | |
| Bool streq (const Char *a, const Char *b) | |
| { | |
| return strcmp(a, b) == 0; | |
| } | |
| # if defined(ENV_LANG_CXX) | |
| } | |
| # endif | |
| #else | |
| # include <string.h> | |
| #endif | |
| /* ********************************************************************************* | |
| * Base 32 Encoding | |
| * **************** | |
| * Inspired by Crockford's Base 32 encoding (https://www.crockford.com/base32.html) | |
| * | |
| * Changes: | |
| * | |
| * 1. U/u is decoded to the same integer as V/v. | |
| * When encoding, v is the right value to encode to. | |
| * | |
| * 2. For the extra check symbols, use - . _ ~ u, since | |
| * these are the symbols considered unreserved in URLs | |
| * See section 2.3 of https://www.ietf.org/rfc/rfc3986.txt | |
| * | |
| * (Explaination: If checksum is required, the integer | |
| * value of the string is divided by 37 (the next prime | |
| * after 32) and the encoded remainder is appended after | |
| * the string. Since the value will be <= 37, five extra | |
| * symbols are required). | |
| * | |
| * 3. The encoded characters are lower-case, not upper case | |
| * since upper case looks like screaming. Also, this way, | |
| * alphabets and numbers are a bit more distinguishable. | |
| * And it reads like r0x0ring h4x0r 13375p34k, n00bs4uc3! | |
| * (Obligatory: https://megatokyo.com/strip/9) | |
| */ | |
| header_function | |
| Uint8 b32DecodeChar (Char code) // Character to integer | |
| { | |
| switch (code) { | |
| case 'O': case 'o': | |
| case '0': return 0; | |
| case 'I': case 'i': | |
| case 'L': case 'l': | |
| case '1': return 1; | |
| case '2': return 2; | |
| case '3': return 3; | |
| case '4': return 4; | |
| case '5': return 5; | |
| case '6': return 6; | |
| case '7': return 7; | |
| case '8': return 8; | |
| case '9': return 9; | |
| case 'A': case 'a': return 10; | |
| case 'B': case 'b': return 11; | |
| case 'C': case 'c': return 12; | |
| case 'D': case 'd': return 13; | |
| case 'E': case 'e': return 14; | |
| case 'F': case 'f': return 15; | |
| case 'G': case 'g': return 16; | |
| case 'H': case 'h': return 17; | |
| case 'J': case 'j': return 18; | |
| case 'K': case 'k': return 19; | |
| case 'M': case 'm': return 20; | |
| case 'N': case 'n': return 21; | |
| case 'P': case 'p': return 22; | |
| case 'Q': case 'q': return 23; | |
| case 'R': case 'r': return 24; | |
| case 'S': case 's': return 25; | |
| case 'T': case 't': return 26; | |
| case 'U': case 'u': | |
| case 'V': case 'v': return 27; | |
| case 'W': case 'w': return 28; | |
| case 'X': case 'x': return 29; | |
| case 'Y': case 'y': return 30; | |
| case 'Z': case 'z': return 31; | |
| default: debugUnreachable(); return 0; | |
| } | |
| } | |
| header_function | |
| Uint8 b32DecodeChecksumChar (Char check) // Character to integer | |
| { | |
| switch (check) { | |
| case '-': return 32; | |
| case '.': return 33; | |
| case '_': return 34; | |
| case '~': return 35; | |
| case 'u': return 36; | |
| default: return b32DecodeChar(check); | |
| } | |
| } | |
| header_function | |
| Char b32EncodeChar (Uint8 val) // Integer to character | |
| { | |
| switch (val) { | |
| case 0: return '0'; case 1: return '1'; case 2: return '2'; case 3: return '3'; | |
| case 4: return '4'; case 5: return '5'; case 6: return '6'; case 7: return '7'; | |
| case 8: return '8'; case 9: return '9'; case 10: return 'a'; case 11: return 'b'; | |
| case 12: return 'c'; case 13: return 'd'; case 14: return 'e'; case 15: return 'f'; | |
| case 16: return 'g'; case 17: return 'h'; case 18: return 'j'; case 19: return 'k'; | |
| case 20: return 'm'; case 21: return 'n'; case 22: return 'p'; case 23: return 'q'; | |
| case 24: return 'r'; case 25: return 's'; case 26: return 't'; case 27: return 'v'; | |
| case 28: return 'w'; case 29: return 'x'; case 30: return 'y'; case 31: return 'z'; | |
| default: debugUnreachable(); return 0; | |
| } | |
| } | |
| header_function | |
| Char b32EncodeChecksumChar (Uint8 val) // Integer to character | |
| { | |
| switch (val) { | |
| case 32: return '-'; | |
| case 33: return '.'; | |
| case 34: return '_'; | |
| case 35: return '~'; | |
| case 36: return 'u'; | |
| default: return b32EncodeChar(val); | |
| } | |
| } | |
| header_function | |
| Bool b32VerifyInputString (const Char *str, Size len, Char checksum) | |
| { | |
| Uint8 mod = 0; | |
| for (Size i = 0; i < len; i++) { | |
| Bool valid = false; | |
| valid |= (str[i] >= 0x30) && (str[i] <= 0x39); // 0-9 | |
| valid |= (str[i] >= 0x61) && (str[i] <= 0x68); // a-h | |
| valid |= (str[i] == 0x6a); // j | |
| valid |= (str[i] == 0x6b); // k | |
| valid |= (str[i] == 0x6d); // m | |
| valid |= (str[i] == 0x6e); // n | |
| valid |= (str[i] >= 0x70) && (str[i] <= 0x74); // p-t | |
| valid |= (str[i] >= 0x76) && (str[i] <= 0x7a); // v-z | |
| valid |= (str[i] >= 0x41) && (str[i] <= 0x48); // A-H | |
| valid |= (str[i] == 0x4a); // j | |
| valid |= (str[i] == 0x4b); // k | |
| valid |= (str[i] == 0x4d); // m | |
| valid |= (str[i] == 0x4e); // n | |
| valid |= (str[i] >= 0x50) && (str[i] <= 0x54); // p-t | |
| valid |= (str[i] >= 0x56) && (str[i] <= 0x5a); // v-z | |
| if (!valid) return false; | |
| mod = (mod + b32DecodeChar(str[i])) % 37u; | |
| } | |
| if (checksum) { | |
| Uint8 check_car = b32DecodeChecksumChar(checksum); | |
| Uint8 check_val = mod; | |
| if (check_val != check_car) return false; | |
| } | |
| return true; | |
| } | |
| header_function | |
| Char b32GetChecksumChar (const Char *str, Size len) | |
| { | |
| Uint8 mod = 0; | |
| for (Size i = 0; i < len; i++) { | |
| mod = (mod + b32DecodeChar(str[i])) % 37u; | |
| } | |
| Char check = b32EncodeChecksumChar(mod); | |
| return check; | |
| } | |
| /************************************************************************************ | |
| * Unicode Processing | |
| */ | |
| // TODO(naman): Go through these security guidelines for safe UTF-8 processing: | |
| // https://security.stackexchange.com/questions/257017/what-are-best-practices-for-handling-user-unicode-in-a-web-application | |
| // TODO(naman): I would like to have my own Unicode variant at some point which will handle all the various weirdnesses of text. | |
| // 1. It should be a multi-byte variable width encoding like UTF-8 | |
| // 2. It should support malformed UTF-16 in the same way that WTF-8 does. | |
| // 3. It should use the Perl6's Normalized Form Grapheme (NFG) to make it easy to iterate on strings. | |
| // 4. Conversion to and from various other formats should be as easy as possible. | |
| /* | |
| * First codepoint Last codepoint Byte 1 Byte 2 Byte 3 Byte 4 | |
| * U+0000 U+007F 0yyyzzzz | |
| * U+0080 U+07FF 110xxxyy 10yyzzzz | |
| * U+0800 U+FFFF 1110wwww 10xxxxyy 10yyzzzz | |
| * U+010000 U+10FFFF 11110uvv 10vvwwww 10xxxxyy 10yyzzzz | |
| */ | |
| // UTF32 to UTF8 | |
| header_function | |
| Size unicodeEncode (Uint32 code, Byte buffer[static_array_size(4)]) | |
| { | |
| if (buffer == nullptr) return 0; | |
| if (code <= 0x7F) { | |
| buffer[0] = cast_val(Byte, code); // 0yyyzzzz | |
| return 1; | |
| } else if (code <= 0x7FF) { | |
| buffer[0] = cast_val(Byte, cast_val(Byte, 0xC0) | cast_val(Byte, (code >> 6))); // 110xxxyy | |
| buffer[1] = cast_val(Byte, cast_val(Byte, 0x80) | cast_val(Byte, (code & 0x3F))); // 10yyzzzz | |
| return 2; | |
| } else if (code <= 0xFFFF) { | |
| buffer[0] = cast_val(Byte, cast_val(Byte, 0xE0) | cast_val(Byte, (code >> 12))); // 1110wwww | |
| buffer[1] = cast_val(Byte, cast_val(Byte, 0x80) | cast_val(Byte, ((code >> 6) & 0x3F))); // 10xxxxyy | |
| buffer[2] = cast_val(Byte, cast_val(Byte, 0x80) | cast_val(Byte, (code & 0x3F))); // 10yyzzzz | |
| return 3; | |
| } else if (code <= 0x10FFFF) { | |
| buffer[0] = cast_val(Byte, cast_val(Byte, 0xF0) | cast_val(Byte, (code >> 18))); // 11110uvv | |
| buffer[1] = cast_val(Byte, cast_val(Byte, 0x80) | cast_val(Byte, ((code >> 12) & 0x3F))); // 10vvwwww | |
| buffer[2] = cast_val(Byte, cast_val(Byte, 0x80) | cast_val(Byte, ((code >> 6) & 0x3F))); // 10xxxxyy | |
| buffer[3] = cast_val(Byte, cast_val(Byte, 0x80) | cast_val(Byte, (code & 0x3F))); // 10yyzzzz | |
| return 4; | |
| } else { | |
| return 0; // Invalid codepoint | |
| } | |
| } | |
| // UTF8 to UTF32 | |
| header_function | |
| Bool unicodeDecode (const Byte *buffer, Size len, Size *offset, Uint32 *codepoint) | |
| { | |
| if (buffer == nullptr) return 0; | |
| Size temp1 = 0; if (offset == nullptr) offset = &temp1; | |
| Uint32 temp2 = 0; if (codepoint == nullptr) codepoint = &temp2; | |
| Size o = *offset; | |
| len -= o; | |
| *codepoint = 0; | |
| if (len == 0) return false; | |
| Uint32 b0 = buffer[o+0]; | |
| *offset += 1; | |
| if (b0 == 0) return true; // nullptr-byte | |
| if ((b0 & 0x80) == 0) { // 0x80 = 10000000 | |
| *codepoint = b0; | |
| return true; | |
| } | |
| if (len == 1) return false; | |
| Uint32 b1 = buffer[o+1]; | |
| *offset += 1; | |
| if (b1 == 0) return false; | |
| Uint32 BA1 = b1 & 0xC0; // 0xC0 = 11000000 | |
| Uint32 BB1 = b1 & 0x3F; // 0x3F = 00111111 | |
| if ((b0 & 0xE0) == 0xC0) { // 0xE0 = 11100000 , 0xC0 = 11000000 | |
| if (BA1 != 0x80) return false; // 0x80 = 10000000 | |
| Uint32 B = b0 & 0x1F; // 0x1F = 00011111 | |
| *codepoint = (B << 6) | BB1; | |
| return true; | |
| } | |
| if (len == 2) return false; | |
| Uint32 b2 = buffer[o+2]; | |
| *offset += 1; | |
| if (b2 == 0) return false; | |
| Uint32 BA2 = b2 & 0xC0; // 0xC0 = 11000000 | |
| Uint32 BB2 = b2 & 0x3F; // 0x3F = 00111111 | |
| if ((b0 & 0xF0) == 0xE0) { // 0xF0 = 11110000 , 0xE0 = 11100000 | |
| if (BA1 != 0x80) return false; // 0x80 = 10000000 | |
| if (BA2 != 0x80) return false; // 0x80 = 10000000 | |
| Uint32 B = b0 & 0x0F; // 0x0F = 00001111 | |
| *codepoint = (B << 12) | (BB1 << 6) | BB2; | |
| return true; | |
| } | |
| if (len == 3) return false; | |
| Uint32 b3 = buffer[o+3]; | |
| *offset += 1; | |
| if (b3 == 0) return false; | |
| Uint32 BA3 = b3 & 0xC0; // 0xC0 = 11000000 | |
| Uint32 BB3 = b3 & 0x3F; // 0x3F = 00111111 | |
| if ((b0 & 0xF8) == 0xF0) { // 0xF8 = 11111000 , 0xF0 = 11110000 | |
| if (BA1 != 0x80) return false; // 0x80 = 10000000 | |
| if (BA2 != 0x80) return false; // 0x80 = 10000000 | |
| if (BA3 != 0x80) return false; // 0x80 = 10000000 | |
| Uint32 B = b0 & 0x07; // 0x07 = 00000111 | |
| Uint32 result = (B << 18) | (BB1 << 12) | (BB2 << 6) | BB3; | |
| if (result > 0x10FFFF) return false; | |
| *codepoint = result; | |
| return true; | |
| } | |
| return false; | |
| } | |
| /********************************************************************** | |
| * Bitwise Delight | |
| */ | |
| // NOTE(naman): Bit vectors are supposed to be zero-indexed. | |
| // NOTE(naman): Base type of bit vectors shouldn't have size > 8 bytes (to prevent shift overflow). | |
| #define bitToBytes(b) (((b)+(CHAR_BIT-1))/(CHAR_BIT)) | |
| #define bit_ValInBuf(array, index) ((index)/(CHAR_BIT * sizeof(*(array)))) | |
| #define bit_BitInVal(array, index) ((index)%(CHAR_BIT * sizeof(*(array)))) | |
| #define bitSet(array, index) \ | |
| ((array)[bit_ValInBuf(array, index)] |= (1LLU << bit_BitInVal(array, index))) | |
| #define bitReset(array, index) \ | |
| ((array)[bit_ValInBuf(array, index)] &= ~(1LLU << bit_BitInVal(array, index))) | |
| #define bitToggle(array, index) \ | |
| ((array)[bit_ValInBuf(array, index)] ^= ~(1LLU << bit_BitInVal(array, index))) | |
| #define bitTest(array, index) \ | |
| ((array)[bit_ValInBuf(array, index)] & (1LLU << bit_BitInVal(array, index))) | |
| header_function | |
| Uint8 bitMSBU32 (Uint32 x) | |
| { | |
| debugAssert(x); | |
| Uint8 result; | |
| #if defined(ENV_COMPILER_MSVC) | |
| // _BitScanReverse: Search MSB->LSB and load the position of first bit in location (zero-indexed) | |
| unsigned long msb_location = 0; | |
| debugAssert(_BitScanReverse(&msb_location, x)); | |
| result = cast_val(Uint8, msb_location); | |
| #else | |
| // __builtin_clz: Returns the number of leading 0-bits starting from MSB | |
| Sint maybe_set_bits = 32 - __builtin_clz(x); | |
| Sint msb_location = maybe_set_bits - 1; // Zero indexing | |
| result = cast_val(Uint8, msb_location); | |
| #endif | |
| return result; | |
| } | |
| header_function | |
| Uint8 bitMSBU64 (Uint64 x) | |
| { | |
| debugAssert(x); | |
| Uint8 result; | |
| #if defined(ENV_COMPILER_MSVC) | |
| // _BitScanReverse64: Search MSB->LSB and load the position of first bit in location (zero-indexed) | |
| unsigned long msb_location = 0; | |
| debugAssert(_BitScanReverse64(&msb_location, x)); | |
| result = cast_val(Uint8, msb_location); | |
| #else | |
| // __builtin_clzll: Returns the number of leading 0-bits starting from MSB | |
| Sint maybe_set_bits = 64 - __builtin_clzll(x); | |
| Sint msb_location = maybe_set_bits - 1; // Zero indexing | |
| result = cast_val(Uint8, msb_location); | |
| #endif | |
| return result; | |
| } | |
| header_function | |
| Uint8 bitLSBU32 (Uint32 x) | |
| { | |
| debugAssert(x); | |
| Uint8 result; | |
| #if defined(ENV_COMPILER_MSVC) | |
| // _BitScanForward: Search LSB->MSB and load the position of first bit in location (zero-indexed) | |
| unsigned long lsb_location = 0; | |
| debugAssert(_BitScanForward(&lsb_location, x)); | |
| result = cast_val(Uint8, lsb_location); | |
| #else | |
| // __builtin_ctz: Returns the number of trailing 0-bits starting from LSB | |
| Sint lsb_location = __builtin_ctz(x); | |
| result = cast_val(Uint8, lsb_location); | |
| #endif | |
| return result; | |
| } | |
| header_function | |
| Uint8 bitLSBU64 (Uint64 x) | |
| { | |
| debugAssert(x); | |
| Uint8 result; | |
| #if defined(ENV_COMPILER_MSVC) | |
| // _BitScanForward: Search LSB->MSB and load the position of first bit in location (zero-indexed) | |
| unsigned long lsb_location = 0; | |
| debugAssert(_BitScanForward64(&lsb_location, x)); | |
| result = cast_val(Uint8, lsb_location); | |
| #else | |
| // __builtin_ctzll: Returns the number of trailing 0-bits starting from LSB | |
| Sint lsb_location = __builtin_ctzll(x); | |
| result = cast_val(Uint8, lsb_location); | |
| #endif | |
| return result; | |
| } | |
| #if defined(ENV_LANG_C) && ENV_LANG_C >= 2011 | |
| # define bitMSB(_x) _Generic((_x), Uint32: bitMSBU32, Uint64: bitMSBU64)(_x) | |
| # define bitLSB(_x) _Generic((_x), Uint32: bitLSBU32, Uint64: bitLSBU64)(_x) | |
| #elif defined(ENV_LANG_CXX) | |
| header_function Uint8 bitMSB (Uint32 x) { return bitMSBU32(x); } | |
| header_function Uint8 bitMSB (Uint64 x) { return bitMSBU64(x); } | |
| header_function Uint8 bitLSB (Uint32 x) { return bitLSBU32(x); } | |
| header_function Uint8 bitLSB (Uint64 x) { return bitLSBU64(x); } | |
| #endif // !LANG_C && !LANG_CPP | |
| header_function | |
| Uint64 bitPow2U32 (Uint32 x) | |
| { | |
| Uint64 y = 1ull << x; | |
| return y; | |
| } | |
| header_function | |
| Uint64 bitPow2U64 (Uint64 x) | |
| { | |
| Uint64 y = 1ull << x; | |
| return y; | |
| } | |
| header_function | |
| Uint8 bitLog2U32 (Uint32 x) | |
| { | |
| debugAssert(x); | |
| Uint8 y = x ? bitMSBU32(x) : 0u; | |
| return y; | |
| } | |
| header_function | |
| Uint8 bitLog2U64 (Uint64 x) | |
| { | |
| debugAssert(x); | |
| Uint8 y = x ? bitMSBU64(x) : 0u; | |
| return y; | |
| } | |
| header_function | |
| Bool bitIsPowerOf2U32 (Uint32 x) | |
| { | |
| debugAssert(x); | |
| Bool b = (x & (x - 1)) == 0; | |
| return b; | |
| } | |
| header_function | |
| Bool bitIsPowerOf2U64 (Uint64 x) | |
| { | |
| debugAssert(x); | |
| Bool b = (x & (x - 1)) == 0; | |
| return b; | |
| } | |
| header_function | |
| Uint64 bitPrevPowerOf2U32 (Uint32 x) | |
| { | |
| debugAssert(x); | |
| Uint64 y = bitIsPowerOf2U32(x) ? x: (1u << (bitLog2U32(x) - 1u)) ; | |
| return y; | |
| } | |
| header_function | |
| Uint64 bitPrevPowerOf2U64 (Uint64 x) | |
| { | |
| debugAssert(x); | |
| Uint64 y = bitIsPowerOf2U64(x) ? x : (1ull << (bitLog2U64(x) - 1ull)); | |
| return y; | |
| } | |
| header_function | |
| Uint64 bitNextPowerOf2U32 (Uint32 x) | |
| { | |
| debugAssert(x); | |
| Uint64 y = bitIsPowerOf2U32(x) ? x : (1u << (bitLog2U32(x) + 1u)); | |
| return y; | |
| } | |
| header_function | |
| Uint64 bitNextPowerOf2U64 (Uint64 x) | |
| { | |
| debugAssert(x); | |
| Uint64 y = bitIsPowerOf2U64(x) ? x : (1ull << (bitLog2U64(x) + 1ull)); | |
| return y; | |
| } | |
| // Number of leading zeros (on the most significant end) | |
| header_function | |
| Uint8 bitMSZerosU32 (Uint32 x) | |
| { | |
| debugAssert(x); | |
| Uint8 clz = 32U - bitMSBU32(x) - 1U; | |
| return clz; | |
| } | |
| header_function | |
| Uint8 bitMSZerosU64 (Uint64 x) | |
| { | |
| debugAssert(x); | |
| Uint8 clz = 64U - bitMSBU64(x) - 1U; | |
| return clz; | |
| } | |
| // square root functions from Mark Dickinson | |
| // https://stackoverflow.com/a/70550680/12862673 | |
| header_function | |
| Uint16 bitSqrtU32 (Uint32 x) | |
| { | |
| Sint lz = bitMSZerosU32(x | 1U) & 0x1E; | |
| x <<= lz; | |
| Uint32 y = 1u + (x >> 30); | |
| y = (y << 1) + (x >> 27) / y; | |
| y = (y << 3) + (x >> 21) / y; | |
| y = (y << 7) + (x >> 9) / y; | |
| y -= x < cast_val(Uint32, y) * y; | |
| return cast_val(Uint16, (y >> (lz >> 1))); | |
| } | |
| header_function | |
| Uint32 bitSqrtU64 (Uint64 x) | |
| { | |
| Uint8 lz = cast_val(Uint8, bitMSZerosU64(x | 1ull) & 0x3Eu); | |
| x <<= lz; | |
| Uint64 y = 2ull + (x >> 63); | |
| y = (y << 1) + (x >> 59) / y; | |
| y = (y << 3) + (x >> 53) / y; | |
| y = (y << 7) + (x >> 41) / y; | |
| y = (y << 15) + (x >> 17) / y; | |
| y -= x < cast_val(Uint64, y) * y; | |
| return cast_val(Uint32, (y >> (lz >> 1))); | |
| } | |
| #if defined(ENV_LANG_C) && ENV_LANG_C >= 2011 | |
| # define bitPow2(x) _Generic((x), Uint32: bitPow2U32, Uint64: bitPow2U64)(x) | |
| # define bitLog2(x) _Generic((x), Uint32: bitLog2U32, Uint64: bitLog2U64)(x) | |
| # define bitIsPowerOf2(x) _Generic((x), Uint32: bitIsPowerOf2U32, Uint64: bitIsPowerOf2U64)(x) | |
| # define bitPrevPowerOf2(x) _Generic((x), Uint32: bitPrevPowerOf2U32, Uint64: bitPrevPowerOf2U64)(x) | |
| # define bitNextPowerOf2(x) _Generic((x), Uint32: bitNextPowerOf2U32, Uint64: bitNextPowerOf2U64)(x) | |
| # define bitSqrt(x) _Generic((x), Uint32: bitSqrtU32, Uint64: bitSqrtU64)(x) | |
| #elif defined(ENV_LANG_CXX) | |
| header_function Uint64 bitPow2 (Uint32 x) { return bitPow2U32(x); } | |
| header_function Uint64 bitPow2 (Uint64 x) { return bitPow2U64(x); } | |
| header_function Uint8 bitLog2 (Uint32 x) { return bitLog2U32(x); } | |
| header_function Uint8 bitLog2 (Uint64 x) { return bitLog2U64(x); } | |
| header_function Bool bitIsPowerOf2 (Uint32 x) { return bitIsPowerOf2U32(x); } | |
| header_function Bool bitIsPowerOf2 (Uint64 x) { return bitIsPowerOf2U64(x); } | |
| header_function Uint64 bitPrevPowerOf2 (Uint32 x) { return bitPrevPowerOf2U32(x); } | |
| header_function Uint64 bitPrevPowerOf2 (Uint64 x) { return bitPrevPowerOf2U64(x); } | |
| header_function Uint64 bitNextPowerOf2 (Uint32 x) { return bitNextPowerOf2U32(x); } | |
| header_function Uint64 bitNextPowerOf2 (Uint64 x) { return bitNextPowerOf2U64(x); } | |
| header_function Uint16 bitSqrt (Uint32 x) { return bitSqrtU32(x); } | |
| header_function Uint32 bitSqrt (Uint64 x) { return bitSqrtU64(x); } | |
| #endif | |
| /************************************************************************************* | |
| * Random Number Generation (xoshiro256**) | |
| * --------------------------------------- | |
| * Courtsey of David Blackman and Sebastiano Vigna | |
| * License: Public Domain | |
| * https://prng.di.unimi.it/xoshiro256starstar.c | |
| */ | |
| // splitmix64 RNG used to seed the xoshiro256** state | |
| // https://prng.di.unimi.it/splitmix64.c | |
| header_function | |
| Uint64 rng_SeedCalculator (Uint64 *seed_ptr) | |
| { | |
| *seed_ptr += 0x9e3779b97f4a7c15; | |
| Uint64 seed = *seed_ptr; | |
| seed = (seed ^ (seed >> 30)) * 0xbf58476d1ce4e5b9; | |
| seed = (seed ^ (seed >> 27)) * 0x94d049bb133111eb; | |
| Uint64 result = seed ^ (seed >> 31); | |
| return result; | |
| } | |
| header_function | |
| Uint64 rng_RotateLeft(const Uint64 x, Sint k) { | |
| return (x << k) | (x >> (64 - k)); | |
| } | |
| typedef struct RNG_State { | |
| Uint64 s[4]; | |
| } RNG_State; | |
| header_function | |
| RNG_State rngMake (Uint64 seed) | |
| { | |
| RNG_State state = {}; | |
| state.s[0] = rng_SeedCalculator(&seed); | |
| state.s[1] = rng_SeedCalculator(&seed); | |
| state.s[2] = rng_SeedCalculator(&seed); | |
| state.s[3] = rng_SeedCalculator(&seed); | |
| return state; | |
| } | |
| header_function | |
| Uint64 rngGet (RNG_State *state) | |
| { | |
| const Uint64 result = rng_RotateLeft(state->s[1] * 5, 7) * 9; | |
| const Uint64 t = state->s[1] << 17; | |
| state->s[2] ^= state->s[0]; | |
| state->s[3] ^= state->s[1]; | |
| state->s[1] ^= state->s[2]; | |
| state->s[0] ^= state->s[3]; | |
| state->s[2] ^= t; | |
| state->s[3] = rng_RotateLeft(state->s[3], 45); | |
| return result; | |
| } | |
| // This is equivalent to 2^128 calls to rngGet(), it can be used to generate 2^128 | |
| // non-overlapping subsequences for parallel computations. | |
| header_function | |
| RNG_State rngJump128 (RNG_State prev_state) | |
| { | |
| persistent_immutable Uint64 JUMP[] = { 0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c }; | |
| RNG_State new_state = {}; | |
| for (Size i = 0; i < elemin(JUMP); i++) { | |
| for (Size b = 0; b < 64; b++) { | |
| if (JUMP[i] & 1ull << b) { | |
| new_state.s[0] ^= prev_state.s[0]; | |
| new_state.s[1] ^= prev_state.s[1]; | |
| new_state.s[2] ^= prev_state.s[2]; | |
| new_state.s[3] ^= prev_state.s[3]; | |
| } | |
| rngGet(&prev_state); | |
| } | |
| } | |
| return new_state; | |
| } | |
| // This is equivalent to 2^192 calls to rngGet(), it can be used to generate 2^64 starting points, | |
| // from each of which rngJump128() will generate 2^64 non-overlapping subsequences for parallel | |
| // **distributed** computations. | |
| header_function | |
| RNG_State rngJump192 (RNG_State prev_state) | |
| { | |
| persistent_immutable Uint64 LONG_JUMP[] = { 0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241, 0x39109bb02acbe635 }; | |
| RNG_State new_state = {}; | |
| for (Size i = 0; i < elemin(LONG_JUMP); i++) { | |
| for (Size b = 0; b < 64; b++) { | |
| if (LONG_JUMP[i] & 1ull << b) { | |
| new_state.s[0] ^= prev_state.s[0]; | |
| new_state.s[1] ^= prev_state.s[1]; | |
| new_state.s[2] ^= prev_state.s[2]; | |
| new_state.s[3] ^= prev_state.s[3]; | |
| } | |
| rngGet(&prev_state); | |
| } | |
| } | |
| return new_state; | |
| } | |
| /************************************************************************************* | |
| * Serialization | |
| */ | |
| header_function | |
| void dump8 (Uint8 src, Byte *dest) | |
| { | |
| dest[0] = src; | |
| } | |
| #define dump8BE dump8 | |
| #define dump8LE dump8 | |
| header_function | |
| void dump16LE (Uint16 src, Byte *dest) | |
| { | |
| dest[0] = cast_val(Uint8, (0x00000000000000FF & (src)) >> 000); | |
| dest[1] = cast_val(Uint8, (0x000000000000FF00 & (src)) >> 010); | |
| } | |
| header_function | |
| void dump16BE (Uint16 src, Byte *dest) | |
| { | |
| dest[0] = cast_val(Uint8, (0x000000000000FF00 & (src)) >> 010); | |
| dest[1] = cast_val(Uint8, (0x00000000000000FF & (src)) >> 000); | |
| } | |
| header_function | |
| void dump32LE (Uint32 src, Byte *dest) | |
| { | |
| dest[0] = cast_val(Uint8, (0x00000000000000FF & (src)) >> 000); | |
| dest[1] = cast_val(Uint8, (0x000000000000FF00 & (src)) >> 010); | |
| dest[2] = cast_val(Uint8, (0x0000000000FF0000 & (src)) >> 020); | |
| dest[3] = cast_val(Uint8, (0x00000000FF000000 & (src)) >> 030); | |
| } | |
| header_function | |
| void dump32BE (Uint32 src, Byte *dest) | |
| { | |
| dest[0] = cast_val(Uint8, (0x00000000FF000000 & (src)) >> 030); | |
| dest[1] = cast_val(Uint8, (0x0000000000FF0000 & (src)) >> 020); | |
| dest[2] = cast_val(Uint8, (0x000000000000FF00 & (src)) >> 010); | |
| dest[3] = cast_val(Uint8, (0x00000000000000FF & (src)) >> 000); | |
| } | |
| header_function | |
| void dump64LE (Uint64 src, Byte *dest) | |
| { | |
| dest[0] = cast_val(Uint8, (0x00000000000000FF & (src)) >> 000); | |
| dest[1] = cast_val(Uint8, (0x000000000000FF00 & (src)) >> 010); | |
| dest[2] = cast_val(Uint8, (0x0000000000FF0000 & (src)) >> 020); | |
| dest[3] = cast_val(Uint8, (0x00000000FF000000 & (src)) >> 030); | |
| dest[4] = cast_val(Uint8, (0x000000FF00000000 & (src)) >> 040); | |
| dest[5] = cast_val(Uint8, (0x0000FF0000000000 & (src)) >> 050); | |
| dest[6] = cast_val(Uint8, (0x00FF000000000000 & (src)) >> 060); | |
| dest[7] = cast_val(Uint8, (0xFF00000000000000 & (src)) >> 070); | |
| } | |
| header_function | |
| void dump64BE (Uint64 src, Byte *dest) | |
| { | |
| dest[0] = cast_val(Uint8, (0xFF00000000000000 & (src)) >> 070); | |
| dest[1] = cast_val(Uint8, (0x00FF000000000000 & (src)) >> 060); | |
| dest[2] = cast_val(Uint8, (0x0000FF0000000000 & (src)) >> 050); | |
| dest[3] = cast_val(Uint8, (0x000000FF00000000 & (src)) >> 040); | |
| dest[4] = cast_val(Uint8, (0x00000000FF000000 & (src)) >> 030); | |
| dest[5] = cast_val(Uint8, (0x0000000000FF0000 & (src)) >> 020); | |
| dest[6] = cast_val(Uint8, (0x000000000000FF00 & (src)) >> 010); | |
| dest[7] = cast_val(Uint8, (0x00000000000000FF & (src)) >> 000); | |
| } | |
| header_function | |
| Uint8 load8 (const Byte *src) | |
| { | |
| Uint8 result = src[0]; | |
| return result; | |
| } | |
| #define load8BE load8 | |
| #define load8LE load8 | |
| header_function | |
| Uint16 load16LE (const Byte *src) { | |
| Uint16 result = cast_val(Uint16, cast_val(Uint16, 255 & src[1]) << 8 | cast_val(Uint16, 255 & src[0])); | |
| return result; | |
| } | |
| header_function | |
| Uint16 load16BE (const Byte *src) { | |
| Uint16 result = cast_val(Uint16, cast_val(Uint16, 255 & src[0]) << 8 | cast_val(Uint16, 255 & src[1])); | |
| return result; | |
| } | |
| header_function | |
| Uint32 load32LE (const Byte *src) | |
| { | |
| Uint32 result = (cast_val(Uint32, 255 & src[3]) << 030 | cast_val(Uint32, 255 & src[2]) << 020 | | |
| cast_val(Uint32, 255 & src[1]) << 010 | cast_val(Uint32, 255 & src[0]) << 000); | |
| return result; | |
| } | |
| header_function | |
| Uint32 load32BE (const Byte *src) | |
| { | |
| Uint32 result = (cast_val(Uint32, 255 & src[0]) << 030 | cast_val(Uint32, 255 & src[1]) << 020 | | |
| cast_val(Uint32, 255 & src[2]) << 010 | cast_val(Uint32, 255 & src[3]) << 000); | |
| return result; | |
| } | |
| header_function | |
| Uint64 load64LE (const Byte *src) | |
| { | |
| Uint64 result = (cast_val(Uint64, 255 & src[7]) << 070 | cast_val(Uint64, 255 & src[6]) << 060 | | |
| cast_val(Uint64, 255 & src[5]) << 050 | cast_val(Uint64, 255 & src[4]) << 040 | | |
| cast_val(Uint64, 255 & src[3]) << 030 | cast_val(Uint64, 255 & src[2]) << 020 | | |
| cast_val(Uint64, 255 & src[1]) << 010 | cast_val(Uint64, 255 & src[0]) << 000); | |
| return result; | |
| } | |
| header_function | |
| Uint64 load64BE (const Byte *src) | |
| { | |
| Uint64 result = (cast_val(Uint64, 255 & src[0]) << 070 | cast_val(Uint64, 255 & src[1]) << 060 | | |
| cast_val(Uint64, 255 & src[2]) << 050 | cast_val(Uint64, 255 & src[3]) << 040 | | |
| cast_val(Uint64, 255 & src[4]) << 030 | cast_val(Uint64, 255 & src[5]) << 020 | | |
| cast_val(Uint64, 255 & src[6]) << 010 | cast_val(Uint64, 255 & src[7]) << 000); | |
| return result; | |
| } | |
| typedef union loadump_8 { Uint8 u8; Sint8 s8; } loadump_8; | |
| typedef union loadump_16 { Uint16 u16; Sint16 s16; } loadump_16; | |
| typedef union loadump_32 { Uint32 u32; Sint32 s32; Float32 f32; } loadump_32; | |
| typedef union loadump_64 { Uint64 u64; Sint64 s64; Float64 f64; } loadump_64; | |
| /************************************************************************************* | |
| * Serialization | |
| */ | |
| // NOTE(naman): A basic implementation of this function for reading and writing from memory | |
| // is given below after the implementation of segar. | |
| // Search for `Srlz_Memory_Loader` and `Srlz_Memory_Dumper`. | |
| #define SRLZ_FUNC(_name) Bool _name (void* data, Size size, void* userdata) | |
| header_function | |
| Bool srlzUint8 (Uint8 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) { | |
| unused_variable(big_endian); | |
| loadump_8 ld = {0}; | |
| Byte buf[sizeof(*datum)] = {0}; | |
| if (write) { | |
| ld.u8 = *datum; | |
| dump8(ld.u8, buf); | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| } else { | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| ld.u8 = load8(buf); | |
| *datum = ld.u8; | |
| } | |
| return true; | |
| } | |
| header_function | |
| Bool srlzSint8 (Sint8 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) { | |
| unused_variable(big_endian); | |
| loadump_8 ld = {0}; | |
| Byte buf[sizeof(*datum)] = {0}; | |
| if (write) { | |
| ld.s8 = *datum; | |
| dump8(ld.u8, buf); | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| } else { | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| ld.u8 = load8(buf); | |
| *datum = ld.s8; | |
| } | |
| return true; | |
| } | |
| header_function | |
| Bool srlzUint16 (Uint16 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) { | |
| loadump_16 ld = {0}; | |
| Byte buf[sizeof(*datum)] = {0}; | |
| if (write) { | |
| ld.u16 = *datum; | |
| if (big_endian) { dump16BE(ld.u16, buf); } | |
| else { dump16LE(ld.u16, buf); } | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| } else { | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| if (big_endian) { ld.u16 = load16BE(buf); } | |
| else { ld.u16 = load16LE(buf); } | |
| *datum = ld.u16; | |
| } | |
| return true; | |
| } | |
| header_function | |
| Bool srlzSint16 (Sint16 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) { | |
| loadump_16 ld = {0}; | |
| Byte buf[sizeof(*datum)] = {0}; | |
| if (write) { | |
| ld.s16 = *datum; | |
| if (big_endian) { dump16BE(ld.u16, buf); } | |
| else { dump16LE(ld.u16, buf); } | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| } else { | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| if (big_endian) { ld.u16 = load16BE(buf); } | |
| else { ld.u16 = load16LE(buf); } | |
| *datum = ld.s16; | |
| } | |
| return true; | |
| } | |
| header_function | |
| Bool srlzUint32 (Uint32 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) { | |
| loadump_32 ld = {0}; | |
| Byte buf[sizeof(*datum)] = {0}; | |
| if (write) { | |
| ld.u32 = *datum; | |
| if (big_endian) { dump32BE(ld.u32, buf); } | |
| else { dump32LE(ld.u32, buf); } | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| } else { | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| if (big_endian) { ld.u32 = load32BE(buf); } | |
| else { ld.u32 = load32LE(buf); } | |
| *datum = ld.u32; | |
| } | |
| return true; | |
| } | |
| header_function | |
| Bool srlzSint32 (Sint32 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) { | |
| loadump_32 ld = {0}; | |
| Byte buf[sizeof(*datum)] = {0}; | |
| if (write) { | |
| ld.s32 = *datum; | |
| if (big_endian) { dump32BE(ld.u32, buf); } | |
| else { dump32LE(ld.u32, buf); } | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| } else { | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| if (big_endian) { ld.u32 = load32BE(buf); } | |
| else { ld.u32 = load32LE(buf); } | |
| *datum = ld.s32; | |
| } | |
| return true; | |
| } | |
| header_function | |
| Bool srlzFloat32 (Float32 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) { | |
| loadump_32 ld = {0}; | |
| Byte buf[sizeof(*datum)] = {0}; | |
| if (write) { | |
| ld.f32 = *datum; | |
| if (big_endian) { dump32BE(ld.u32, buf); } | |
| else { dump32LE(ld.u32, buf); } | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| } else { | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| if (big_endian) { ld.u32 = load32BE(buf); } | |
| else { ld.u32 = load32LE(buf); } | |
| *datum = ld.f32; | |
| } | |
| return true; | |
| } | |
| header_function | |
| Bool srlzUint64 (Uint64 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) { | |
| loadump_64 ld = {0}; | |
| Byte buf[sizeof(*datum)] = {0}; | |
| if (write) { | |
| ld.u64 = *datum; | |
| if (big_endian) { dump64BE(ld.u64, buf); } | |
| else { dump64LE(ld.u64, buf); } | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| } else { | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| if (big_endian) { ld.u64 = load64BE(buf); } | |
| else { ld.u64 = load64LE(buf); } | |
| *datum = ld.u64; | |
| } | |
| return true; | |
| } | |
| header_function | |
| Bool srlzSint64 (Sint64 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) { | |
| loadump_64 ld = {0}; | |
| Byte buf[sizeof(*datum)] = {0}; | |
| if (write) { | |
| ld.s64 = *datum; | |
| if (big_endian) { dump64BE(ld.u64, buf); } | |
| else { dump64LE(ld.u64, buf); } | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| } else { | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| if (big_endian) { ld.u64 = load64BE(buf); } | |
| else { ld.u64 = load64LE(buf); } | |
| *datum = ld.s64; | |
| } | |
| return true; | |
| } | |
| header_function | |
| Bool srlzFloat64 (Float64 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) { | |
| loadump_64 ld = {0}; | |
| Byte buf[sizeof(*datum)] = {0}; | |
| if (write) { | |
| ld.f64 = *datum; | |
| if (big_endian) { dump64BE(ld.u64, buf); } | |
| else { dump64LE(ld.u64, buf); } | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| } else { | |
| if (!func(buf, sizeof(*datum), funcdata)) return false; | |
| if (big_endian) { ld.u64 = load64BE(buf); } | |
| else { ld.u64 = load64LE(buf); } | |
| *datum = ld.f64; | |
| } | |
| return true; | |
| } | |
| /************************************************************************************** | |
| * Printing | |
| */ | |
| #define PRINT_FUNC(name) Size name (void *userdata, const Char *fmt, ...) | |
| #define PRINT_CALLBACK_FUNC(name) void name (void *userdata, const Char *filled_buffer, Size buffer_len, Size buffer_cap) | |
| typedef enum Print_Flags { | |
| Print_Flags_LEFT_JUSTIFIED = 1u << 0, | |
| Print_Flags_ALTERNATE_FORM = 1u << 1, | |
| Print_Flags_LEADING_PLUS = 1u << 2, | |
| Print_Flags_LEADING_SPACE = 1u << 3, | |
| Print_Flags_LEADING_ZERO = 1u << 4, | |
| Print_Flags_INT8 = 1u << 5, | |
| Print_Flags_INT16 = 1u << 6, | |
| Print_Flags_INT64 = 1u << 7, | |
| Print_Flags_NEGATIVE = 1u << 8, | |
| Print_Flags_FLOAT_FIXED = 1u << 9, | |
| Print_Flags_FLOAT_EXP = 1u << 10, | |
| Print_Flags_FLOAT_HEX = 1u << 11, | |
| } Print_Flags; | |
| // TODO(naman): Add the following features: | |
| // * Comma in numbers | |
| // + Indian style (`) | |
| // + Western style (') | |
| // * Unit suffix | |
| // + SI (1 K = 1000) (:) | |
| // + IEC (1 K = 1024) (;) | |
| header_function | |
| with_clang_gcc(__attribute__(( format( __printf__, 3, 0 )))) /* 2nd arg is 0 since va_list can't be checked */ | |
| Size printStringVarArg (Char *buffer, Size buffer_size, | |
| Char const *format, va_list va, | |
| PRINT_CALLBACK_FUNC((*callback)), void *userdata) | |
| { | |
| debugAssert(buffer); | |
| debugAssert(buffer_size); | |
| debugAssert(format); | |
| debugAssert(callback); | |
| Char const *fmt = format; | |
| Size buffer_index = 0; | |
| Size total_size = 0; | |
| #define PRINT_ADD_CHAR(_c) do { \ | |
| if (buffer_index == buffer_size) { \ | |
| callback(userdata, buffer, buffer_index, buffer_size); \ | |
| buffer_index = 0; \ | |
| } \ | |
| buffer[buffer_index++] = _c; \ | |
| total_size++; \ | |
| } while (0) | |
| for(;;) { | |
| // Copy everything up to the next % (or end of string) | |
| while ((fmt[0] != '%') && (fmt[0] != '\0')) { | |
| PRINT_ADD_CHAR(fmt[0]); | |
| fmt++; | |
| } | |
| if (fmt[0] == '%') { | |
| Char const *format_pointer = fmt; | |
| fmt++; | |
| // read the modifiers first | |
| Uint32 flags = 0; | |
| // flags | |
| for(;;) { | |
| switch (fmt[0]) { | |
| case '-': { // if we have left justify | |
| flags |= Print_Flags_LEFT_JUSTIFIED; | |
| fmt++; | |
| continue; | |
| } break; | |
| case '#': { // if we use alternate form | |
| flags |= Print_Flags_ALTERNATE_FORM; | |
| fmt++; | |
| continue; | |
| } break; | |
| case '+': { // if we have leading plus | |
| flags |= Print_Flags_LEADING_PLUS; | |
| fmt++; | |
| continue; | |
| } break; | |
| case ' ': { // if we have leading space | |
| flags |= Print_Flags_LEADING_SPACE; | |
| fmt++; | |
| continue; | |
| } break; | |
| case '0': { // if we have leading zero | |
| flags |= Print_Flags_LEADING_ZERO; | |
| fmt++; | |
| goto flags_done; | |
| } break; | |
| default: { | |
| goto flags_done; | |
| } break; | |
| } | |
| } | |
| flags_done: | |
| Sint minimum_width = 0; // called field width in documentation | |
| // get the field width | |
| if (fmt[0] == '*') { | |
| minimum_width = va_arg(va, Sint); | |
| fmt++; | |
| } else { | |
| while ((fmt[0] >= '0') && (fmt[0] <= '9')) { | |
| minimum_width = (minimum_width * 10) + (fmt[0] - '0'); | |
| fmt++; | |
| } | |
| } | |
| Sint precision = -1; | |
| // get the precision | |
| if (fmt[0] == '.') { | |
| fmt++; | |
| if (fmt[0] == '*') { | |
| precision = va_arg(va, Sint); | |
| fmt++; | |
| } else { | |
| precision = 0; | |
| if (fmt[0] == '-') { // Negative precision is treated as if precision was omitted | |
| fmt++; | |
| precision = -1; | |
| while ((fmt[0] >= '0') && (fmt[0] <= '9')) { | |
| fmt++; | |
| } | |
| } else { | |
| while ((fmt[0] >= '0') && (fmt[0] <='9')) { | |
| precision = (precision * 10) + (fmt[0] - '0'); | |
| fmt++; | |
| } | |
| } | |
| } | |
| } | |
| // handle integer size overrides | |
| switch (fmt[0]) { | |
| case 'h': { // are we 64-bit | |
| flags |= Print_Flags_INT16; | |
| fmt++; | |
| if (fmt[0] == 'h') { | |
| flags &= ~cast_val(Uint32, Print_Flags_INT16); | |
| flags |= Print_Flags_INT8; | |
| fmt++; | |
| } | |
| } break; | |
| case 'l': { // are we 64-bit | |
| flags |= Print_Flags_INT64; | |
| fmt++; | |
| if (fmt[0] == 'l') fmt++; | |
| } break; | |
| case 'z': { // are we 64-bit on size_t? | |
| flags |= (sizeof(Size) == 8) ? Print_Flags_INT64 : 0; | |
| fmt++; | |
| } break; | |
| case 't': { // are we 64-bit on ptrdiff_t? | |
| flags |= (sizeof(Dptr) == 8) ? Print_Flags_INT64 : 0; | |
| fmt++; | |
| } break; | |
| default: { | |
| } break; | |
| } | |
| # define PRINT_STR_SIZE 2048ULL | |
| Char num_str[PRINT_STR_SIZE]; | |
| Char *num_str_ptr = num_str; | |
| Char *str = nullptr; | |
| Char head_str[8] = {0}; | |
| Size head_index = 0; | |
| Char tail_str[8] = {0}; | |
| Size tail_index = 0; | |
| Size len = 0; | |
| switch (fmt[0]) { // handle each replacement | |
| case 's': { // string | |
| // get the string | |
| str = va_arg(va, Char*); | |
| if (str == nullptr) { | |
| str = cast_const(Char *, "null"); | |
| } | |
| // By this point, str is most definitely not nullptr | |
| len = strlen(str); | |
| // clamp to precision | |
| // Since precision inits at -1, if none was mentioned, this will not execute | |
| if (len > cast_val(Size, precision)) { | |
| len = cast_val(Size, precision); | |
| } | |
| precision = 0; | |
| } break; | |
| case 'c': { // char | |
| // get the character | |
| str = num_str_ptr + PRINT_STR_SIZE - 1; | |
| *str = cast_val(Char, va_arg(va, Sint)); | |
| len = 1; | |
| precision = 0; | |
| } break; | |
| case 'n': { // weird write-bytes specifier | |
| if (flags & Print_Flags_INT64) { | |
| Sint64 *p = va_arg(va, Sint64*); | |
| *p = cast_val(Sint64, buffer_index); | |
| } else if (flags & Print_Flags_INT16) { | |
| Sint16 *p = va_arg(va, Sint16*); | |
| *p = cast_val(Sint16, buffer_index); | |
| } else if (flags & Print_Flags_INT8) { | |
| Sint8 *p = va_arg(va, Sint8*); | |
| *p = cast_val(Sint8, buffer_index); | |
| } else { | |
| Sint *p = va_arg(va, Sint*); | |
| *p = cast_val(Sint, buffer_index); | |
| } | |
| } break; | |
| case 'b': case 'B': | |
| case 'o': case 'O' : | |
| case 'x': case 'X': { // binary | |
| enum { BIN, OCT, HEX } base; | |
| Bool upper = false; | |
| Char type = fmt[0]; | |
| switch (type) { | |
| case 'b': base = BIN; break; | |
| case 'B': base = BIN; upper = true; break; | |
| case 'o': base = OCT; break; | |
| case 'O': base = OCT; upper = true; break; | |
| case 'x': base = HEX; break; | |
| case 'X': base = HEX; upper = true; break; | |
| default: { | |
| base = BIN; // To silence unitialized variable warning | |
| debugAssert(false && "Print: Integer base unitialized"); | |
| } break; | |
| } | |
| Uint64 num; | |
| if (flags & Print_Flags_INT64) { | |
| num = va_arg(va, Uint64); | |
| } else { | |
| num = va_arg(va, Uint32); | |
| } | |
| Uint64 num_dec = num; | |
| if (flags & Print_Flags_INT8) { | |
| num_dec = cast_val(Uint8, num_dec); | |
| } else if (flags & Print_Flags_INT16) { | |
| num_dec = cast_val(Uint16, num_dec); | |
| } | |
| str = num_str_ptr + PRINT_STR_SIZE; | |
| if ((num == 0) && (precision == 0)) { | |
| break; | |
| } | |
| for (;;) { | |
| Uint64 and_mask = 0; | |
| switch (base) { | |
| case BIN: and_mask = 0x1ULL; break; | |
| case OCT: and_mask = 0x7ULL; break; | |
| case HEX: and_mask = 0xFULL; break; | |
| default: debugBreak(); break; | |
| } | |
| Uint64 shift_magnitude = 0; | |
| switch (base) { | |
| case BIN: shift_magnitude = 1ULL; break; | |
| case OCT: shift_magnitude = 3ULL; break; | |
| case HEX: shift_magnitude = 4ULL; break; | |
| default: debugBreak(); break; | |
| } | |
| Uint64 n = num_dec & and_mask; | |
| num_dec = num_dec >> shift_magnitude; | |
| str--; | |
| switch (base) { | |
| case BIN: { | |
| *str = (n == 1) ? '1' : '0'; | |
| } break; | |
| case OCT: { | |
| switch (n) { | |
| case 0: *str = '0'; break; | |
| case 1: *str = '1'; break; | |
| case 2: *str = '2'; break; | |
| case 3: *str = '3'; break; | |
| case 4: *str = '4'; break; | |
| case 5: *str = '5'; break; | |
| case 6: *str = '6'; break; | |
| case 7: *str = '7'; break; | |
| default: debugBreak(); break; | |
| } | |
| } break; | |
| case HEX: { | |
| switch (n) { | |
| case 0x0: *str = '0'; break; | |
| case 0x1: *str = '1'; break; | |
| case 0x2: *str = '2'; break; | |
| case 0x3: *str = '3'; break; | |
| case 0x4: *str = '4'; break; | |
| case 0x5: *str = '5'; break; | |
| case 0x6: *str = '6'; break; | |
| case 0x7: *str = '7'; break; | |
| case 0x8: *str = '8'; break; | |
| case 0x9: *str = '9'; break; | |
| case 0xA: *str = upper ? 'A' : 'a'; break; | |
| case 0xB: *str = upper ? 'B' : 'b'; break; | |
| case 0xC: *str = upper ? 'C' : 'c'; break; | |
| case 0xD: *str = upper ? 'D' : 'd'; break; | |
| case 0xE: *str = upper ? 'E' : 'e'; break; | |
| case 0xF: *str = upper ? 'F' : 'f'; break; | |
| default: debugBreak(); break; | |
| } | |
| } break; | |
| default: debugBreak(); break; | |
| } | |
| if ((num_dec != 0) || (((num_str_ptr + PRINT_STR_SIZE) - str) < precision)) { | |
| continue; | |
| } else { | |
| break; | |
| } | |
| } | |
| len = (cast_ptr(Uptr, num_str_ptr) + PRINT_STR_SIZE) - cast_ptr(Uptr, str); | |
| Char head_char = 0; | |
| switch (base) { | |
| case BIN: head_char = upper ? 'B' : 'b'; break; | |
| case OCT: head_char = upper ? 'O' : 'o'; break; | |
| case HEX: head_char = upper ? 'X' : 'x'; break; | |
| default: debugBreak(); break; | |
| } | |
| if (flags & Print_Flags_ALTERNATE_FORM) { | |
| head_str[head_index++] = '0'; | |
| if (upper) { | |
| head_str[head_index++] = head_char; | |
| } else { | |
| head_str[head_index++] = head_char; | |
| } | |
| } | |
| if (precision < 0) { | |
| precision = 0; | |
| } | |
| } break; | |
| case 'u': | |
| case 'd': { // integer | |
| // get the integer and abs it | |
| Uint64 num = 0; | |
| if (flags & Print_Flags_INT64) { | |
| Sint64 i64 = va_arg(va, Sint64); | |
| if ((fmt[0] != 'u') && (i64 < 0)) { | |
| num = 0ULL - cast_val(Uint64, i64); | |
| flags |= Print_Flags_NEGATIVE; | |
| } else { | |
| num = cast_val(Uint64, i64); | |
| } | |
| } else { | |
| Sint32 i = va_arg(va, Sint32); | |
| if ((fmt[0] != 'u') && (i < 0)) { | |
| flags |= Print_Flags_NEGATIVE; | |
| num = cast_val(Uint64, 0U - cast_val(Uint32, i)); | |
| } else { | |
| num = cast_val(Uint64, i); | |
| } | |
| } | |
| // convert to string | |
| Uint64 num_dec = num; | |
| if (flags & Print_Flags_INT8) { | |
| num_dec = cast_val(Uint8, num_dec); | |
| } else if (flags & Print_Flags_INT16) { | |
| num_dec = cast_val(Uint16, num_dec); | |
| } | |
| str = num_str_ptr + PRINT_STR_SIZE; | |
| if ((num == 0) && (precision == 0)) { | |
| break; | |
| } | |
| while (num_dec) { | |
| str--; | |
| *str = cast_val(Char, num_dec % 10) + '0'; | |
| num_dec /= 10; | |
| } | |
| // get the length that we copied | |
| len = (cast_ptr(Uptr, num_str_ptr) + PRINT_STR_SIZE) - cast_ptr(Uptr, str); | |
| if (len == 0) { | |
| --str; | |
| *str = '0'; | |
| len = 1; | |
| } | |
| if (flags & Print_Flags_NEGATIVE) { | |
| head_str[head_index++] = '-'; | |
| } | |
| if (flags & Print_Flags_LEADING_PLUS) { | |
| head_str[head_index++] = '+'; | |
| } | |
| if (flags & Print_Flags_LEADING_SPACE) { | |
| if (!(flags & Print_Flags_NEGATIVE)) { | |
| head_str[head_index++] = ' '; | |
| } | |
| } | |
| if (flags & Print_Flags_LEADING_ZERO) { | |
| head_str[head_index++] = '0'; | |
| } | |
| if (precision < 0) { | |
| precision = 0; | |
| } | |
| } break; | |
| case 'p': { // pointer | |
| const void *pv = va_arg(va, void*); | |
| const Uptr pval = cast_ptr(Uptr, pv); | |
| const Uint64 num = cast_val(Uint64, pval); | |
| debugAssert(sizeof(pval) <= sizeof(num)); | |
| Uint64 num_dec = num; | |
| str = num_str_ptr + PRINT_STR_SIZE; | |
| for (;;) { | |
| Uint64 n = num_dec & 0xf; | |
| num_dec = num_dec >> 4; | |
| str--; | |
| switch (n) { | |
| case 0x0: *str = '0'; break; | |
| case 0x1: *str = '1'; break; | |
| case 0x2: *str = '2'; break; | |
| case 0x3: *str = '3'; break; | |
| case 0x4: *str = '4'; break; | |
| case 0x5: *str = '5'; break; | |
| case 0x6: *str = '6'; break; | |
| case 0x7: *str = '7'; break; | |
| case 0x8: *str = '8'; break; | |
| case 0x9: *str = '9'; break; | |
| case 0xA: *str = 'A'; break; | |
| case 0xB: *str = 'B'; break; | |
| case 0xC: *str = 'C'; break; | |
| case 0xD: *str = 'D'; break; | |
| case 0xE: *str = 'E'; break; | |
| case 0xF: *str = 'F'; break; | |
| default: debugBreak(); break; | |
| } | |
| if ((num_dec != 0) || (((num_str_ptr + PRINT_STR_SIZE) - str) < precision)) { | |
| continue; | |
| } else { | |
| break; | |
| } | |
| } | |
| len = (cast_ptr(Uptr, num_str_ptr) + PRINT_STR_SIZE) - cast_ptr(Uptr, str); | |
| } break; | |
| case 'f': case 'F': | |
| case 'e': case 'E': | |
| case 'a': case 'A': { | |
| switch (fmt[0]) { | |
| case 'f': case 'F': { flags |= Print_Flags_FLOAT_FIXED; } break; | |
| case 'e': case 'E': { flags |= Print_Flags_FLOAT_EXP; } break; | |
| case 'a': case 'A': { flags |= Print_Flags_FLOAT_HEX; } break; | |
| default: break; | |
| } | |
| Bool capital = false; | |
| switch (fmt[0]) { | |
| case 'F': case 'E': case 'A': { capital = true; } break; | |
| default: break; | |
| } | |
| Bool precision_provided = precision >= 0; | |
| if (flags & Print_Flags_FLOAT_HEX) { | |
| // Default precision is set to -1, so this means that if precision is not mentioned, | |
| // we print 13 digits which is enough for a lossless roundtrip. | |
| if (precision < 0) { | |
| precision = 13; | |
| } else if (precision > 17) { | |
| precision = 13; | |
| } | |
| } else { | |
| // Since decimal (non-hex) is for human reading, 6 is the right precision | |
| // (considering that it is also default in libc). | |
| if (precision < 0) { | |
| precision = 6; | |
| } else if (precision > 17) { | |
| precision = 17; | |
| } | |
| } | |
| Float64 f64 = va_arg(va, Float64); | |
| union loadump_64 ld64 = {.f64 = f64}; | |
| Uint64 bits = ld64.u64; | |
| /* NOTE(naman): 64-bit IEEE-754 Floating Point structure | |
| ____________________________________________ | |
| | Sign bit | Exponent bits | Mantissa bits| | |
| | 1 | 11 | 52 | | |
| -------------------------------------------- | |
| */ | |
| #define PRINT_F64_MANTISSA_BITS 52 | |
| #define PRINT_F64_EXPONENT_BITS 11 | |
| #define PRINT_F64_BIAS 1023 | |
| // Is the top bit set? | |
| Bool negative = bits >> (PRINT_F64_MANTISSA_BITS + PRINT_F64_EXPONENT_BITS); | |
| // Remove all mantissa bits ------------------ and then reset the sign bit | |
| Uint32 exponent_biased = ((cast_val(Uint32, bits >> PRINT_F64_MANTISSA_BITS)) & | |
| ((1U << PRINT_F64_EXPONENT_BITS) - 1U)); | |
| Sint32 exponent = cast_val(Sint32, exponent_biased) - PRINT_F64_BIAS; | |
| // Reset all bits except for the mantissa bits | |
| Uint64 mantissa = bits & ((1ULL << PRINT_F64_MANTISSA_BITS) - 1ULL); | |
| if (negative) { | |
| flags |= Print_Flags_NEGATIVE; | |
| } | |
| { // Leading String | |
| if (flags & Print_Flags_NEGATIVE) { | |
| head_str[head_index++] = '-'; | |
| } else if (flags & Print_Flags_LEADING_PLUS) { | |
| head_str[head_index++] = '+'; | |
| } else if (flags & Print_Flags_LEADING_SPACE) { | |
| if (!(flags & Print_Flags_NEGATIVE)) { | |
| head_str[head_index++] = ' '; | |
| } | |
| } | |
| if ((flags & Print_Flags_LEADING_ZERO) && | |
| !(flags & Print_Flags_FLOAT_HEX)) { | |
| head_str[head_index++] = '0'; | |
| } | |
| } | |
| // Step 1: Print if it is NaN/inf or zero | |
| if (exponent_biased == 0x7FF) { // Handle NaN and Inf | |
| str = num_str; | |
| if (capital) { | |
| if (mantissa) { | |
| *str++ = 'N'; | |
| *str++ = 'A'; | |
| *str++ = 'N'; | |
| } else { | |
| *str++ = 'I'; | |
| *str++ = 'N'; | |
| *str++ = 'F'; | |
| } | |
| } else { | |
| if (mantissa) { | |
| *str++ = 'n'; | |
| *str++ = 'a'; | |
| *str++ = 'n'; | |
| } else { | |
| *str++ = 'i'; | |
| *str++ = 'n'; | |
| *str++ = 'f'; | |
| } | |
| } | |
| } else if (exponent_biased == 0 && mantissa == 0) { | |
| if (flags & Print_Flags_FLOAT_HEX) { | |
| head_str[head_index++] = '0'; | |
| head_str[head_index++] = capital ? 'X' : 'x'; | |
| } | |
| str = num_str; | |
| *str++ = '0'; | |
| if (precision > 0) { | |
| *str++ = '.'; | |
| memset(str, '0', cast_val(Size, precision)); | |
| str += precision; | |
| } | |
| if (flags & Print_Flags_FLOAT_EXP) { | |
| tail_str[tail_index++] = capital ? 'E' : 'e'; | |
| tail_str[tail_index++] = '+'; | |
| tail_str[tail_index++] = '0'; | |
| tail_str[tail_index++] = '0'; | |
| } else if (flags & Print_Flags_FLOAT_HEX) { | |
| tail_str[tail_index++] = capital ? 'P' : 'p'; | |
| tail_str[tail_index++] = '+'; | |
| tail_str[tail_index++] = '0'; | |
| } | |
| } | |
| // Normalized means that the float can always be represented as | |
| // mantissa_normalised * 2^exponent_normalised | |
| Uint64 mantissa_normalised; | |
| Sint32 exponent_normalised; | |
| if ((exponent_biased == 0) && (mantissa != 0)) { // Subnormal/Denormals | |
| // Value of float is: | |
| // (mantissa / 2^52) * 2^(1-1023) | |
| // => mantissa * 2^(1 - 1023 - 52) | |
| // => mantissa * 2^(-1074) | |
| // Meaning, mantissa: mantissa ; exponent: -1074 | |
| mantissa_normalised = mantissa; | |
| exponent_normalised = -1074; | |
| } else { | |
| // If the float is normal, the value is: | |
| // (1 + (mantissa / 2^52)) * 2^(exponent) | |
| // => (2^52 + mantissa) * 2^(exponent - 52) | |
| // Meaning, mantissa: 2^52 + mantissa ; exponent: exponent - 52 | |
| mantissa_normalised = mantissa | (1ULL << 52); | |
| exponent_normalised = exponent - 52; | |
| } | |
| // Step 2: Print if it a hexadecimal float | |
| if ((str == nullptr) && (flags & Print_Flags_FLOAT_HEX)) { | |
| head_str[head_index++] = '0'; | |
| head_str[head_index++] = capital ? 'X' : 'x'; | |
| Uint64 man = mantissa_normalised; | |
| Sint32 exp = exponent_normalised + PRINT_F64_MANTISSA_BITS; | |
| if (mantissa || precision) { | |
| /* This makes it so that the MSB of mantissa is on | |
| * 60th bit, and the normal/denormal bit (that we set above) | |
| * is on 61st. This is done so that we can later extract the | |
| * bit a nibble at a time by taking the top 4 bits repeatedly like this: | |
| * (man >> 60) & 0xF; man <<= 4; | |
| * This prevents having to do more complex masking. | |
| * | |
| * To find out how much to shift, we need to know how many nibbles | |
| * need to be printed, and then shift so that that many nibbles end up | |
| * in the front (when going from MSB to LSB). That calculation is: | |
| * bitsInFloat64 - (bitsInNibble * (MANTISSA_BITS/bitsInNibble + 1)) | |
| * => 64 - (4 * (52/4 + 1)) | |
| * => 64 - (4 * (13 + 1)) | |
| * => 64 - (4 * 14) | |
| * => 64 - 56 | |
| * => 8 | |
| * (1 at the end comes to handle the 53rd implicit bit that we just added above) | |
| */ | |
| man <<= 8; | |
| /* This will do the rounding if the precision is smaller than maximum. | |
| * It does this by incrementing the MSB of mantissa that won't be printed. | |
| * Doing that will send a carry forward if the nibble associated with | |
| * that MSB was >= 8; and not if it was < 7. Similarly, the carry will | |
| * propagate further, rounding each nibble to its nearest value, | |
| * with ties to even (i.e., round-to-nearest-even) which is considered default. | |
| */ | |
| if (precision < 13) { | |
| /* Now that we have moved all the nibbles to the top with `man <<= 8`, | |
| * the bit index of the first nibble that will NOT be printed | |
| * is (59 - 4*precision). | |
| */ | |
| const Uint64 one_at_unprinted_msb_if_precision_is[13] = { | |
| 1ULL << (59 - 4 * 0), // 576460752303423488ULL, 0x800000000000000 | |
| 1ULL << (59 - 4 * 1), // 36028797018963968ULL, 0x80000000000000 | |
| 1ULL << (59 - 4 * 2), // 2251799813685248ULL, 0x8000000000000 | |
| 1ULL << (59 - 4 * 3), // 140737488355328ULL, 0x800000000000 | |
| 1ULL << (59 - 4 * 4), // 8796093022208ULL, 0x80000000000 | |
| 1ULL << (59 - 4 * 5), // 549755813888ULL, 0x8000000000 | |
| 1ULL << (59 - 4 * 6), // 34359738368ULL, 0x800000000 | |
| 1ULL << (59 - 4 * 7), // 2147483648ULL, 0x80000000 | |
| 1ULL << (59 - 4 * 8), // 134217728ULL, 0x8000000 | |
| 1ULL << (59 - 4 * 9), // 8388608ULL, 0x800000 | |
| 1ULL << (59 - 4 * 10), // 524288ULL, 0x80000 | |
| 1ULL << (59 - 4 * 11), // 32768ULL, 0x8000 | |
| 1ULL << (59 - 4 * 12), // 2048ULL, 0x800 | |
| }; | |
| // Various masks used for rounding | |
| Uint64 one_at_unprinted_msb = one_at_unprinted_msb_if_precision_is[precision]; | |
| Uint64 ones_after_unprinted_msb = one_at_unprinted_msb - 1ULL; | |
| Uint64 zeroes_at_unprinted = ~((ones_after_unprinted_msb << 1) | 1); | |
| Uint64 one_at_printed_lsb = one_at_unprinted_msb << 1; | |
| // https://medium.com/angular-in-depth/how-to-round-binary-fractions-625c8fa3a1af | |
| // Truncate the mantissa so zero all bits that won't be printed due to precision | |
| Uint64 lower = man & zeroes_at_unprinted; | |
| // Set most significant unprinted bit (not zero) to one | |
| Uint64 middle = lower + one_at_unprinted_msb; | |
| // Add one bit at least significant printed bit (and deal with any carry forwards) | |
| Uint64 upper = lower + one_at_printed_lsb; | |
| if (man < middle) { | |
| man = lower; | |
| } else if (man > middle) { | |
| man = upper; | |
| } else { // man == middle | |
| // Resolve tie to nearest even | |
| if ((lower & one_at_printed_lsb) == 0) { | |
| man = lower; | |
| } else { | |
| man = upper; | |
| } | |
| } | |
| } | |
| Char const *hexs = capital ? "0123456789ABCDEF" : "0123456789abcdef"; | |
| str = num_str; | |
| // This prints 0/1 depending on normal/denormal status | |
| *str++ = hexs[(man >> 60) & 0xF]; | |
| man <<= 4; | |
| if (precision) *str++ = '.'; | |
| Sint32 n = precision; | |
| Uint32 count_of_end_zeroes = 0; | |
| while (n--) { | |
| if ((man >> 60) & 0xF) { | |
| count_of_end_zeroes = 0; | |
| } else { | |
| count_of_end_zeroes++; | |
| } | |
| *str++ = hexs[(man >> 60) & 0xF]; | |
| man <<= 4; | |
| } | |
| if (precision_provided) { | |
| count_of_end_zeroes = 0; | |
| } | |
| // str -= count_of_end_zeroes; // Apparently, ending zeroes ARE printed | |
| unused_variable(count_of_end_zeroes); | |
| } else { | |
| len = 0; | |
| } | |
| { | |
| tail_str[tail_index++] = capital ? 'P' : 'p'; | |
| tail_str[tail_index++] = exp >= 0 ? '+' : '-'; | |
| exp = exp > 0 ? exp : -exp; | |
| Char es[4] = {0}; | |
| Char *ep = &es[elemin(es)]; | |
| Char el = 0; | |
| if (exp == 0) { | |
| *--ep = '0'; | |
| el++; | |
| } | |
| while (exp) { | |
| *--ep = cast_val(Char, exp % 10) + '0'; | |
| el++; | |
| exp /= 10; | |
| } | |
| while (el) { | |
| el--; | |
| tail_str[tail_index++] = *ep++; | |
| } | |
| } | |
| } | |
| Bool rounding_up_added_a_new_integer_digit_on_the_left = true; | |
| // If this is true, str will be set to num_str at the end, else to num_str+1. | |
| // This is needed to deal with the carry digit when rounding due to less precision. | |
| // Step 3: Print if it is the usual float to decimal situation | |
| // FIXME(naman): This uses bigint arithmetic, which makes it quite slow (especially | |
| // for doubles with huge integral parts). See about replacing it with | |
| // the Swift's dtoa that uses a improved Grisu2/Errol3 algorithm: | |
| // https://github.com/swiftlang/swift/blob/main/stdlib/public/runtime/SwiftDtoa.cpp | |
| if (str == nullptr) { // If still unprinted, print through the long method | |
| rounding_up_added_a_new_integer_digit_on_the_left = false; | |
| str = num_str_ptr + 1; | |
| // This algorithm is basically a fixed-point exact-conversion | |
| // implementation (inspired by Tei Andu: https://pastebin.com/z9hYEWF1). | |
| // Since this implementation is extremely simple, we don't try to find | |
| // the minimum digits that allow round tripping, especially since | |
| // `printf` like interfaces don't allow asking for it either. | |
| // If you want round-tripping, print as a hex-float, or | |
| // print with precision set to 17. | |
| // If M = mantissa_normalised and X = exponent_normalised, | |
| // then M should be interpreted as a number of shape 1.pqrs or 0.pqrs | |
| // Multiplying with 2^X turns this into a real floating point value: | |
| // v = M.2^X | |
| // But X can be both positive and negative, which means that it could move | |
| // the binary point in M right or left. And even if it is positive, it may not | |
| // move the binary point enough for M to become an integer, which is what | |
| // we want for easy processing. | |
| // So, we multiply the whole value `v` with a huge number 2^p, where `p` | |
| // is chosen so that it can overpower even the most negative X so that | |
| // the whole number `v` becomes a single integer. | |
| // N = v.2^p = M.2^X.2^p | |
| // => N = v.2^p = M.2^(X+p) | |
| // => N = v.2^p = M.2^L | |
| // What this means is that we shift the mantissa M left by X+p (=L) bits to turn | |
| // the whole number into a giant integer. Later, we can extract the integral | |
| // and fractions parts (`I` and `F`) of the original value `v` using: | |
| // I = floor(N/2^p) = N >> p | |
| // F = N mod 2^p = N & ((1 << p) - 1) | |
| // Now, L has to be greater than or equal to zero, since shifting right is stupid, | |
| // even if the mantissa is already integral. So, | |
| // L >= 0 | |
| // => X+p >= 0 | |
| // => p >= -X | |
| // If `p` is greater than `-X`, we need highest value of `-X` which means we need | |
| // lowest value of `X`. So let's find Xmin: | |
| // First, let's say that the number of exponent bits is k. | |
| // Then, bias is equal to 2^(k-1) - 1 | |
| // And since the values of exponent_biased can not be 0 and 111...1 | |
| // since these values are reserved for NaN and Inf, exponent_biased's range is | |
| // [1, 2^k - 2]. | |
| // To remove bias, we subtract it, so: | |
| // exponent = exponent_biased - bias | |
| // => exponent = [1, 2^k - 2] - (2^(k-1) - 1) | |
| // => exponent = [1, 2^k - 2] - 2^(k-1) + 1 | |
| // => exponent = [1 - 2^(k-1) + 1, 2.2^(k-1) - 2^(k-1) + 1] | |
| // => exponent = [2 - 2^(k-1), 2^(k-1) - 1] | |
| // For Float32, this is [-126, 127] and for Float64, this is [-1022, 1023] | |
| // Since X = exponent - mantissa_bits, | |
| // X for Float32 is [-126-23, 127-23] => [-149, 104] | |
| // X for Float64 is [-1022-52, 1023-52] => [-1074, 971] | |
| // Therefore, coming to the inequality, | |
| // p >= -X | |
| // => p >= -(-1074) | |
| // => p >= 1074 | |
| // Similarly to find out what is the maximum possible number of bits in `N`, we need | |
| // to add the already existing bits in mantissa and how much we shifted them by. Thus, | |
| // h = L + mantissa_bits | |
| // => h = X + p + mantissa_bits | |
| // => h = (exponent - mantissa_bits) + p + mantissa_bits | |
| // => h = exponent + p | |
| // Let's say that the big int that will store the shifted mantissa has B bits. | |
| // Accounting for one carry bit, this means that, | |
| // h <= B - 1 | |
| // => exponent + p <= B - 1 | |
| // => p <= B - 1 - exponent | |
| // For inequality to hold forever, (B - 1 - exponent) has to be minimum, meaning | |
| // that `exponent` has to be maximum. | |
| // => p <= B - 1 - 1023 | |
| // => p <= B - 1024 | |
| // Combining the two inequalities about `p`, we get | |
| // 1074 <= B - 1024 | |
| // => B >= 2098 | |
| // Since the big int B will be composed of Uint64s, we need to find out how many | |
| // Uint64s it will take. 2098/(8*8) is 32.78, and the closest larger number that | |
| // can be easily cleanly divided in two in 34. However, the Ryan Juckett algorithm used | |
| // bigints with 35 Uint32s, so maybe its safer to use 36 Uint64s, meaning: | |
| // B = 2304 | |
| Uint64 bigint[36] = {0}; | |
| // Once we set B, we can derive p by: | |
| // p <= 2304 - 1024 | |
| // => p <= 1280 | |
| // and p >= 1074 | |
| // The ideal choice is p = 1152, since it is B/2 (easy to deal with in various ways). | |
| Uint pivot = 1152; // p above, 160 in reference | |
| Sint total_left_shift_signed = exponent_normalised + cast_val(Sint, pivot); | |
| debugAssert(total_left_shift_signed >= 0); | |
| Uint total_left_shift = cast_val(Uint, total_left_shift_signed); | |
| // `L` above, `mlsft` in reference | |
| // The casting is safe since pivot is chosen to be big enough that it makes | |
| // even fully negative X become positive after addition. | |
| // Because pivot is divided by 64 evenly (1152/64=18), the pivot bit will fall | |
| // on an element boundary. This means that in the total_left_shift, the shift | |
| // contributed by pivot always ends at a element boundary, and then the shift by | |
| // exponent_normalised (whether positive or negative) moves the mantissa bits | |
| // left or right, potentially out of alignment with that element boundary. | |
| Uint pivot_elem = pivot/64; | |
| // Since we are using Uint64s for bigint, we need to calculate which elements | |
| // of the array do the bits of mantissa get stored in first, and which bits get | |
| // stored where. | |
| // N = M.2^L | |
| // => N = M.2^(64*(L/64) + (L%64)) | |
| // If elems_shifted = L/64, and bits_shifted = L%64 | |
| // => N = M.2^(bits_shifted).2^(64*elems_shifted) | |
| // This means we first shift by enough bits to move the mantissa into the right | |
| // relative position between two elements. Then we shift across the as many word | |
| // until the mantissa is in the right place. | |
| // NOTE(naman): The LSB->MSB of our mantissa is stored from index 0->35 | |
| // So the best way to visualize the bigint array is: | |
| // bigint = [ 35 | 34 | 33 | 32 | ... | 3 | 2 | 1 | 0 ] | |
| // with the bits in each element going: | |
| // element = [ 63 | 62 | 61 | ... | 3 | 2 | 1 | 0 ] | |
| // In the rest of the comments below, I will refer to range within | |
| // the array as [larger, smaller] indices, since that is what makes | |
| // sense here. | |
| Uint elems_shifted = total_left_shift >> 6; // L / 64, `a` in reference | |
| Uint bits_shifted = total_left_shift & ((1u << 6) - 1u); // L % 64, `b` in reference | |
| bigint[elems_shifted] = mantissa_normalised << bits_shifted; // Mantissa's LSB | |
| if (bits_shifted) { | |
| bigint[elems_shifted+1] = mantissa_normalised >> (64u - bits_shifted); // Mantissa's MSB | |
| } | |
| // The `I` (integer part) is in [35, pivot_elem] and the `F` (fractional part) is | |
| // in [pivot_elem-1, 0]. | |
| Char *str_int_begin = str; | |
| Char *str_int_end; | |
| { // Print integral part | |
| // The basic algorithm is to divide by 10 continuously and collect the remainder. | |
| // This remainder is the digit to be printed in the next place value. Of course, | |
| // when we are dealing with bigint, we have to modify the algorithm like this: | |
| // carry = 0 | |
| // for (&elem in integral_bigint from left to right) { | |
| // current = carry.2^64 + *elem | |
| // *elem = floor(current/10) | |
| // carry = current % 10 | |
| // } | |
| // As an example, lets say we have the number 5150, and we are dealing with | |
| // two digits at a time. Diving by 10 will mean first doing 51/10=5 and getting | |
| // carry of 1. Then, we multiply the carry 1 with 100, and add it to 50 to get 150, | |
| // which is divided again as 150/10=15. Thus, the final quotient is 515. | |
| // Now, doing the (carry.2^64 + *elem) will require more than 64-bits, which we don't | |
| // have. Maybe we could do it in 128-bits, but there is an easier hack. What if we break | |
| // this operation into two 32-bit ops? That way, we can get it done directly without | |
| // needing any new helper functions or data types. | |
| // Let start with: | |
| // current = carry.2^64 + *elem | |
| // Divide *elem in two 32-bit parts, `hi` and `lo` | |
| // *elem = hi.2^32 + lo | |
| // Let's say: | |
| // cur_hi = carry.2^32 + hi | |
| // and cur_hi = 10.q_hi + r_hi // Quotient and Remainder | |
| // Similarly, | |
| // cur_lo = r_hi.2^32 + lo | |
| // and cur_lo = 10.q_lo + r_lo | |
| // Combining: | |
| // current = carry.2^64 + *elem | |
| // => current = carry.2^64 + hi.2^32 + lo | |
| // => current = 2^32.(carry.2^32 + hi) + lo | |
| // => current = 2^32.cur_hi + lo | |
| // => current = 2^32.(10.q_hi + r_hi) + lo | |
| // => current = 2^32.10.q_hi + 2^32.r_hi + lo | |
| // => current = (q_hi << 32).10 + cur_lo | |
| // => current = (q_hi << 32).10 + 10.q_lo + r_lo | |
| // => current = 10.((q_hi << 32) + q_lo) + r_lo | |
| // And so, we can calculate: | |
| // floor(current/10) = (q_hi << 32) +q_lo | |
| // and current % 10 = r_lo | |
| Bool non_zero_digits_left = false; | |
| Sint non_zero_starts_at = cast_val(Sint, elems_shifted + 1); | |
| str_int_begin = str; | |
| do { | |
| Uint32 carry = 0; | |
| non_zero_digits_left = false; | |
| for (Sint i = non_zero_starts_at; i >= cast_val(Sint, pivot_elem); i--) { | |
| Uint64 elem = bigint[i]; | |
| Uint32 hi = cast_val(Uint32, elem >> 32); | |
| Uint32 lo = cast_val(Uint32, elem); | |
| Uint64 cur_hi = (cast_val(Uint64, carry) << 32) + hi; | |
| Uint32 q_hi = cast_val(Uint32, cur_hi / 10u); | |
| Uint32 r_hi = cast_val(Uint32, cur_hi % 10u); | |
| Uint64 cur_lo = (cast_val(Uint64, r_hi) << 32) + lo; | |
| Uint32 q_lo = cast_val(Uint32, cur_lo / 10u); | |
| Uint32 r_lo = cast_val(Uint32, cur_lo % 10u); | |
| bigint[i] = (cast_val(Uint64, q_hi) << 32) + q_lo; | |
| carry = r_lo; | |
| Bool is_zero = bigint[i] == 0; | |
| if ((i == non_zero_starts_at) && is_zero) { | |
| non_zero_starts_at--; | |
| } | |
| non_zero_digits_left |= !is_zero; | |
| } | |
| debugAssert(carry < 10); | |
| *str++ = cast_val(Char, carry) + '0'; | |
| } while (non_zero_digits_left); | |
| str_int_end = str - 1; | |
| // Reverse the printed string | |
| Char *begin = str_int_begin; | |
| Char *end = str_int_end; | |
| while (end > begin) { | |
| Char swap = *end; | |
| *end-- = *begin; | |
| *begin++ = swap; | |
| } | |
| } | |
| *str++ = '.'; | |
| Char *str_frc_begin = str; | |
| Char *str_frc_end; | |
| { // Print Fractional part | |
| // When I say that `F` is the fractional part, what I mean is that the | |
| // `F` is an integer that will make the fractional part if the number was | |
| // written in the format `I.F` (where the dot is the decimal point, not | |
| // multiplication. But as it is, if we simply were to read the bits in | |
| // [pivot_elem-1, 0], we just get an integer F that represents | |
| // the fractional part. So, to actually get the digits out of it, | |
| // we will have to figure out how to treat the integer F as a fraction. | |
| // Also, remember that we have a total `pivot` number of bits in F, | |
| // meaning that the decimal point (after multiplying 2^p originally when | |
| // making the whole floating point an integer) is on the pivot point. | |
| // So, the way to get the fractional part is to read only what comes after | |
| // the pivot. | |
| // To calculate the digit: | |
| // d = floor((10 * F)/2^p) | |
| // What this means is that we first multiply F by 10 (thus moving one digit to | |
| // the left of the decimal point), and then right-shift by p-bits so that all | |
| // the numbers right of the decimal point disappear, leaving only the one digit | |
| // that was to the left of the point. | |
| // Once this is done, before we can do the next digit, we change F with: | |
| // F = (10 * F) % 2^p | |
| // Basically, this gives us the bits right of the decimal point after multiplying | |
| // with 10. | |
| // Since we are dealing with bigint, the multiplication algorithm has to be: | |
| // carry = 0 | |
| // while (&elem in fractional_bigint from right to left) { | |
| // product = *elem * 10 | |
| // *elem = carry + (product % 2^64) | |
| // carry = product / 2^64 | |
| // } | |
| // As an example, lets say we have the number 0.5432, and we are dealing with | |
| // two digits at a time. Multiplying with 10 will mean first doing 32*10=320 | |
| // and getting result of 20 and carry of 3. Then, we multiply 54 with 10 | |
| // to get 540 to which we add the carry 3, getting 543. Again, 43 remain and 5 | |
| // becomes the carry. This 5 is the digit extracted, since the number now is | |
| // 5.432, with 5 stored in carry and 4320 being left for future digit extraction. | |
| // Similar to the integral part, doing the (*elem * 10) and (carry + product) | |
| // will require more than 64-bits, which we don't have. So, let's try to do this | |
| // with 32 bit ops again. | |
| // Let's start with: | |
| // product = *elem * 10 | |
| // Divide *elem in two 32-bit parts, `hi` and `lo` | |
| // *elem = hi.2^32 + lo | |
| // Let's say: | |
| // prod_lo = lo * 10 | |
| // prod_lo_mod = prod_lo % 2^32 | |
| // elem_lo = carry + prod_lo_mod | |
| // carry_lo = prod_lo / 2^32 | |
| // | |
| // prod_hi = hi * 10 | |
| // elem_hi = carry_lo + (prod_hi % 2^32) | |
| // carry_hi = prod_hi / 2^32 | |
| // | |
| // *elem = elem_hi.2^32 + elem_lo | |
| // carry = carry_hi | |
| Bool non_zero_digits_left = false; | |
| Uint fraction_starts_at = elems_shifted; | |
| Uint digit_count = 0; | |
| // This loop will always print atleast one digit | |
| do { | |
| Uint32 carry = 0; | |
| non_zero_digits_left = false; | |
| for (Uint32 i = fraction_starts_at; i < pivot_elem; i++) { | |
| Uint64 elem = bigint[i]; | |
| Uint32 hi = cast_val(Uint32, elem >> 32); | |
| Uint32 lo = cast_val(Uint32, elem); | |
| Uint64 prod_lo = cast_val(Uint64, lo) * 10u; | |
| Uint32 prod_lo_mod = cast_val(Uint32, prod_lo); | |
| Uint32 elem_lo = carry + prod_lo_mod; | |
| Uint32 carry_lo = cast_val(Uint32, prod_lo >> 32); | |
| if (elem_lo < prod_lo_mod) { // Overflow when adding carry | |
| carry_lo++; | |
| } | |
| Uint64 prod_hi = cast_val(Uint64, hi) * 10u; | |
| Uint32 prod_hi_mod = cast_val(Uint32, prod_hi); | |
| Uint32 elem_hi = carry_lo + prod_hi_mod; | |
| Uint32 carry_hi = cast_val(Uint32, prod_hi >> 32); | |
| if (elem_hi < prod_hi_mod) { // Overflow when adding carry | |
| carry_hi++; | |
| } | |
| bigint[i] = (cast_val(Uint64, elem_hi) << 32) | elem_lo; | |
| carry = carry_hi; | |
| Bool is_zero = bigint[i] == 0; | |
| if ((i == fraction_starts_at) && is_zero) { | |
| fraction_starts_at++; | |
| } | |
| non_zero_digits_left |= !is_zero; | |
| } | |
| if (digit_count < (cast_val(Uint, precision) + 1)) { | |
| debugAssert(carry < 10); | |
| *str++ = cast_val(Char, carry) + '0'; | |
| digit_count++; | |
| } else { | |
| // Even after printing (precision + 1) digits, the remaining fraction is | |
| // not zero. So we mark that condition by setting digit_count to a value | |
| // one greater than that. | |
| digit_count = cast_val(Uint, precision) + 2; | |
| } | |
| } while (non_zero_digits_left && (digit_count <= (cast_val(Uint, precision) + 1))); | |
| str_frc_end = str - 1; | |
| unused_variable(str_frc_end); // Prevent "set but unused" warnings | |
| // TODO(naman): Add support for printing floats in exponential notation (A.BCe+D) | |
| // This may be useful: https://gist.github.com/forksnd/c9a91957e9e5c933635148d3b74f7d65 | |
| // Now, do rounding based on round-half-to-even (Banker's rounding) | |
| // https://en.wikipedia.org/wiki/Rounding#Rounding_half_to_even | |
| // The basic algorithm is this, given precision after decimal point is p, | |
| // 1. Let rounding_digit be the digit at position (p+1) | |
| // 2. Let tail be all digits that come after rounding_digit | |
| // 3. If rounding_digit > 5, round up | |
| // 4. If rounding_digit < 5, round down (do nothing) | |
| // 5. If rounding_digit == 5, | |
| // A. If tail has any non-zero digits: round up | |
| // B. If tail is all zeroes, | |
| // a) If digit at position p is odd, round up | |
| // b) If digit at position p is even, do nothing | |
| // The edge cases to keep in mind are: | |
| // 1. precision == 0, meaning there is no fractional part | |
| // 2. Round-up cascade reaching into integral part from fractional part | |
| // 3. For negative numbers, rounding is done to magnitude and sign is added later | |
| Bool round_up; | |
| if (digit_count <= cast_val(Uint, precision)) { // We needed more fractional digits | |
| // We didn't collect (precision + 1) digits, so non_zero_digits_left must have | |
| // become false, meaning that there is a bunch of zeroes that should trail our | |
| // number. | |
| if (cast_val(Uint, precision) > digit_count) { | |
| Uint zero_count = cast_val(Uint, precision) - digit_count; | |
| // Trailing zeroes | |
| memset(str, '0', zero_count); | |
| str += zero_count; | |
| str_frc_end = str - 1; | |
| } | |
| // Since the fraction ended prematurely, there is no rounding needed either. | |
| round_up = false; | |
| } else { // We got all the fractional digits we needed | |
| const Char rounding_digit = str_frc_begin[precision]; | |
| // Bool tail_is_nonzero = ((digit_count > (cast_val(Uint, precision) + 1)) || | |
| // non_zero_digits_left); | |
| Bool tail_is_nonzero; | |
| if (digit_count > (cast_val(Uint, precision) + 1)) { | |
| // We captured more than precision+1 digits => there is a non-zero tail | |
| tail_is_nonzero = true; | |
| } else if (digit_count < (cast_val(Uint, precision) + 1)) { | |
| // We didn't even capture the rounding digit => fraction ended early => no tail | |
| tail_is_nonzero = false; | |
| } else { | |
| // exactly precision+1 digits were captured — use the post-multiply flag | |
| tail_is_nonzero = non_zero_digits_left; | |
| } | |
| if (rounding_digit > '5') { | |
| round_up = true; | |
| } else if (rounding_digit < '5') { | |
| round_up = false; | |
| } else { // rounding_digit == 5 | |
| if (tail_is_nonzero) { | |
| round_up = true; | |
| } else { | |
| Char prev; | |
| if (precision == 0) { // No fraction digits, rounding baed on integer | |
| // We have still printed one fraction digit in the (precision + 1) | |
| // slot, which is why the (rounding_digit == 5) condition is hit. | |
| prev = *str_int_end; | |
| } else { // Fraction digit present, rounding based on them | |
| prev = str_frc_begin[precision - 1]; | |
| } | |
| Bool prev_is_odd = (prev - '0') & 1; | |
| round_up = prev_is_odd; | |
| } | |
| } | |
| if (round_up) { | |
| Sint k = precision - 1; // -1 if precision == 0 | |
| while (k >= 0) { // Only run if there are fractional digits to print | |
| if (str_frc_begin[k] < '9') { // Can be rounded up in place | |
| str_frc_begin[k]++; // Round up | |
| break; // and we are done | |
| } else { // str_frc_begin[k] == '9' | |
| debugAssert(str_frc_begin[k] == '9'); | |
| str_frc_begin[k] = '0'; // 9+1 = 10, so 0 here and 1 carries over | |
| k--; // We need to apply the carry to the digit on the left | |
| } | |
| } | |
| if (k < 0) { // Either no fractional digits, or 1 carried from fractional rounding | |
| Char *int_digit = str_int_end; | |
| while (int_digit >= str_int_begin) { | |
| if (*int_digit < '9') { | |
| (*int_digit)++; | |
| break; | |
| } else { | |
| *int_digit = '0'; | |
| int_digit--; | |
| } | |
| } | |
| if (int_digit < str_int_begin) { // One more carry | |
| debugAssert(int_digit == (str_int_begin - 1)); | |
| rounding_up_added_a_new_integer_digit_on_the_left = true; | |
| *int_digit = '1'; | |
| str_int_begin--; | |
| } | |
| } | |
| } | |
| str = str_frc_begin + precision; | |
| } | |
| } | |
| } | |
| len = cast_val(Uptr, str - num_str_ptr); | |
| if (rounding_up_added_a_new_integer_digit_on_the_left) { | |
| str = num_str_ptr; | |
| } else { | |
| str = num_str_ptr + 1; | |
| len -= 1; | |
| } | |
| precision = 0; | |
| } break; | |
| case '%': { | |
| str = num_str_ptr; | |
| str[0] = '%'; | |
| len = 1; | |
| precision = 0; | |
| } break; | |
| case '\0': { | |
| // If the format string ends prematurely, we print | |
| // whatever half-formed conversion specification came through; | |
| // to do that, we decrement the pointer since we need to make | |
| // sure that we don't end up copying the terminating null byte, | |
| // since that will be put in the final output at the end. We | |
| // also want to decrement since after the next fmt++ (at the | |
| // end of the function), we want fmt to point to the null byte | |
| // so that the priting will properly end. | |
| fmt--; | |
| goto nlib_print_unimplemented_feature; | |
| } break; | |
| default: { // unknown, just copy conversion specification | |
| nlib_print_unimplemented_feature: | |
| str = num_str_ptr; | |
| while (format_pointer <= fmt) { | |
| str[0] = format_pointer[0]; | |
| format_pointer++; | |
| str++; | |
| } | |
| len = cast_val(Size, str - num_str_ptr); | |
| str = num_str_ptr; | |
| precision = 0; | |
| } break; | |
| } | |
| Size head_size = head_index; | |
| head_index = 0; | |
| Size tail_size = tail_index; | |
| tail_index = 0; | |
| // get minimum_width=leading/trailing space, precision=leading zeros | |
| if (cast_val(Size, precision) < len) { | |
| precision = cast_val(Sint, len); | |
| } | |
| Sint zeros_head_tail = (precision + | |
| cast_val(Sint, head_size) + | |
| cast_val(Sint, tail_size)); | |
| if (minimum_width < zeros_head_tail) { | |
| minimum_width = zeros_head_tail; | |
| } | |
| minimum_width -= zeros_head_tail; | |
| precision -= cast_val(Sint, len); | |
| // handle right justify and leading zeros | |
| if ((flags & Print_Flags_LEFT_JUSTIFIED) == 0) { | |
| if (flags & Print_Flags_LEADING_ZERO) { // then everything is in precision | |
| precision = (minimum_width > precision) ? minimum_width : precision; | |
| minimum_width = 0; | |
| } | |
| } | |
| // copy leading spaces | |
| if ((minimum_width + precision) > 0) { | |
| // copy leading spaces (or when doing %8.4d stuff) | |
| if ((flags & Print_Flags_LEFT_JUSTIFIED) == 0) { | |
| for (Sint i = 0; i < minimum_width; i++) { | |
| PRINT_ADD_CHAR(' '); | |
| } | |
| } | |
| for (Size i = 0; i < head_size; i++) { // copy the head | |
| PRINT_ADD_CHAR(head_str[i]); | |
| } | |
| head_index += head_size; | |
| for (Sint i = 0; i < precision; i++) { // copy leading zeros | |
| PRINT_ADD_CHAR('0'); | |
| } | |
| } | |
| if (head_index < head_size) { // copy the head | |
| Size repeat = head_size - head_index; | |
| for (Size i = 0; i < repeat; i++) { | |
| PRINT_ADD_CHAR(head_str[i]); | |
| } | |
| head_index += repeat; | |
| } | |
| for (Size i = 0; i < len; i++) { // copy the string | |
| PRINT_ADD_CHAR(str[i]); | |
| } | |
| str += len; | |
| for (Size i = 0; i < tail_size; i++) { // copy the tail | |
| PRINT_ADD_CHAR(tail_str[i]); | |
| } | |
| // handle the left justify | |
| if (flags & Print_Flags_LEFT_JUSTIFIED) { | |
| for (Sint i = 0; i < minimum_width; i++) { | |
| PRINT_ADD_CHAR(' '); | |
| } | |
| } | |
| fmt++; | |
| } else if (fmt[0] =='\0') { | |
| break; | |
| } | |
| } | |
| if (buffer_index) { | |
| callback(userdata, buffer, buffer_index, buffer_size); | |
| } | |
| #undef PRINT_ADD_CHAR | |
| return total_size; | |
| } | |
| /************************************************************************************** | |
| * Memory | |
| */ | |
| header_function | |
| void* memoryAlignUpPtrTo (void *p, Size align) | |
| { | |
| Size k = align - 1LLU; | |
| Size not_k = ~k; | |
| Size up = cast_bit(Uptr, p) + k; | |
| Size result_uptr = up & cast_val(Uptr, not_k); | |
| void *result = cast_bit(void *, result_uptr); | |
| return result; | |
| } | |
| header_function | |
| Size memoryAlignUpSizeTo (Size s, Size align) | |
| { | |
| Size k = align - 1LLU; | |
| Size result = (s + k) & (~ k); | |
| return result; | |
| } | |
| header_function | |
| void* memoryAlignDownPtrTo (void *p, Size align) | |
| { | |
| Byte *pb = cast_val(Byte *, p); | |
| Size k = align - 1LLU; | |
| Byte *result = cast_val(Byte *, memoryAlignUpPtrTo(pb - k, align)); | |
| return result; | |
| } | |
| header_function | |
| Size memoryAlignDownSizeTo (Size s, Size align) | |
| { | |
| Size k = align - 1LLU; | |
| Size result = memoryAlignUpSizeTo(s - k, align); | |
| return result; | |
| } | |
| #if defined(ENV_LANG_C) && ENV_LANG_C >= 2011 | |
| # define memoryAlignUpTo(x, a) _Generic((x), void*: memoryAlignUpPtrTo, Size:memoryAlignUpSizeTo)(x, a) | |
| # define memoryAlignDownTo(x, a) _Generic((x), void*: memoryAlignDownPtrTo, Size:memoryAlignDownSizeTo)(x, a) | |
| #elif defined(ENV_LANG_CXX) | |
| header_function void* memoryAlignUpTo (void *p, Size align) { return memoryAlignUpPtrTo(p, align); } | |
| header_function Size memoryAlignUpTo (Size s, Size align) { return memoryAlignUpSizeTo(s, align); } | |
| header_function void* memoryAlignDownTo (void *p, Size align) { return memoryAlignDownPtrTo(p, align); } | |
| header_function Size memoryAlignDownTo (Size s, Size align) { return memoryAlignDownSizeTo(s, align); } | |
| #endif | |
| # define memoryAlignUp(x) (memoryAlignUpTo(x, alignof(max_align_t))) | |
| # define memoryAlignDown(x) (memoryAlignDownTo(x, alignof(max_align_t))) | |
| #define aligned_sizeof(x) memoryAlignUp(sizeof(x)) | |
| #define MEMORY_ALLOT_FUNC(name) void* name (void *userdata, Size amount) | |
| #define MEMORY_REMIT_FUNC(name) void name (void *userdata, void *ptr) | |
| /************************************************************************************* | |
| * Free Grid | |
| */ | |
| /* | |
| * row mask |‾‾‾‾‾‾‾‾‾‾side‾‾‾‾‾‾‾‾‾| | |
| * lsb | |
| * 1 FF FF FF FF FF ‾| | |
| * 1 FF FF FF FF FF | | |
| * 1 FF FF 07 00 00 side | |
| * 0 00 00 00 00 00 | | |
| * 0 00 00 00 00 00 _| | |
| * msb | |
| * 1 1 1 1 1 <- col mask | |
| * lsb msb | |
| */ | |
| // TODO(naman): Here, the "correct" thing to do would have been to | |
| // calculate the `side` variable using `sqrt(elem_count)`. | |
| #define FG_SIDE 64u | |
| // NOTE(naman): 2^18 is 64*64*64 (= 262144), and that is the maximum count we support | |
| #define FREE_GRID_MAXIMUM_ELEMENTS (1U << 18) | |
| typedef struct Free_Grid { | |
| Uint64 masks[FG_SIDE * FG_SIDE * sizeof(Uint64)]; | |
| Uint8 count_masks_in_row[FG_SIDE]; | |
| Uint8 count_masks_in_col[FG_SIDE]; | |
| Uint64 row_mask; | |
| Uint64 col_mask; | |
| Uint32 count; | |
| Uint32 allocated; | |
| } Free_Grid; | |
| header_function | |
| void fgCreate (Free_Grid *fg, Uint32 elem_count) | |
| { | |
| debugAssert(elem_count <= FREE_GRID_MAXIMUM_ELEMENTS); | |
| memzero(fg); | |
| fg->count = elem_count; | |
| Uint32 fully_marked_mask_count = elem_count / 64; | |
| Uint32 extra_marked_bits_count = elem_count % 64; | |
| if (fully_marked_mask_count) memset(fg->masks, 0xFF, fully_marked_mask_count * sizeof(Uint64)); | |
| if (extra_marked_bits_count) { | |
| fg->masks[fully_marked_mask_count] |= ((~cast_val(Uint64, 0x0ull)) >> (64 - extra_marked_bits_count)); | |
| } | |
| Uint32 total_marked_masks = fully_marked_mask_count + (extra_marked_bits_count ? 1 : 0); | |
| Uint32 fully_filled_rows = total_marked_masks / FG_SIDE; | |
| Uint8 partially_filled_row_size = total_marked_masks % FG_SIDE; | |
| Uint32 total_rows = fully_filled_rows + (partially_filled_row_size ? 1 : 0); | |
| fg->row_mask = ((~cast_val(Uint64, 0x0ull)) >> (64 - total_rows)); | |
| Uint32 col_bits_count = fully_filled_rows ? FG_SIDE : partially_filled_row_size; | |
| fg->col_mask = ((~cast_val(Uint64, 0x0ull)) >> (64 - minimum(FG_SIDE, col_bits_count))); | |
| for (Size i = 0; i < fully_filled_rows; i++) { | |
| fg->count_masks_in_row[i] = FG_SIDE; | |
| } | |
| if (partially_filled_row_size) { | |
| pragma_msvc("warning ( push )"); | |
| pragma_msvc("warning ( disable: 6386 )"); // Analyze: WRITE_OVERRUN (Buffer overrun while writing to 'fg->count_masks_in_row') | |
| fg->count_masks_in_row[fully_filled_rows] = partially_filled_row_size; | |
| pragma_msvc("warning ( pop )"); | |
| } | |
| for (Size i = 0; i < FG_SIDE; i++) { | |
| fg->count_masks_in_col[i] = cast_val(Uint8, fully_filled_rows); | |
| if (partially_filled_row_size && (i < partially_filled_row_size)) { | |
| fg->count_masks_in_col[i]++; | |
| } | |
| } | |
| return; | |
| } | |
| header_function | |
| void fgDelete (Free_Grid *fg) | |
| { | |
| unused_variable(fg); | |
| } | |
| header_function | |
| Bool fgAllocate (Free_Grid *fg, Uint32 *index) | |
| { | |
| Uint8 row_first_bit_index = bitLSBU64(fg->row_mask); | |
| if (row_first_bit_index == cast_val(Uint8, -1)) { | |
| return false; | |
| } | |
| debugAssert(row_first_bit_index < 64); | |
| Uint8 col_first_bit_index = bitLSBU64(fg->col_mask); | |
| debugAssert(col_first_bit_index != cast_val(Uint8, -1)); | |
| while (!fg->masks[(row_first_bit_index * FG_SIDE) + col_first_bit_index]) { | |
| col_first_bit_index++; | |
| } | |
| debugAssert(col_first_bit_index < 64); | |
| Uint32 mask_array_index = row_first_bit_index * FG_SIDE + col_first_bit_index; | |
| debugAssert(mask_array_index < (64*64)); | |
| debugAssert(fg->masks[mask_array_index]); | |
| Uint8 mask_first_bit_index = bitLSBU64(fg->masks[mask_array_index]); | |
| debugAssert(mask_first_bit_index != cast_val(Uint8, -1)); | |
| *index = mask_array_index * 64 + cast_val(Uint32, mask_first_bit_index); | |
| debugAssert(*index < fg->count); | |
| fg->masks[mask_array_index] &= ~(1ULL << mask_first_bit_index); | |
| if (fg->masks[mask_array_index] == 0) { | |
| fg->count_masks_in_row[row_first_bit_index]--; | |
| fg->count_masks_in_col[col_first_bit_index]--; | |
| if (fg->count_masks_in_row[row_first_bit_index] == 0) { | |
| fg->row_mask &= ~(1ULL << row_first_bit_index); | |
| } | |
| if (fg->count_masks_in_col[col_first_bit_index] == 0) { | |
| fg->col_mask &= ~(1ULL << col_first_bit_index); | |
| } | |
| } | |
| fg->allocated++; | |
| return true; | |
| } | |
| header_function | |
| void fgFree (Free_Grid *fg, Uint32 index) | |
| { | |
| debugAssert(index < fg->count); | |
| Uint32 fully_traversed_mask_index = (index + 1) / 64; | |
| Uint32 extra_untreaded_bits_count = (index + 1) % 64; | |
| Uint32 total_relevant_mask_count = fully_traversed_mask_index + (extra_untreaded_bits_count ? 1 : 0); | |
| Uint32 final_relevant_mask_index = total_relevant_mask_count - 1; | |
| Uint32 row_bit_index = final_relevant_mask_index / FG_SIDE; | |
| Uint32 col_bit_index = final_relevant_mask_index - (row_bit_index * FG_SIDE); | |
| Uint32 relevant_bit_index; | |
| if (extra_untreaded_bits_count) { | |
| relevant_bit_index = extra_untreaded_bits_count - 1; | |
| } else { | |
| relevant_bit_index = 63; | |
| } | |
| debugAssert((fg->masks[final_relevant_mask_index] & (1ULL << relevant_bit_index)) == 0); | |
| Uint64 old_mask = fg->masks[final_relevant_mask_index]; | |
| fg->masks[final_relevant_mask_index] |= (1ULL << relevant_bit_index); | |
| if (old_mask == 0) { | |
| fg->count_masks_in_row[row_bit_index]++; | |
| fg->count_masks_in_col[col_bit_index]++; | |
| fg->row_mask |= (1ULL << row_bit_index); | |
| fg->col_mask |= (1ULL << col_bit_index); | |
| } | |
| } | |
| #undef FG_SIZE | |
| /************************************************************************************************ | |
| * TLSF Allocator | |
| * -------------- | |
| * | |
| * The following is a sample table with buffer_size = GiB(4), min_alloc = MiB(1) and increment_step = 8 | |
| * Each row is primary level, where the buffer is divided into units of size being consecutive powers of two | |
| * (geometric progression). Each column them breaks each unit into a linear sub-unit of size being a constant | |
| * increment (arithmetic progression). | |
| * | |
| * ________________________________________________________________________________________________________________________ | |
| * | | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | |
| * |----|-------|---------------|--------------|--------------|--------------|--------------|--------------|--------------| | |
| * | 31 | 2G | 2G+256M | 2G+512M | 2G+768M | 3G | 3G+256M | 3G+512M | 3G+768M | | |
| * | 30 | 1G | 1G+128M | 1G+256M | 1G+384M | 1G+512M | 1G+640M | 1G+768M | 1G+896M | | |
| * | 29 | 512M | 576M | 640M | 704M | 768M | 832M | 896M | 960M | | |
| * | 28 | 256M | 288M | 320M | 352M | 384M | 416M | 448M | 480M | | |
| * | 27 | 128M | 144M | 160M | 176M | 192M | 208M | 224M | 240M | | |
| * | 26 | 64M | 72M | 80M | 88M | 96M | 104M | 112M | 120M | | |
| * | 25 | 32M | 36M | 40M | 44M | 48M | 52M | 56M | 60M | | |
| * | 24 | 16M | 18M | 20M | 22M | 24M | 26M | 28M | 30M | | |
| * | 23 | 8M | 9M | 10M | 11M | 12M | 13M | 14M | 15M | | |
| * | 22 | 4M | 4M+512K | 5M | 5M+512K | 6M | 6M+512K | 7M | 7M+512K | | |
| * | 21 | 2M | 2M+256K | 2M+512K | 2M+768K | 3M | 3M+256K | 3M+512K | 3M+768K | | |
| * | 20 | 1M | 1M+128K | 1M+256K | 1M+384K | 1M+512K | 1M+640K | 1M+768K | 1M+896K | | |
| * |------------|---------------|--------------|--------------|--------------|--------------|--------------|--------------| | |
| * | |
| * Reference: | |
| * [1] : http://www.gii.upv.es/tlsf/files/papers/tlsf_desc.pdf | |
| * [2] : http://www.gii.upv.es/tlsf/files/papers/ecrts04_tlsf.pdf | |
| * [3] : http://www.gii.upv.es/tlsf/files/papers/jrts2008.pdf | |
| */ | |
| // TODO(naman): Right now, this is 64-bytes large. Once I am sure that the allocator is bug-free (i.e., once it has been | |
| // tested enough), rename `next_slot_available_for_dispensing` and `next_free` to `next` and `prev_free` to `prev`, since a slot can either | |
| // be empty or house a free node but not both. Also, store the `allocated` bool in the top bit of `size`. That will drop the | |
| // size of the struct to 48-bytes, reducing the metadata size by close to 25%. | |
| typedef struct TLSF_Node { | |
| Uint64 offset; | |
| Uint64 size; | |
| // Contains the pointer to the next slot from which a node can be dispensed in case of out-of-band allocation | |
| struct TLSF_Node *next_slot_available_for_dispensing; | |
| // For free blocks. The next member of the node contains the index of the next node in chain corresponding to another | |
| // free block in the same size class. The index of chain's head is stored in the free_table. | |
| struct TLSF_Node *next_free, *prev_free; | |
| // For allocated and free blocks. The following members contain the index of the nodes associated with the blocks right | |
| // after and before them in the address space. | |
| struct TLSF_Node *next_mem, *prev_mem; | |
| Bool allocated; // false = free (make sure it's not left true for empty slots) | |
| Byte _pad[7]; | |
| } TLSF_Node; | |
| static_assert(sizeof(TLSF_Node) % alignof(max_align_t) == 0, \ | |
| "TLSF Nodes need to be of the same size as alignment, " \ | |
| "so that they can be packed in the node_dispenser, etc."); | |
| #define TLSF_INCREMENT_COUNT_PER_ROW 16 | |
| typedef struct TLSF { | |
| MEMORY_ALLOT_FUNC((*m_allot)); | |
| MEMORY_REMIT_FUNC((*m_remit)); | |
| void *m_datum; | |
| Uint64 buffer_size; | |
| Uint64 min_alloc; | |
| Uint32 rows, columns; | |
| // One bit for each row, to show if any bit in the corresponding row_bitmap is set. | |
| // Bitmap's LSB->MSB is bottom->top row | |
| Uint64 column_bitap; | |
| // One bitmap per row, with each bit standing for one cell. | |
| // Index 0->N maps to bottom->top row, bitmap's LSB->MSB is left->right cell | |
| Uint64 row_bitmaps[64]; | |
| TLSF_Node* free_table[64][TLSF_INCREMENT_COUNT_PER_ROW]; | |
| union { // Both point to TLSF->data | |
| Byte *memory; // In-place memory allocator | |
| TLSF_Node *node_dispenser; // Out-of-band 64-bit address space allocator | |
| }; | |
| TLSF_Node *first_slot_available_for_dispensing; // If this is NULL, we are dealing with in-place memory allocator | |
| Byte data[]; | |
| } TLSF; | |
| static_assert(sizeof(TLSF) % alignof(max_align_t) == 0, | |
| "TLSF's size is not divisible by alignof(max_align_t), thus data[] member will be out of alignment"); | |
| #define TLSF_HEAD_OF_FREE_LIST_(t, r, c) ((t)->free_table[(r)][(c)]) | |
| // sl = log2(size) | |
| // ml = log2(min_alloc) | |
| // il = log2(increment) | |
| // | |
| // r = sl - ml | |
| // c = (size - 2^sl) / ((2*2^sl - 2^sl)/2^il) | |
| // = (size - 2^sl) / (2^sl*(2 - 1) / 2^il) | |
| // = (size - 2^sl) / (2^sl / 2^il) | |
| // = (size - 2^sl) / (2^(sl-il)) | |
| // = (size/2^(sl-il)) - (2^sl-(2^(sl-il))) | |
| // = (size/2^(sl-il)) - 2^il | |
| // = (size >> (sl-il)) - 2^il | |
| // = (size >> (sl-il)) - (1 << il) | |
| #define TLSF_ROW_(t, s) cast_val(Uint8, (bitLog2U64(s) - bitLog2U64((t)->min_alloc))) | |
| #define TLSF_COL_(t, s) cast_val(Uint8, (((s) >> (bitLog2U64(s) - bitLog2U64(cast_val(Uint64, ((t)->columns))))) - \ | |
| (1ULL << bitLog2U64(cast_val(Uint64, ((t)->columns)))))) | |
| // NOTE(naman): All three parameters should be powers of two with value >= 2. | |
| // buffer_size should be greater than min_alloc | |
| // NOTE(naman): With (buffer_size=512 MiB, min_alloc=32 KiB), it will need slightly more than 1 MiB of metadata. | |
| // WARN(naman): When using with in-band memory, make sure to add the sizeof(TLSF_Node) to min_alloc | |
| // before calling this function. | |
| #define tlsfCreate(_miface, _bufsz, _minal) tlsfCreateEx(_miface, _bufsz, _minal, true) | |
| header_function | |
| TLSF* tlsfCreateEx (MEMORY_ALLOT_FUNC((*m_allot)), MEMORY_REMIT_FUNC((*m_remit)), void *m_datum, | |
| Uint64 buffer_size, Uint64 min_alloc, | |
| Bool out_of_band_metadata) | |
| { | |
| Uint8 increment_steps = 16; | |
| Uint8 buffer_size_bits = bitLog2U64(buffer_size); | |
| Uint8 min_alloc_bits = bitLog2U64(min_alloc); | |
| Uint8 row_count = cast_val(Uint8, buffer_size_bits - min_alloc_bits); | |
| Size maximum_allocation_count = buffer_size / min_alloc; | |
| Size total_size = aligned_sizeof(TLSF); | |
| if (out_of_band_metadata) { | |
| Size list_size = maximum_allocation_count * sizeof(TLSF_Node); | |
| total_size += memoryAlignUp(list_size); | |
| } else { | |
| total_size += memoryAlignUp(buffer_size); | |
| } | |
| TLSF *tlsf = cast_val(TLSF *, m_allot(m_datum, total_size)); | |
| memzero(tlsf); | |
| tlsf->m_allot = m_allot; | |
| tlsf->m_remit = m_remit; | |
| tlsf->m_datum = m_datum; | |
| tlsf->buffer_size = buffer_size; | |
| tlsf->min_alloc = min_alloc; | |
| tlsf->rows = row_count; | |
| tlsf->columns = increment_steps; | |
| if (out_of_band_metadata) { | |
| pragma_clang("clang diagnostic push"); | |
| pragma_clang("clang diagnostic ignored \"-Wcast-align\""); | |
| tlsf->node_dispenser = cast_ptr(TLSF_Node *, tlsf->data); | |
| pragma_clang("clang diagnostic pop"); | |
| tlsf->first_slot_available_for_dispensing = &tlsf->node_dispenser[0]; | |
| // Put all available nodes in a list so that we can find the next one in O(1) for real-time | |
| for (Size i = 0; i < maximum_allocation_count-1; i++) { | |
| memzero(&tlsf->node_dispenser[i]); | |
| tlsf->node_dispenser[i].next_slot_available_for_dispensing = &tlsf->node_dispenser[i] + 1; | |
| } | |
| } else { | |
| asan_block(tlsf->data, buffer_size); | |
| tlsf->memory = tlsf->data; | |
| tlsf->first_slot_available_for_dispensing = nullptr; | |
| } | |
| // This macro is defined here since it only becomes valid from here | |
| // (one first_slot_available_for_dispensing has been set) | |
| # define TLSF_OOB_(t) ((t)->first_slot_available_for_dispensing) | |
| { // Adding the full buffer in free_table | |
| TLSF_Node node; | |
| memzero(&node); | |
| node.offset = 0; | |
| node.size = buffer_size - 1; | |
| Uint8 row = TLSF_ROW_(tlsf, node.size); | |
| Uint8 col = TLSF_COL_(tlsf, node.size); | |
| TLSF_Node *slot_for_full_buffer = nullptr; | |
| if (out_of_band_metadata) { | |
| slot_for_full_buffer = tlsf->first_slot_available_for_dispensing; | |
| tlsf->first_slot_available_for_dispensing = slot_for_full_buffer->next_slot_available_for_dispensing; | |
| } else { | |
| pragma_clang("clang diagnostic push"); | |
| pragma_clang("clang diagnostic ignored \"-Wcast-align\""); | |
| slot_for_full_buffer = cast_ptr(TLSF_Node *, tlsf->memory); | |
| debugAssert(cast_ptr(Uptr, slot_for_full_buffer) % alignof(max_align_t) == 0); | |
| pragma_clang("clang diagnostic pop"); | |
| } | |
| if (!TLSF_OOB_(tlsf)) asan_allow(slot_for_full_buffer, sizeof(*slot_for_full_buffer)); | |
| *slot_for_full_buffer = node; | |
| if (!TLSF_OOB_(tlsf)) asan_block(slot_for_full_buffer, sizeof(*slot_for_full_buffer)); | |
| TLSF_HEAD_OF_FREE_LIST_(tlsf, row, col) = slot_for_full_buffer; | |
| tlsf->column_bitap |= (1ULL << row); | |
| tlsf->row_bitmaps[row] |= (1ULL << col); | |
| } | |
| return tlsf; | |
| } | |
| header_function | |
| void tlsfDelete (TLSF *tlsf) | |
| { | |
| MEMORY_REMIT_FUNC((*m_remit)) = tlsf->m_remit; | |
| void *m_datum = tlsf->m_datum; | |
| m_remit(m_datum, tlsf); | |
| } | |
| header_function | |
| void tlsf_AddNodeToFreeList (TLSF *tlsf, TLSF_Node *node) | |
| { | |
| if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node)); | |
| Size row = TLSF_ROW_(tlsf, node->size); // fl in [1] | |
| Size col = TLSF_COL_(tlsf, node->size); // sl in [1] | |
| // Add node to free list | |
| node->prev_free = nullptr; | |
| node->next_free = TLSF_HEAD_OF_FREE_LIST_(tlsf, row, col); | |
| if (TLSF_HEAD_OF_FREE_LIST_(tlsf, row, col)) { | |
| TLSF_Node *head = TLSF_HEAD_OF_FREE_LIST_(tlsf, row, col); | |
| if (!TLSF_OOB_(tlsf)) asan_allow(head, sizeof(*head)); | |
| head->prev_free = node; | |
| if (!TLSF_OOB_(tlsf)) asan_block(head, sizeof(*head)); | |
| } | |
| if (!TLSF_OOB_(tlsf)) asan_block(node, sizeof(*node)); | |
| // Add node to table | |
| TLSF_HEAD_OF_FREE_LIST_(tlsf, row, col) = node; | |
| // Update bitmap | |
| tlsf->row_bitmaps[row] |= (1ULL << col); | |
| tlsf->column_bitap |= (1ULL << row); | |
| } | |
| header_function | |
| void tlsf_RemoveNodeFromFreeList (TLSF *tlsf, TLSF_Node *node) | |
| { | |
| if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node)); | |
| Size row = TLSF_ROW_(tlsf, node->size); // fl in [1] | |
| Size col = TLSF_COL_(tlsf, node->size); // sl in [1] | |
| // Remove node from the free list | |
| if (node->next_free) { | |
| if (!TLSF_OOB_(tlsf)) asan_allow(node->next_free, sizeof(*node->next_free)); | |
| node->next_free->prev_free = node->prev_free; | |
| if (!TLSF_OOB_(tlsf)) asan_block(node->next_free, sizeof(*node->next_free)); | |
| } | |
| if (node->prev_free) { | |
| if (!TLSF_OOB_(tlsf)) asan_allow(node->prev_free, sizeof(*node->prev_free)); | |
| node->prev_free->next_free = node->next_free; | |
| if (!TLSF_OOB_(tlsf)) asan_block(node->prev_free, sizeof(*node->prev_free)); | |
| } | |
| // Remove node from table | |
| if (TLSF_HEAD_OF_FREE_LIST_(tlsf, row, col) == node) { | |
| TLSF_HEAD_OF_FREE_LIST_(tlsf, row, col) = node->next_free; | |
| } | |
| if (!TLSF_OOB_(tlsf)) asan_block(node, sizeof(*node)); | |
| // Update bitmap | |
| if (TLSF_HEAD_OF_FREE_LIST_(tlsf, row, col) == nullptr) { // No free blocks left in that size range | |
| tlsf->row_bitmaps[row] &= ~(1ULL << col); | |
| if (tlsf->row_bitmaps[row] == 0) { | |
| tlsf->column_bitap &= ~(1ULL << row); | |
| } | |
| } | |
| } | |
| header_function | |
| TLSF_Node* tlsf_AcquireNode (TLSF *tlsf, Size offset) | |
| { | |
| TLSF_Node *result; | |
| // Find a empty slot in list and put the splitted node in it | |
| if (TLSF_OOB_(tlsf)) { | |
| result = tlsf->first_slot_available_for_dispensing; | |
| tlsf->first_slot_available_for_dispensing = result->next_slot_available_for_dispensing; | |
| } else { | |
| pragma_clang("clang diagnostic push"); | |
| pragma_clang("clang diagnostic ignored \"-Wcast-align\""); | |
| result = cast_ptr(TLSF_Node *, &tlsf->data[offset]); | |
| debugAssert(cast_ptr(Uptr, result) % alignof(max_align_t) == 0); | |
| pragma_clang("clang diagnostic pop"); | |
| } | |
| if (!TLSF_OOB_(tlsf)) asan_allow(result, sizeof(*result)); | |
| memzero(result); | |
| if (!TLSF_OOB_(tlsf)) asan_block(result, sizeof(*result)); | |
| return result; | |
| } | |
| header_function | |
| void tlsf_ReleaseNode (TLSF *tlsf, TLSF_Node *node) | |
| { | |
| if (TLSF_OOB_(tlsf)) { // Add node into the slot dispenser | |
| node->next_slot_available_for_dispensing = tlsf->first_slot_available_for_dispensing; | |
| tlsf->first_slot_available_for_dispensing = node; | |
| } | |
| } | |
| // WARN(naman): When using with in-band memory, make sure to add the sizeof(TLSF_Node) to size_requested | |
| // before calling this function. | |
| header_function | |
| TLSF_Node* tlsfAllocate (TLSF *tlsf, Size size_requested) | |
| { | |
| Size size = memoryAlignUp(maximum(size_requested, tlsf->min_alloc)); | |
| if (size > tlsf->buffer_size) return nullptr; | |
| // The next slot for size (so that we do a loop-less good fit for real-time instead of a looped best fit) | |
| // sl = log2(size) | |
| // il = log2(increment) | |
| // | |
| // gap = size_of_each_column_in_that_row = ((2*2^sl - 2^sl)/2^il) = 2^(sl-il) | |
| // size_next = size + 2^(sl-il) | |
| // However, in case of size = 2^sl, we don't want to go to the next block. Thus, we should subtract 1 from size_next, | |
| // so that we end up back in the same table slot in that particular case. | |
| // => size_next = size + 2^(sl-il) - 1 | |
| Size size_class = size + (1ULL << (bitLog2U64(size) - bitLog2U64(cast_val(Uint64, tlsf->columns)))) - 1; | |
| size_class = minimum(size_class, tlsf->buffer_size-1); | |
| Size row = TLSF_ROW_(tlsf, size_class); // fl in [1] | |
| Size col = TLSF_COL_(tlsf, size_class); // sl in [1] | |
| Uint64 row_bitmap = tlsf->row_bitmaps[row] & (0xFFFFFFFFFFFFFFFFULL << col); | |
| Size row_available, col_available; | |
| if (row_bitmap) { | |
| col_available = bitLSBU64(row_bitmap); | |
| row_available = row; | |
| } else { | |
| Uint64 col_bitmap = tlsf->column_bitap & (0xFFFFFFFFFFFFFFFFULL << (row + 1)); | |
| if (col_bitmap) { | |
| row_available = bitLSBU64(col_bitmap); | |
| col_available = bitLSBU64(tlsf->row_bitmaps[row_available]); | |
| } else { | |
| return nullptr; | |
| } | |
| } | |
| // Get the free node of the free list and mark it used | |
| TLSF_Node *free_node = TLSF_HEAD_OF_FREE_LIST_(tlsf, row_available, col_available); // This will definitely succeed, so free_node != nullptr | |
| // WARN(naman): Don't zero out free_node. Some of it's contents are used below and some are carried forward. | |
| tlsf_RemoveNodeFromFreeList(tlsf, free_node); | |
| TLSF_Node *split_node_slot = nullptr; | |
| if (!TLSF_OOB_(tlsf)) asan_allow(free_node, sizeof(*free_node)); | |
| if (free_node->size >= (size + tlsf->min_alloc)) { // If the acquired block is too big | |
| Size split_node_size = free_node->size - size; | |
| Size split_offset = free_node->offset + size; | |
| split_node_slot = tlsf_AcquireNode(tlsf, split_offset); | |
| if (!TLSF_OOB_(tlsf)) asan_allow(split_node_slot, sizeof(*split_node_slot)); | |
| split_node_slot->offset = split_offset; | |
| split_node_slot->size = split_node_size; | |
| split_node_slot->prev_mem = free_node; | |
| split_node_slot->next_mem = free_node->next_mem; | |
| if (!TLSF_OOB_(tlsf)) asan_block(split_node_slot, sizeof(*split_node_slot)); | |
| tlsf_AddNodeToFreeList(tlsf, split_node_slot); | |
| } | |
| { // Create a node for the allocated block | |
| // free_node->offset = offset; // Already set | |
| free_node->size = size; | |
| free_node->next_slot_available_for_dispensing = nullptr; | |
| free_node->next_free = nullptr; | |
| free_node->prev_free = nullptr; | |
| free_node->next_mem = split_node_slot; | |
| // free_node->prev_mem = prev_mem; // Already set | |
| free_node->allocated = true; | |
| } | |
| if (!TLSF_OOB_(tlsf)) asan_block(free_node, sizeof(*free_node)); | |
| return free_node; | |
| } | |
| header_function | |
| TLSF_Node* tlsf_MergeLeft (TLSF *tlsf, TLSF_Node *node) | |
| { | |
| if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node)); | |
| TLSF_Node *left = node->prev_mem; | |
| if (!TLSF_OOB_(tlsf)) asan_allow(left, sizeof(*left)); | |
| if (left && !left->allocated) { | |
| Size node_size_old = node->size; | |
| Size left_size_old = left->size; | |
| Size left_size_new = node_size_old + left_size_old; | |
| tlsf_RemoveNodeFromFreeList(tlsf, left); | |
| if (!TLSF_OOB_(tlsf)) asan_allow(left, sizeof(*left)); | |
| { // Remove node from the memory list | |
| left->next_mem = node->next_mem; | |
| if (left->next_mem) { | |
| if (!TLSF_OOB_(tlsf)) asan_allow(left->next_mem, sizeof(*left->next_mem)); | |
| left->next_mem->prev_mem = left; | |
| if (!TLSF_OOB_(tlsf)) asan_block(left->next_mem, sizeof(*left->next_mem)); | |
| } | |
| } | |
| tlsf_ReleaseNode(tlsf, node); | |
| if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node)); | |
| if (!node->allocated) { // If the node to be expanded is free (e.g., during deallocation) | |
| tlsf_RemoveNodeFromFreeList(tlsf, node); | |
| if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node)); | |
| left->size = left_size_new; // Updated here, so that tlsf_AddNodeToFreeList below works | |
| tlsf_AddNodeToFreeList(tlsf, left); | |
| if (!TLSF_OOB_(tlsf)) asan_allow(left, sizeof(*left)); | |
| } else { | |
| left->allocated = true; | |
| node->allocated = false; | |
| left->size = left_size_new; // Still has to be updated if node->allocated == true | |
| } | |
| if (!TLSF_OOB_(tlsf)) asan_block(node, sizeof(*node)); | |
| if (!TLSF_OOB_(tlsf)) asan_block(left, sizeof(*left)); | |
| return left; | |
| } else { | |
| if (!TLSF_OOB_(tlsf)) asan_block(node, sizeof(*node)); | |
| if (!TLSF_OOB_(tlsf)) asan_block(left, sizeof(*left)); | |
| return node; | |
| } | |
| } | |
| header_function | |
| TLSF_Node* tlsf_MergeRight (TLSF *tlsf, TLSF_Node *node) | |
| { | |
| if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node)); | |
| TLSF_Node *right = node->next_mem; | |
| if (!TLSF_OOB_(tlsf)) asan_allow(right, sizeof(*right)); | |
| if (right && !right->allocated) { | |
| Size node_size_old = node->size; | |
| Size right_size_old = right->size; | |
| Size node_size_new = node_size_old + right_size_old; | |
| tlsf_RemoveNodeFromFreeList(tlsf, right); | |
| if (!TLSF_OOB_(tlsf)) asan_allow(right, sizeof(*right)); | |
| { // Remove right from the memory list | |
| node->next_mem = right->next_mem; | |
| if (node->next_mem) { | |
| if (!TLSF_OOB_(tlsf)) asan_allow(node->next_mem, sizeof(*node->next_mem)); | |
| node->next_mem->prev_mem = node; | |
| if (!TLSF_OOB_(tlsf)) asan_block(node->next_mem, sizeof(*node->next_mem)); | |
| } | |
| } | |
| tlsf_ReleaseNode(tlsf, right); | |
| if (!TLSF_OOB_(tlsf)) asan_allow(right, sizeof(*right)); | |
| if (!node->allocated) { // If the node to be expanded is free (e.g., during deallocation) | |
| tlsf_RemoveNodeFromFreeList(tlsf, node); | |
| if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node)); | |
| node->size = node_size_new; // Updated here, so that tlsf_AddNodeToFreeList below works | |
| tlsf_AddNodeToFreeList(tlsf, node); | |
| if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node)); | |
| } else { | |
| node->size = node_size_new; // Still has to be updated if node->allocated == true | |
| } | |
| } | |
| if (!TLSF_OOB_(tlsf)) asan_block(node, sizeof(*node)); | |
| if (!TLSF_OOB_(tlsf)) asan_block(right, sizeof(*right)); | |
| return node; | |
| } | |
| // WARN(naman): When using with in-band memory, make sure to add the sizeof(TLSF_Node) to size_requested | |
| // before calling this function. | |
| header_function | |
| Bool tlsfAdjustLeft (TLSF *tlsf, TLSF_Node **node, Size new_size) | |
| { | |
| if (!(*node)->allocated) return false; | |
| *node = tlsf_MergeLeft(tlsf, *node); | |
| new_size = memoryAlignUp(maximum(new_size, tlsf->min_alloc)); | |
| if ((*node)->size < new_size) return false; | |
| Size remnant_offset = (*node)->offset; | |
| Size remnant_size = (*node)->size - new_size; | |
| if (remnant_size == 0) return true; | |
| if (remnant_size < tlsf->min_alloc) return true; | |
| { // Add a remnnant node | |
| TLSF_Node *remnant = tlsf_AcquireNode(tlsf, remnant_offset); | |
| // Size it properly | |
| remnant->offset = remnant_offset; | |
| remnant->size = remnant_size; | |
| (*node)->offset += remnant->size; | |
| (*node)->size = new_size; | |
| // Add to memory list | |
| remnant->next_mem = *node; | |
| remnant->prev_mem = (*node)->prev_mem; | |
| if ((*node)->prev_mem) (*node)->prev_mem->next_mem = remnant; | |
| (*node)->prev_mem = remnant; | |
| tlsf_AddNodeToFreeList(tlsf, remnant); | |
| } | |
| return true; | |
| } | |
| // WARN(naman): When using with in-band memory, make sure to add the sizeof(TLSF_Node) to size_requested | |
| // before calling this function. | |
| header_function | |
| Bool tlsfAdjustRight (TLSF *tlsf, TLSF_Node **node, Size new_size) | |
| { | |
| if (!(*node)->allocated) return false; | |
| *node = tlsf_MergeRight(tlsf, *node); | |
| new_size = memoryAlignUp(maximum(new_size, tlsf->min_alloc)); | |
| if ((*node)->size < new_size) return false; | |
| Size remnant_offset = (*node)->offset + new_size; | |
| Size remnant_size = (*node)->size - new_size; | |
| if (remnant_size == 0) return true; | |
| if (remnant_size < tlsf->min_alloc) return true; | |
| { // Add a remnnant node | |
| TLSF_Node *remnant = tlsf_AcquireNode(tlsf, remnant_offset); | |
| // Size it properly | |
| remnant->offset = remnant_offset; | |
| remnant->size = remnant_size; | |
| // node->offset doesn't change | |
| (*node)->size = new_size; | |
| // Add to memory list | |
| remnant->prev_mem = *node; | |
| remnant->next_mem = (*node)->next_mem; | |
| if ((*node)->next_mem) (*node)->next_mem->prev_mem = remnant; | |
| (*node)->next_mem = remnant; | |
| tlsf_AddNodeToFreeList(tlsf, remnant); | |
| } | |
| return true; | |
| } | |
| header_function | |
| Bool tlsfFree (TLSF *tlsf, TLSF_Node *node) | |
| { | |
| if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node)); | |
| if (!node->allocated) return false; | |
| if (!TLSF_OOB_(tlsf)) asan_block(node, sizeof(*node)); | |
| node = tlsf_MergeLeft(tlsf, node); | |
| node = tlsf_MergeRight(tlsf, node); | |
| tlsf_AddNodeToFreeList(tlsf, node); | |
| if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node)); | |
| node->allocated = false; | |
| if (!TLSF_OOB_(tlsf)) asan_block(node, sizeof(*node)); | |
| if (!TLSF_OOB_(tlsf)) asan_block(node, sizeof(*node)); | |
| return true; | |
| } | |
| header_function | |
| Size tlsfGetSize (TLSF *tlsf, TLSF_Node *node) | |
| { | |
| if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node)); | |
| if (!node->allocated) return 0; | |
| Size size = node->size; | |
| if (!TLSF_OOB_(tlsf)) asan_block(node, sizeof(*node)); | |
| return size; | |
| } | |
| /************************************************************************************************ | |
| * TLSF-based General Purpose Memory Allocator | |
| */ | |
| // TODO(naman): Add support for multiple allocation buffers, so that when one buffer is filled up, | |
| // we allocate a new one to parcel new allocations out of and keep going. This will probably require | |
| // having multiple TLSF pointers | |
| typedef struct Memory { | |
| TLSF *tlsf; | |
| Size max_mem; | |
| } Memory; | |
| // NOTE(naman): With (512 MiB, 32 KiB, 16), it will need slightly more than 1 MiB of metadata. | |
| header_function | |
| Memory memCreate (MEMORY_ALLOT_FUNC((*m_allot)), MEMORY_REMIT_FUNC((*m_remit)), void *m_datum, | |
| Size memory_size) | |
| { | |
| Size max_mem = memory_size; | |
| Size min_mem = bitNextPowerOf2((2 * sizeof(TLSF_Node)) - 1); // To get atleast 50% load | |
| debugAssert(max_mem > min_mem); | |
| debugAssert(bitIsPowerOf2(max_mem)); | |
| debugAssert(bitIsPowerOf2(min_mem)); | |
| TLSF *tlsf = tlsfCreateEx(m_allot, m_remit, m_datum, max_mem, min_mem, false); | |
| Memory memory = { | |
| .tlsf = tlsf, | |
| .max_mem = max_mem, | |
| }; | |
| return memory; | |
| } | |
| header_function | |
| void memDelete (Memory *mt) | |
| { | |
| tlsfDelete(mt->tlsf); | |
| } | |
| header_function | |
| void* memAllot (Memory *mt, Size size) | |
| { | |
| debugAssert(size); | |
| Size real_size = size + sizeof(TLSF_Node); | |
| TLSF_Node *node = tlsfAllocate(mt->tlsf, real_size); | |
| Byte *result = cast_ptr(Byte *, node + 1); | |
| asan_allow(result, size); | |
| memset(result, 0, size); | |
| return result; | |
| } | |
| // Return nullptr if extention failed, so that the application knows that more memory is not available | |
| header_function | |
| void* memExtend (Memory *mt, void *ptr, Size size) | |
| { | |
| TLSF_Node *node = cast_ptr(TLSF_Node *, ptr) - 1; | |
| Size real_size = size + sizeof(TLSF_Node); | |
| Bool adjusted = tlsfAdjustLeft(mt->tlsf, &node, real_size); | |
| Byte *result = cast_ptr(Byte *, node + 1); | |
| return adjusted ? result : nullptr; | |
| } | |
| header_function | |
| void memRemit (Memory *mt, void *ptr) | |
| { | |
| debugAssert(ptr); | |
| TLSF_Node *node = cast_ptr(TLSF_Node *, ptr) - 1; | |
| Size size = tlsfGetSize(mt->tlsf, node); | |
| asan_block(ptr, size); | |
| tlsfFree(mt->tlsf, node); | |
| } | |
| header_function | |
| Size memGetSize (Memory *mt, void *ptr) | |
| { | |
| TLSF_Node *node = cast_ptr(TLSF_Node *, ptr) - 1; | |
| asan_allow(node, sizeof(*node)); | |
| Size size = tlsfGetSize(mt->tlsf, node); | |
| asan_block(node, sizeof(*node)); | |
| return size; | |
| } | |
| /***************************************************************************************** | |
| * Memory Push Allocator | |
| */ | |
| struct Memory_Push_Block_ { | |
| struct Memory_Push_Block_ *next; | |
| Size cap; | |
| Size len; | |
| Byte _pad[8]; | |
| Byte data[]; | |
| }; | |
| static_assert(offsetof(struct Memory_Push_Block_, data) % alignof(max_align_t) == 0, "The data member has to be properly aligned"); | |
| typedef struct Memory_Push { | |
| Memory *parent_memory; | |
| struct Memory_Push_Block_ *first, *last; | |
| Size size; | |
| } Memory_Push; | |
| header_function | |
| Size memPush_PaddingForAlignment (void *ptr, Size offset, Size alignment) | |
| { | |
| Uptr old_location = cast_bit(Uptr, ptr) + offset; | |
| if (old_location % alignment) { | |
| Uptr next_location = old_location + alignment; | |
| Uptr align_location = (next_location/alignment)*alignment; | |
| Uptr diff = align_location - old_location; | |
| return diff; | |
| } | |
| return 0; | |
| } | |
| header_function | |
| struct Memory_Push_Block_* memPush_AcquireMemory (Memory_Push *mp, Size size) | |
| { | |
| Size size_total = sizeof(struct Memory_Push_Block_) + size; | |
| struct Memory_Push_Block_ *block = cast_ptr(struct Memory_Push_Block_ *, | |
| memAllot(mp->parent_memory, size_total)); | |
| memzero(block); | |
| block->cap = size; | |
| block->len = 0; | |
| asan_block(block->data, block->cap); | |
| return block; | |
| } | |
| header_function | |
| void memPushCreate (Memory_Push *mp, Memory *parent_memory, Size size) | |
| { | |
| memzero(mp); | |
| mp->parent_memory = parent_memory; | |
| mp->size = size; | |
| mp->first = memPush_AcquireMemory(mp, size); | |
| mp->last = mp->first; | |
| memzero(mp->first); | |
| mp->first->cap = size; | |
| } | |
| header_function | |
| void memPushDelete (Memory_Push *push) | |
| { | |
| for (struct Memory_Push_Block_ *mpb = push->first, *mpbn; mpb != nullptr; mpb = mpbn) { | |
| mpbn = mpb->next; | |
| asan_block(mpb->data, mpb->cap); | |
| memRemit(push->parent_memory, mpb); | |
| } | |
| memzero(push); | |
| } | |
| header_function | |
| void* memPushAllotA (Memory_Push *push, Size size, Size alignment) | |
| { | |
| debugAssert(size); | |
| Size pad = memPush_PaddingForAlignment(push->last->data, push->last->len, alignment); | |
| if ((push->last->len + pad + size) > push->last->cap) { | |
| if (push->last->next == nullptr) { | |
| Size s = maximum(push->size, size) + alignment; | |
| push->last->next = memPush_AcquireMemory(push, s); | |
| push->last->next->cap = s; | |
| } | |
| push->last = push->last->next; | |
| } | |
| pad = memPush_PaddingForAlignment(push->last->data, push->last->len, alignment); | |
| push->last->len += pad; | |
| void *m = push->last->data + push->last->len; | |
| push->last->len += size; | |
| asan_allow(m, size); | |
| memset(m, 0, size); | |
| return m; | |
| } | |
| header_function | |
| void* memPushAllot (Memory_Push *push, Size size) | |
| { | |
| return memPushAllotA(push, size, alignof(max_align_t)); | |
| } | |
| header_function | |
| void memPushRemitAll (Memory_Push *push) | |
| { | |
| for (struct Memory_Push_Block_ *mpb = push->first, *mpbn; mpb != nullptr; mpb = mpbn) { | |
| mpbn = mpb->next; | |
| asan_block(mpb->data, mpb->cap); | |
| mpb->len = 0; | |
| } | |
| push->last = push->first; | |
| } | |
| /************************************************************************************************ | |
| * Memory Slab Allocator | |
| */ | |
| typedef struct Memory_Slab { | |
| Free_Grid *fg; | |
| Memory *parent_memory; | |
| void *buffer; | |
| Size memory_size; | |
| Size elem_size; | |
| Size elem_count; | |
| Size elem_allocated; | |
| } Memory_Slab; | |
| header_function | |
| void memSlabCreate (Memory_Slab *s, Memory *parent_memory, Uint32 elem_count, Size elem_size) | |
| { | |
| memzero(s); | |
| s->parent_memory = parent_memory; | |
| s->elem_count = elem_count; | |
| s->elem_size = elem_size; | |
| s->memory_size = elem_size * elem_count; | |
| s->fg = cast_val(Free_Grid*, memAllot(s->parent_memory, sizeof(*s->fg))); | |
| fgCreate(s->fg, elem_count); | |
| s->buffer = memAllot(s->parent_memory, s->memory_size); | |
| asan_block(s->buffer, s->memory_size); | |
| } | |
| header_function | |
| void memSlabDelete (Memory_Slab *s) | |
| { | |
| asan_allow(s->buffer, s->memory_size); | |
| memRemit(s->parent_memory, s->buffer); | |
| fgDelete(s->fg); | |
| } | |
| header_function | |
| Size memSlabGetIndexOfPtr (Memory_Slab *s, void *m) | |
| { | |
| Uptr offset = cast_bit(Uptr, m) - cast_bit(Uptr, s->buffer); | |
| Uptr index = offset / s->elem_size; | |
| return index; | |
| } | |
| header_function | |
| void* memSlabGetPtrOfIndex (Memory_Slab *s, Size index) | |
| { | |
| void *result = cast_val(Byte *, s->buffer) + (index * s->elem_size); | |
| return result; | |
| } | |
| header_function | |
| void* memSlabAllot (Memory_Slab *s) | |
| { | |
| debugAssert(s->elem_allocated < s->elem_count); | |
| Uint32 index = 0; | |
| debugAssert(fgAllocate(s->fg, &index)); | |
| void *result = memSlabGetPtrOfIndex(s, index); | |
| asan_allow(result, s->elem_size); | |
| memset(result, 0, s->elem_size); | |
| s->elem_allocated++; | |
| return result; | |
| } | |
| header_function | |
| void memSlabRemit (Memory_Slab *s, void *m) | |
| { | |
| debugAssert(m); | |
| debugAssert(s->elem_allocated); | |
| asan_block(m, s->elem_size); | |
| Uint32 index = cast_val(Uint32, (cast_val(Dptr, (cast_val(Byte *, m) - cast_val(Byte *, s->buffer)))/cast_val(Dptr, s->elem_size))); | |
| fgFree(s->fg, index); | |
| s->elem_allocated--; | |
| } | |
| /************************************************************************************************ | |
| * Hashing | |
| * ******* | |
| */ | |
| // https://en.wikipedia.org/wiki/Pearson_hashing | |
| header_function | |
| Uint8 hash8 (const void *data, Size len, Uint8 seed) | |
| { | |
| const Byte *d = cast_val(const Byte*, data); | |
| Uint8 H = seed; | |
| for (Size i = 0; i < len; i++) { | |
| const Uint8 A = cast_val(Uint8, H ^ d[i]); | |
| // Permutation function generated with https://programming.sirrida.de/calcperm.php using input "4 0 3 7 5 2 1 6" | |
| const Uint8 B = cast_val(Uint8, (((A & 0x41) << 1) | | |
| ((A & 0x04) << 3) | | |
| ((A & 0x02) << 5) | | |
| ((A & 0x90) >> 4) | | |
| ((A & 0x28) >> 1))); | |
| H = B; | |
| } | |
| return H; | |
| } | |
| // Reference: Fast CRCs by Gam D. Nguyen (https://arxiv.org/pdf/1009.5949) [Fig. 8, Page 14] | |
| header_function | |
| Uint16 hash16 (const void *data, Size len, Uint16 seed) | |
| { | |
| const Byte *d = cast_val(const Byte*, data); | |
| const Uint16 f = 0x7; | |
| const Uint16 k = 0x8000; | |
| Uint16 CRC = seed; | |
| Size i; | |
| // Run until zero or one bytes are left | |
| for (i = 0; (i + 2) <= len; i = i + 2) { | |
| const Uint16 hword = load16BE(d + i); | |
| Uint16 a = cast_val(Uint16, CRC ^ hword); | |
| Uint16 c; | |
| if (a & k) { | |
| c = cast_val(Uint16, (a << 1) ^ f); | |
| } else { | |
| c = cast_val(Uint16, a << 1); | |
| } | |
| if (c & k) { | |
| CRC = cast_val(Uint16, (c << 1) ^ f); | |
| } else { | |
| CRC = cast_val(Uint16, c << 1); | |
| } | |
| CRC = cast_val(Uint16, CRC ^ c ^ a); | |
| } | |
| // Finish up the last byte | |
| if ((i + 1) == len) { | |
| const Uint16 hword = cast_val(Uint16, cast_val(Uint16, load8(d + i)) << 8); | |
| Uint16 a = cast_val(Uint16, CRC ^ hword); | |
| Uint16 c; | |
| if (a & k) { | |
| c = cast_val(Uint16, (a << 1) ^ f); | |
| } else { | |
| c = cast_val(Uint16, a << 1); | |
| } | |
| if (c & k) { | |
| CRC = cast_val(Uint16, (c << 1) ^ f); | |
| } else { | |
| CRC = cast_val(Uint16, c << 1); | |
| } | |
| CRC = cast_val(Uint16, CRC ^ c ^ a); | |
| } | |
| return CRC; | |
| } | |
| // https://github.com/aappleby/smhasher/blob/0ff96f7835817a27d0487325b6c16033e2992eb5/src/MurmurHash3.cpp#L94-L146 | |
| header_function | |
| Uint32 hash32 (const void *data, Size len, Uint32 seed) | |
| { | |
| const Byte *d = cast_val(const Byte*, data); | |
| Uint32 H = seed; | |
| const Uint32 c1 = 0xcc9e2d51; | |
| const Uint32 c2 = 0x1b873593; | |
| #if defined(ENV_COMPILER_MSVC) | |
| # define ROTL32(x,y) _rotl(x,y) | |
| #elif defined(ENV_COMPILER_GCC) || defined(ENV_COMPILER_CLANG) | |
| # define ROTL32(x,y) ((x << y) | (x >> (32 - y))) | |
| #endif | |
| Size i; | |
| // Run until zero or one bytes are left | |
| for (i = 0; (i + 4) <= len; i = i + 4) { | |
| Uint32 word = load32BE(d + i); | |
| word *= c1; | |
| word = ROTL32(word, 15); | |
| word *= c2; | |
| H ^= word; | |
| H = ROTL32(H, 13); | |
| H = H * 5 + 0xe6546b64; | |
| } | |
| { // Finish up the remaining bytes | |
| Size remaining = len - i; | |
| Uint32 k = 0; | |
| switch (remaining) { | |
| case 3: k ^= cast_val(Uint32, d[i+2]) << 16; switch_fallthrough; | |
| case 2: k ^= cast_val(Uint32, d[i+1]) << 8; switch_fallthrough; | |
| case 1: k ^= cast_val(Uint32, d[i]); | |
| k *= c1; | |
| k = ROTL32(k, 15); | |
| k *= c2; | |
| H ^= k; | |
| switch_fallthrough; | |
| default: break; | |
| } | |
| } | |
| #undef ROTL32 | |
| H ^= len; | |
| H ^= H >> 16; | |
| H *= 0x85ebca6b; | |
| H ^= H >> 13; | |
| H *= 0xc2b2ae35; | |
| H ^= H >> 16; | |
| return H; | |
| } | |
| // https://github.com/aappleby/smhasher/blob/0ff96f7835817a27d0487325b6c16033e2992eb5/src/MurmurHash2.cpp#L88-L137 | |
| header_function | |
| Uint64 hash64 (const void *data, Size len, Uint64 seed) | |
| { | |
| const Byte *d = cast_val(const Byte*, data); | |
| const Uint64 m = 0xc6a4a7935bd1e995ULL; | |
| const Sint r = 47; | |
| Uint64 H = seed ^ (len * m); | |
| Size i; | |
| // Run until less than eight bytes are left | |
| for (i = 0; (i + 8) <= len; i = i + 8) { | |
| Uint64 dword = load64BE(d + i); | |
| dword *= m; | |
| dword ^= dword >> r; | |
| dword *= m; | |
| H ^= dword; | |
| H *= m; | |
| } | |
| { // Finish up the remaining bytes | |
| Size remaining = len - i; | |
| switch (remaining) { | |
| case 7: H ^= cast_val(Uint64, d[i+6]) << 48; switch_fallthrough; | |
| case 6: H ^= cast_val(Uint64, d[i+5]) << 40; switch_fallthrough; | |
| case 5: H ^= cast_val(Uint64, d[i+4]) << 32; switch_fallthrough; | |
| case 4: H ^= cast_val(Uint64, d[i+3]) << 24; switch_fallthrough; | |
| case 3: H ^= cast_val(Uint64, d[i+2]) << 16; switch_fallthrough; | |
| case 2: H ^= cast_val(Uint64, d[i+1]) << 8; switch_fallthrough; | |
| case 1: H ^= cast_val(Uint64, d[i]); | |
| H *= m; | |
| switch_fallthrough; | |
| default: break; | |
| } | |
| } | |
| H ^= H >> r; | |
| H *= m; | |
| H ^= H >> r; | |
| return H; | |
| } | |
| /************************************************************************************************ | |
| * Cyclic Redundancy Check (CRC) | |
| */ | |
| /* CRC-32 | |
| * | |
| * Copied from: https://web.archive.org/web/20080303102530/http://c.snippets.org/snip_lister.php?fname=crc_32.c | |
| * Copyright (C) 1986 Gary S. Brown. You may use this program, or | |
| * code or tables extracted from it, as desired without restriction. | |
| * | |
| * Seems to be the same algoritm that is used in the HashCheck shell extensions (https://code.kliu.org/hashcheck/) | |
| */ | |
| header_function | |
| Uint32 crc32 (Byte *buf, Size len) | |
| { | |
| persistent_immutable Uint32 lookup_table[] = { /* CRC polynomial 0xedb88320 */ | |
| 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, | |
| 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, | |
| 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, | |
| 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, | |
| 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, | |
| 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, | |
| 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, | |
| 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, | |
| 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, | |
| 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, | |
| 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, | |
| 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, | |
| 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, | |
| 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, | |
| 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, | |
| 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, | |
| 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, | |
| 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, | |
| 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, | |
| 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, | |
| 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, | |
| 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, | |
| 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, | |
| 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, | |
| 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, | |
| 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, | |
| 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, | |
| 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, | |
| 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, | |
| 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, | |
| 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, | |
| 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, | |
| 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, | |
| 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, | |
| 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, | |
| 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, | |
| 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, | |
| 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, | |
| 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, | |
| 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, | |
| 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, | |
| 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, | |
| 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d | |
| }; | |
| Uint32 accumulator = 0xFFFFFFFFu; | |
| for (Size i = 0; i < len; i++) { | |
| Uint32 crc = accumulator; | |
| Uint8 byte = buf[i]; | |
| Uint32 mixed = crc ^ byte; | |
| Uint8 index = cast_val(Uint8, mixed & 0xFFu); | |
| Uint32 lookup = lookup_table[index]; | |
| Uint32 upper = crc >> 8; | |
| accumulator = lookup ^ upper; | |
| } | |
| Uint32 result = ~accumulator; | |
| return result; | |
| } | |
| /************************************************************************************************ | |
| * Symbol and Label (Small immutable stack-allocated strings) | |
| * *********************************************** | |
| */ | |
| // We put the string at the end since most reads will be about comparing the hash and length; | |
| // and even in cases when one has to read the string, the likelihood is that the string will be less | |
| // than 248 characters long, meaning that later cache lines will never actually load. | |
| typedef struct Label { | |
| union { struct { Uint32 h32; Uint16 h16; Uint8 h8; Uint8 len; }; Uint64 _id; }; | |
| Char str[247]; Char _zero; // = 0 | |
| } Label; | |
| static_assert(sizeof(Label) == 256, "Label incorrectly sized"); | |
| #define LABEL_STR_CAPACITY (elemin(valzero(Label).str)) | |
| typedef struct Symbol { | |
| union { struct { Uint32 h32; Uint16 h16; Uint8 h8; Uint8 len; }; Uint64 _id; }; | |
| Char str[55]; Char _zero; // = 0 | |
| } Symbol; | |
| static_assert(sizeof(Symbol) == 64, "Symbol incorrectly sized"); | |
| #define SYMBOL_STR_CAPACITY (elemin(valzero(Symbol).str)) | |
| header_function | |
| Label labelMakeL (const Char *str, Uint8 len) | |
| { | |
| debugAssert(len <= LABEL_STR_CAPACITY); | |
| Label l = { | |
| .len = len, | |
| }; | |
| if (l.len) { | |
| memcpy(l.str, str, l.len); | |
| l.h32 = hash32(l.str, l.len, 0); | |
| l.h16 = hash16(l.str, l.len, 0); | |
| l.h8 = hash8(l.str, l.len, 0); | |
| } | |
| return l; | |
| } | |
| header_function | |
| Symbol symbolMakeL (const Char *str, Uint8 len) | |
| { | |
| debugAssert(len <= SYMBOL_STR_CAPACITY); | |
| Symbol l = { | |
| .len = len, | |
| }; | |
| if (l.len) { | |
| memcpy(l.str, str, l.len); | |
| l.h32 = hash32(l.str, l.len, 0); | |
| l.h16 = hash16(l.str, l.len, 0); | |
| l.h8 = hash8(l.str, l.len, 0); | |
| } | |
| return l; | |
| } | |
| header_function Label labelMake (const Char *str) { return labelMakeL(str, cast_val(Uint8, strlen(str))); } | |
| header_function Symbol symbolMake (const Char *str) { return symbolMakeL(str, cast_val(Uint8, strlen(str))); } | |
| header_function Uint8 labelLen (Label l) { return l.len; } | |
| header_function Uint8 symbolLen (Symbol l) { return l.len; } | |
| header_function Bool labelIsEqual (Label a, Label b) { return (a._id == b._id) && (memcmp(a.str, b.str, a.len) == 0); } | |
| header_function Bool symbolIsEqual (Symbol a, Symbol b) { return (a._id == b._id) && (memcmp(a.str, b.str, a.len) == 0); } | |
| header_function Bool labelIsEqualStrL (Label a, const Char *b, Size blen) | |
| { | |
| return (a.len == blen) && (memcmp(a.str, b, blen) == 0); | |
| } | |
| header_function Bool symbolIsEqualStrL (Symbol a, const Char *b, Size blen) | |
| { | |
| return (a.len == blen) && (memcmp(a.str, b, blen) == 0); | |
| } | |
| header_function | |
| Bool labelIsEqualStr (Label a, const Char *b) | |
| { | |
| Bool result = labelIsEqualStrL(a, b, strlen(b)); | |
| return result; | |
| } | |
| header_function | |
| Bool symbolIsEqualStr (Symbol a, const Char *b) | |
| { | |
| Bool result = symbolIsEqualStrL(a, b, strlen(b)); | |
| return result; | |
| } | |
| header_function | |
| Bool srlzLabel (Label *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) { | |
| if (!srlzUint8(&datum->len, func, funcdata, write, big_endian)) return false; | |
| if (datum->len) { | |
| if (!func(datum->str, datum->len, funcdata)) return false; | |
| if (!write) *datum = labelMakeL(datum->str, datum->len); | |
| } else { | |
| if (!write) memzero(datum); | |
| } | |
| return true; | |
| } | |
| header_function | |
| Bool srlzSymbol (Symbol *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) { | |
| if (!srlzUint8(&datum->len, func, funcdata, write, big_endian)) return false; | |
| if (datum->len) { | |
| if (!func(datum->str, datum->len, funcdata)) return false; | |
| if (!write) *datum = symbolMakeL(datum->str, datum->len); | |
| } else { | |
| if (!write) memzero(datum); | |
| } | |
| return true; | |
| } | |
| /************************************************************************************************ | |
| * Txt (String Library) | |
| * ******************** | |
| */ | |
| typedef struct Txt { Char *str; } Txt; | |
| typedef struct Txt_Header_ { | |
| Uint32 len; | |
| Char str[]; | |
| } Txt_Header_; | |
| header_function | |
| Txt txt_Allocate (Memory_Push *mp, Size len) | |
| { | |
| Size size = len + 1 + sizeof(Txt_Header_); | |
| Txt_Header_ *hdr = cast_val(Txt_Header_*, memPushAllotA(mp, size, alignof(Txt_Header_))); | |
| debugAssert(len < UINT32_MAX); | |
| hdr->len = cast_val(Uint32, len); | |
| hdr->str[len] = '\0'; | |
| Char *s = hdr->str; | |
| Txt t = {.str = s}; | |
| return t; | |
| } | |
| header_function | |
| Txt txtNewL (Memory_Push *mp, const Char *s, Size len) | |
| { | |
| Txt t = txt_Allocate(mp, len); | |
| memcpy(t.str, s, len); | |
| return t; | |
| } | |
| header_function | |
| Txt txtNew (Memory_Push *mp, const Char *s) | |
| { | |
| Size length = strlen(s); | |
| Txt result = txtNewL(mp, s, length); | |
| return result; | |
| } | |
| header_function | |
| Char* txtStr (Txt t) | |
| { | |
| Char *s = t.str; | |
| return s; | |
| } | |
| header_function | |
| Size txtLen (Txt t) | |
| { | |
| Txt_Header_ *hdr = containerof(t.str, Txt_Header_, str); | |
| return hdr->len; | |
| } | |
| header_function | |
| Bool txt_EqN (Txt t1, Txt t2, Size len) | |
| { | |
| Bool result = memcmp(t1.str, t2.str, len) == 0; | |
| return result; | |
| } | |
| header_function | |
| Bool txtEq_TT (Txt t1, Txt t2) | |
| { | |
| Size t1l = txtLen(t1); | |
| Size t2l = txtLen(t2); | |
| if (t1l != t2l) return false; | |
| Bool result = txt_EqN(t1, t2, t1l); | |
| return result; | |
| } | |
| header_function | |
| Bool txtEq_TC (Txt t1, const Char *s2) | |
| { | |
| Size t1l = txtLen(t1); | |
| Size s2l = strlen(s2); | |
| if (t1l != s2l) return false; | |
| Bool result = memcmp(txtStr(t1), s2, t1l) == 0; | |
| return result; | |
| } | |
| header_function | |
| Bool txtEq_TL (Txt t1, Label l2) | |
| { | |
| Size t1l = txtLen(t1); | |
| Size l2l = labelLen(l2); | |
| if (t1l != l2l) return false; | |
| Bool result = memcmp(txtStr(t1), l2.str, t1l) == 0; | |
| return result; | |
| } | |
| header_function | |
| Bool txtEq_TS (Txt t1, Symbol s2) | |
| { | |
| Size t1l = txtLen(t1); | |
| Size s2l = symbolLen(s2); | |
| if (t1l != s2l) return false; | |
| Bool result = memcmp(txtStr(t1), s2.str, t1l) == 0; | |
| return result; | |
| } | |
| #if defined(ENV_LANG_C) | |
| # define txtEq(_a, _b) \ | |
| _Generic((_b) \ | |
| , Txt : txtEq_TT \ | |
| , Char* : txtEq_TC \ | |
| , const Char* : txtEq_TC \ | |
| , Label : txtEq_TL \ | |
| , Symbol : txtEq_TS \ | |
| )(_a, _b) | |
| #elif defined(ENV_LANG_CXX) | |
| header_function Bool txtEq (Txt a, Txt b) { return txtEq_TT(a, b); } | |
| header_function Bool txtEq (Txt a, Char* b) { return txtEq_TC(a, b); } | |
| header_function Bool txtEq (Txt a, const Char* b) { return txtEq_TC(a, b); } | |
| header_function Bool txtEq (Txt a, Label b) { return txtEq_TL(a, b); } | |
| header_function Bool txtEq (Txt a, Symbol b) { return txtEq_TS(a, b); } | |
| #endif | |
| header_function | |
| Char* txtChr (Txt t, Char c) | |
| { | |
| Size len = txtLen(t); | |
| Char *str = txtStr(t); | |
| for (Size i = 0; i < len; i++) { | |
| if (str[i] == c) return &str[i]; | |
| } | |
| return nullptr; | |
| } | |
| header_function | |
| Char* txtChrR (Txt t, Char c) | |
| { | |
| Size len = txtLen(t); | |
| Char *str = txtStr(t); | |
| for (Size i = len - 1; i < len; i++) { | |
| if (str[i] == c) return &str[i]; | |
| } | |
| return nullptr; | |
| } | |
| typedef struct Txt_Span { | |
| Txt t; | |
| Uint32 begin, end; // begin inclusive, end exclusive { [begin, end) } | |
| Uint64 hash; | |
| } Txt_Span; | |
| header_function | |
| Txt_Span txtGetSpan (Txt t, Sint64 begin, Sint64 end) | |
| { | |
| if (begin < 0) begin = cast_val(Uint32, cast_val(Sint32, txtLen(t)) - (-begin)); | |
| if (end < 0) end = cast_val(Uint32, cast_val(Sint32, txtLen(t)) - (-end)); | |
| if (end <= begin) { | |
| end = begin + 1; // So that we don't end up with unnecessary errors | |
| } | |
| if (end > cast_val(Sint, txtLen(t))) { | |
| end = cast_val(Sint, txtLen(t)); | |
| } | |
| Txt_Span ts = { | |
| .t = t, | |
| .begin = cast_val(Uint32, begin), | |
| .end = cast_val(Uint32, end), | |
| .hash = hash64(txtStr(t) + begin, cast_val(Size, end - begin), 0), | |
| }; | |
| return ts; | |
| } | |
| header_function | |
| Char* txtspanPtr(Txt_Span ts) | |
| { | |
| Char *str = txtStr(ts.t) + ts.begin; | |
| return str; | |
| } | |
| header_function | |
| Size txtspanLen (Txt_Span ts) | |
| { | |
| Size len = ts.end - ts.begin; | |
| return len; | |
| } | |
| header_function | |
| Char txtspanIndex (Txt_Span ts, Uint index) | |
| { | |
| Uint ioffset = ts.begin + index; | |
| if (ioffset >= ts.end) { | |
| return '\0'; | |
| } else { | |
| Char c = ts.t.str[ioffset]; | |
| return c; | |
| } | |
| } | |
| header_function | |
| Txt txtspanExtract (Memory_Push *mp, Txt_Span ts) | |
| { | |
| return txtNewL(mp, txtspanPtr(ts), txtspanLen(ts)); | |
| } | |
| header_function | |
| Bool txtspanIsEqualStr (Txt_Span ts, const Char *str) | |
| { | |
| Size strs = strlen(str); | |
| Size tss = ts.end - ts.begin; | |
| return (strs == tss) && (memcmp(str, ts.t.str + ts.begin, tss) == 0); | |
| } | |
| header_function | |
| Bool txtspanIsEqualLabel (Txt_Span ts, Label l) | |
| { | |
| return labelIsEqualStrL(l, ts.t.str + ts.begin, ts.end-ts.begin); | |
| } | |
| header_function | |
| Label labelMakeTS (Txt_Span ts) | |
| { | |
| return labelMakeL(ts.t.str + ts.begin, cast_val(Uint8, ts.end - ts.begin)); | |
| } | |
| header_function | |
| Symbol symbolMakeTS (Txt_Span ts) | |
| { | |
| return symbolMakeL(ts.t.str + ts.begin, cast_val(Uint8, ts.end - ts.begin)); | |
| } | |
| typedef struct Txt_Fmt_Chunk Txt_Fmt_Chunk; | |
| typedef struct Txt_Fmt_Pool { | |
| Memory_Slab chunk_allocator; | |
| Size chunk_count; | |
| } Txt_Fmt_Pool; | |
| typedef struct Txt_Fmt { | |
| Txt_Fmt_Pool *pool; | |
| Size length; | |
| Txt_Fmt_Chunk *first_chunk, *last_chunk; | |
| } Txt_Fmt; | |
| struct Txt_Fmt_Chunk { | |
| Txt_Fmt_Chunk *next_chunk; | |
| Char data[55]; | |
| Uint8 filled; | |
| }; | |
| static_assert(sizeof(Txt_Fmt_Chunk) == 64, "Txt fmt chunk won't fit in a single cache line"); | |
| #define TXT_FMT_CHARS_PER_CHUNK_ elemin(valzero(Txt_Fmt_Chunk).data) | |
| static_assert(TXT_FMT_CHARS_PER_CHUNK_ == 55, "Number of bytes in each Txt fmt chunk is wrong"); | |
| header_function | |
| void txtfmtPoolCreate (Txt_Fmt_Pool *tp, Memory *parent_memory, Uint32 chunk_count) | |
| { | |
| memzero(tp); | |
| // 512 chunks in 32 KiB (same size as the free-grid metadata) | |
| chunk_count = maximum(chunk_count, 512u); | |
| tp->chunk_count = chunk_count; | |
| memSlabCreate(&tp->chunk_allocator, parent_memory, chunk_count, sizeof(Txt_Fmt_Chunk)); | |
| } | |
| header_function | |
| void txtfmtPoolDelete (Txt_Fmt_Pool *tp) | |
| { | |
| memSlabDelete(&tp->chunk_allocator); | |
| } | |
| header_function | |
| Txt_Fmt txtfmtCreate (Txt_Fmt_Pool *tp) | |
| { | |
| Txt_Fmt txt = { | |
| .pool = tp, | |
| }; | |
| txt.first_chunk = cast_val(Txt_Fmt_Chunk*, memSlabAllot(&tp->chunk_allocator)); | |
| memzero(txt.first_chunk); | |
| txt.last_chunk = txt.first_chunk; | |
| return txt; | |
| } | |
| header_function | |
| void txtfmtDelete (Txt_Fmt *tf) | |
| { | |
| for (Txt_Fmt_Chunk *tc = tf->first_chunk, *tcc; tc != nullptr; tc = tcc) { | |
| tcc = tc->next_chunk; | |
| memSlabRemit(&tf->pool->chunk_allocator, tc); | |
| } | |
| } | |
| header_function | |
| Txt_Fmt_Chunk* txtfmt_GetLastChunk (Txt_Fmt *tf) | |
| { | |
| if (tf->last_chunk->filled == TXT_FMT_CHARS_PER_CHUNK_) { | |
| tf->last_chunk->next_chunk = cast_val(Txt_Fmt_Chunk*, memSlabAllot(&tf->pool->chunk_allocator)); | |
| memzero(tf->last_chunk->next_chunk); | |
| tf->last_chunk = tf->last_chunk->next_chunk; | |
| } | |
| return tf->last_chunk; | |
| } | |
| header_function | |
| Size txtfmtAffix (Txt_Fmt *tf, Char c) | |
| { | |
| Txt_Fmt_Chunk *s = txtfmt_GetLastChunk(tf); | |
| s->data[s->filled] = c; | |
| s->filled++; | |
| tf->length++; | |
| return 1; | |
| } | |
| header_function | |
| PRINT_CALLBACK_FUNC(txtfmt_PrintCallback) | |
| { | |
| unused_variable(buffer_cap); | |
| Txt_Fmt *tf = cast_val(Txt_Fmt*, userdata); | |
| Txt_Fmt_Chunk *s = txtfmt_GetLastChunk(tf); | |
| debugAssert(s); | |
| Size length = cast_val(Size, buffer_len); | |
| Size empty = TXT_FMT_CHARS_PER_CHUNK_ - s->filled; | |
| if (length > empty) { | |
| memcpy(s->data + s->filled, filled_buffer, empty); | |
| s->filled += cast_val(Uint8, empty); | |
| if (length - empty) { | |
| s = txtfmt_GetLastChunk(tf); | |
| memcpy(s->data, filled_buffer + empty, length - empty); | |
| s->filled += cast_val(Uint8, length - empty); | |
| } | |
| } else if (length != 0) { | |
| memcpy(s->data + s->filled, filled_buffer, length); | |
| s->filled += cast_val(Uint8, length); | |
| } | |
| tf->length += length; | |
| } | |
| with_clang_gcc(__attribute__(( format( __printf__, 2, 0 )))) /* 2nd arg is 0 since va_list can't be checked */ | |
| header_function | |
| Size txtfmtAppendFV (Txt_Fmt *tf, const Char *fmt, va_list va) | |
| { | |
| Char buf[TXT_FMT_CHARS_PER_CHUNK_] = {0}; | |
| Size size = printStringVarArg(buf, elemin(buf), fmt, va, txtfmt_PrintCallback, tf); | |
| return size; | |
| } | |
| with_clang_gcc(__attribute__ (( format( __printf__, 2, 3 )))) | |
| header_function | |
| Size txtfmtAppendF (Txt_Fmt *tf, const Char *fmt, ...) | |
| { | |
| va_list args; | |
| va_start(args, fmt); | |
| Size result = txtfmtAppendFV(tf, fmt, args); | |
| va_end(args); | |
| return result; | |
| } | |
| header_function | |
| Size txtfmtAppendCL (Txt_Fmt *tf, const Char *tc, Size length) | |
| { | |
| Size copied = 0; | |
| Size len = length; | |
| while (len) { | |
| Txt_Fmt_Chunk *s = txtfmt_GetLastChunk(tf); | |
| Size l = minimum(len, TXT_FMT_CHARS_PER_CHUNK_); | |
| l = minimum(l, TXT_FMT_CHARS_PER_CHUNK_ - s->filled); | |
| memcpy(s->data + s->filled, tc + copied, l); | |
| copied += l; | |
| len -= l; | |
| s->filled += cast_val(Uint8, l); | |
| } | |
| tf->length += length; | |
| return copied; | |
| } | |
| header_function | |
| Size txtfmtAppendC (Txt_Fmt *tf, const Char *tc) | |
| { | |
| Size len = strlen(tc); | |
| Size result = txtfmtAppendCL(tf, tc, len); | |
| return result; | |
| } | |
| header_function | |
| Size txtfmtAppendT (Txt_Fmt *tf, Txt txt) | |
| { | |
| Size result = txtfmtAppendCL(tf, txtStr(txt), txtLen(txt)); | |
| return result; | |
| } | |
| header_function | |
| Size txtfmtAppendTs (Txt_Fmt *tf, Txt_Span ts) | |
| { | |
| Size result = txtfmtAppendCL(tf, txtStr(ts.t) + ts.begin, cast_val(Size, ts.end - ts.begin)); | |
| return result; | |
| } | |
| header_function | |
| Size txtfmtAppendLbl (Txt_Fmt *tf, Label lbl) | |
| { | |
| Size result = txtfmtAppendCL(tf, lbl.str, lbl.len); | |
| return result; | |
| } | |
| header_function | |
| Txt txtfmtFinish (Txt_Fmt *tf, Memory_Push *mp) | |
| { | |
| Txt t = txt_Allocate(mp, tf->length); | |
| Size filled = 0; | |
| for (Txt_Fmt_Chunk *s = tf->first_chunk; s != nullptr; s = s->next_chunk) { | |
| debugAssert((filled + s->filled) < UINT32_MAX); | |
| memcpy(txtStr(t) + filled, s->data, s->filled); | |
| filled += s->filled; | |
| } | |
| (txtStr(t))[filled] = '\0'; | |
| txtfmtDelete(tf); | |
| return t; | |
| } | |
| with_clang_gcc(__attribute__ (( format( __printf__, 3, 4 )))) | |
| header_function | |
| Txt txtFormat (Txt_Fmt_Pool *tfp, Memory_Push *mempush, const Char *fmt, ...) | |
| { | |
| Txt_Fmt tf = txtfmtCreate(tfp); | |
| va_list args; | |
| va_start(args, fmt); | |
| txtfmtAppendFV(&tf, fmt, args); | |
| va_end(args); | |
| Txt result = txtfmtFinish(&tf, mempush); | |
| return result; | |
| } | |
| #undef TXT_FMT_BYTES_PER_CHUNK_ | |
| /************************************************************************************************ | |
| * Tokenizer | |
| * | |
| * A C-syntax tokenizer | |
| */ | |
| typedef enum Token_Kind { | |
| Token_Kind_EOF = 0, | |
| // NOTE(naman): Single letter non-alphanumeric tokens (like '+', '{', etc.) with ASCII value <=127 are stord as their own kind. | |
| Token_Kind_EQUALITY = 128, | |
| Token_Kind_NOTEQUAL, | |
| Token_Kind_LESSEQ, | |
| Token_Kind_GREATEQ, | |
| Token_Kind_LOGICAL_OR, | |
| Token_Kind_LOGICAL_AND, | |
| Token_Kind_SHIFT_LEFT, | |
| Token_Kind_SHIFT_RIGHT, | |
| Token_Kind_ADD_ASSIGN, | |
| Token_Kind_SUB_ASSIGN, | |
| Token_Kind_MULTIPLY_ASSIGN, | |
| Token_Kind_DIVIDE_ASSIGN, | |
| Token_Kind_MODULUS_ASSIGN, | |
| Token_Kind_XOR_ASSIGN, | |
| Token_Kind_OR_ASSIGN, | |
| Token_Kind_AND_ASSIGN, | |
| Token_Kind_SHIFT_LEFT_ASSIGN, | |
| Token_Kind_SHIFT_RIGHT_ASSIGN, | |
| Token_Kind_INCREMENT, | |
| Token_Kind_DECREMENT, | |
| Token_Kind_PTR_DEREF, | |
| Token_Kind_ELLIPSIS, | |
| Token_Kind_ATTRIBUTE_NAMESPACE, | |
| Token_Kind_ATTRIBUTE_BEGIN, | |
| Token_Kind_ATTRIBUTE_END, | |
| Token_Kind_CONCATENATE, | |
| Token_Kind_IDENTIFIER = 175, | |
| Token_Kind_CONSTANT_INTEGER, | |
| Token_Kind_CONSTANT_FLOATING, | |
| Token_Kind_CONSTANT_CHARACTER, | |
| Token_Kind_CONSTANT_STRING, | |
| Token_Kind_TOTAL, | |
| } Token_Kind; | |
| static_assert(Token_Kind_TOTAL <= 255); | |
| typedef struct Token { | |
| Txt_Span span; | |
| Token_Kind kind; | |
| Uint line, column; | |
| Byte _pad[4]; | |
| union { | |
| Uint64 integer; | |
| Float64 floating; | |
| } value; | |
| } Token; | |
| #define TOKENIZER_ERROR_FUNC(_name) Token _name (struct Tokenizer *toker, const Char *msg, void *userdata) | |
| typedef struct Tokenizer { | |
| Token read_token; | |
| Txt src; | |
| Sint64 cursor; | |
| Uint line; | |
| Uint column; | |
| TOKENIZER_ERROR_FUNC((*err_func)); | |
| void *err_data; | |
| } Tokenizer; | |
| header_function Token tokenizerGetToken (Tokenizer *toker); | |
| header_function | |
| Tokenizer tokenizerMake (Txt src, TOKENIZER_ERROR_FUNC((*err_func)), void *err_data) | |
| { | |
| Tokenizer toker = { | |
| .src = src, | |
| .line = 1, | |
| .column = 1, | |
| .err_func = err_func, | |
| .err_data = err_data, | |
| }; | |
| tokenizerGetToken(&toker); | |
| return toker; | |
| } | |
| header_function | |
| void tokenizer_CharAdvance (Tokenizer *toker, Uint count) | |
| { | |
| for (size_t i = 0; i < count; i++) { | |
| if (toker->src.str[toker->cursor] == '\n') { | |
| toker->line++; | |
| toker->column = 1; | |
| } else { | |
| toker->column++; | |
| } | |
| toker->cursor++; | |
| } | |
| } | |
| header_function | |
| Token tokenizerPeekToken (Tokenizer *toker) | |
| { | |
| Token tok = toker->read_token; | |
| return tok; | |
| } | |
| pragma_clang("clang diagnostic push"); | |
| pragma_clang("clang diagnostic ignored \"-Wassign-enum\""); // Assigning random values to enums (ASCII characters) | |
| // FIXME(naman): Implement my own strtod and remove this usage of libc atof | |
| #if defined(ENV_LANG_CXX) | |
| extern "C" | |
| #endif | |
| #if defined(ENV_COMPILER_MSVC) | |
| _Check_return_ _ACRTIMP | |
| #endif | |
| Float64 atof( | |
| #if defined(ENV_COMPILER_MSVC) | |
| _In_z_ | |
| #endif | |
| const Char* str); | |
| header_function | |
| Token tokenizerGetToken (Tokenizer *toker) | |
| { | |
| Token last_token = toker->read_token; | |
| Uint64 tokenized_integer = 0; | |
| Float64 tokenized_floating = 0.0; | |
| { // Eat Whitespace | |
| Bool ate_something_this_loop = true; | |
| while (ate_something_this_loop) { | |
| ate_something_this_loop = false; | |
| while ((toker->src.str[toker->cursor] == ' ') || | |
| (toker->src.str[toker->cursor] == '\t') || | |
| (toker->src.str[toker->cursor] == '\r') || | |
| (toker->src.str[toker->cursor] == '\n')) { | |
| ate_something_this_loop = true; | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| if ((toker->src.str[toker->cursor] == '/') && (toker->src.str[toker->cursor+1] == '/')) { | |
| ate_something_this_loop = true; | |
| for (;;) { | |
| if (toker->src.str[toker->cursor] == '\n') { | |
| if ((toker->cursor) > 0 && toker->src.str[toker->cursor-1] == '\\') { | |
| // do nothing | |
| } else { | |
| break; | |
| } | |
| } | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| if ((toker->src.str[toker->cursor] == '/') && (toker->src.str[toker->cursor+1] == '*')) { | |
| ate_something_this_loop = true; | |
| tokenizer_CharAdvance(toker, 2); | |
| while (!((toker->src.str[toker->cursor] == '*') && (toker->src.str[toker->cursor+1] == '/'))) tokenizer_CharAdvance(toker, 1); | |
| tokenizer_CharAdvance(toker, 2); | |
| } | |
| } | |
| } | |
| Sint64 begin = toker->cursor; | |
| Token_Kind kind; | |
| switch (toker->src.str[toker->cursor]) { | |
| case '\0': { | |
| toker->read_token = valzero(Token); | |
| return last_token; | |
| } break; | |
| case '0': case '1': case '2': case '3': case '4': | |
| case '5': case '6': case '7': case '8': case '9': | |
| case '.': { | |
| // First, make sure if the token is even a number or a standalone dot / ellipsis | |
| if ((toker->src.str[toker->cursor] == '.') && | |
| !((toker->src.str[toker->cursor+1] >= '0') && (toker->src.str[toker->cursor+1] <= '9'))) { | |
| tokenizer_CharAdvance(toker, 1); | |
| if ((toker->src.str[toker->cursor + 0] == '.') && (toker->src.str[toker->cursor + 1] == '.')) { | |
| tokenizer_CharAdvance(toker, 2); | |
| kind = Token_Kind_ELLIPSIS; | |
| } else { | |
| kind = cast_val(Token_Kind, '.'); | |
| } | |
| } else { // If it is a number | |
| Bool floating = false; | |
| { // First, detect if this is a float (which only supports hex and decimal) | |
| Sint64 cursor = toker->cursor + 1; | |
| // Quotes are used in place of commas | |
| if (toker->src.str[cursor] == 'x') { // Hexadecimal | |
| cursor += 2; | |
| while (((toker->src.str[cursor] <= '9') && (toker->src.str[cursor] >= '0')) || | |
| ((toker->src.str[cursor] <= 'F') && (toker->src.str[cursor] >= 'A')) || | |
| ((toker->src.str[cursor] <= 'f') && (toker->src.str[cursor] >= 'a')) || | |
| (toker->src.str[cursor] == '\'')) { | |
| cursor++; | |
| } | |
| if ((toker->src.str[cursor] == '.') || | |
| (toker->src.str[cursor] == 'p') || | |
| (toker->src.str[cursor] == 'P')) { | |
| floating = true; | |
| } | |
| } else { // Decimal | |
| while (((toker->src.str[cursor] <= '9') && (toker->src.str[cursor] >= '0')) || | |
| (toker->src.str[cursor] == '\'')) { | |
| cursor++; | |
| } | |
| if ((toker->src.str[cursor] == '.') || | |
| (toker->src.str[cursor] == 'e') || | |
| (toker->src.str[cursor] == 'E')) { | |
| floating = true; | |
| } | |
| } | |
| } | |
| if (floating) { // If it is float | |
| kind = Token_Kind_CONSTANT_FLOATING; | |
| tokenizer_CharAdvance(toker, 1); | |
| // Hexadecimal float | |
| if ((toker->src.str[toker->cursor] == 'x') || (toker->src.str[toker->cursor] == 'X')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) || | |
| ((toker->src.str[toker->cursor] <= 'F') && (toker->src.str[toker->cursor] >= 'A')) || | |
| ((toker->src.str[toker->cursor] <= 'f') && (toker->src.str[toker->cursor] >= 'a')) || | |
| (toker->src.str[toker->cursor] == '\'')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| if (toker->src.str[toker->cursor] == '.') { | |
| tokenizer_CharAdvance(toker, 1); | |
| while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) || | |
| ((toker->src.str[toker->cursor] <= 'F') && (toker->src.str[toker->cursor] >= 'A')) || | |
| ((toker->src.str[toker->cursor] <= 'f') && (toker->src.str[toker->cursor] >= 'a')) || | |
| (toker->src.str[toker->cursor] == '\'')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| } | |
| if ((toker->src.str[toker->cursor] == 'p') || (toker->src.str[toker->cursor] == 'P')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| if ((toker->src.str[toker->cursor] == '-') || (toker->src.str[toker->cursor] == '+')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) || | |
| (toker->src.str[toker->cursor] == '\'')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| } | |
| } else { // Decimal float | |
| while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) || | |
| (toker->src.str[toker->cursor] == '\'')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| if (toker->src.str[toker->cursor] == '.') { | |
| tokenizer_CharAdvance(toker, 1); | |
| while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) || | |
| (toker->src.str[toker->cursor] == '\'')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| } | |
| if ((toker->src.str[toker->cursor] == 'e') || (toker->src.str[toker->cursor] == 'E')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| if ((toker->src.str[toker->cursor] == '-') || (toker->src.str[toker->cursor] == '+')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) || | |
| (toker->src.str[toker->cursor] == '\'')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| } | |
| } | |
| /* */ if (toker->src.str[toker->cursor] == 'f') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 'l') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 'F') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 'L') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if ((toker->src.str[toker->cursor] == 'd') && (toker->src.str[toker->cursor+1] == 'f')) { | |
| tokenizer_CharAdvance(toker, 2); | |
| } else if ((toker->src.str[toker->cursor] == 'd') && (toker->src.str[toker->cursor+1] == 'd')) { | |
| tokenizer_CharAdvance(toker, 2); | |
| } else if ((toker->src.str[toker->cursor] == 'd') && (toker->src.str[toker->cursor+1] == 'l')) { | |
| tokenizer_CharAdvance(toker, 2); | |
| } else if ((toker->src.str[toker->cursor] == 'D') && (toker->src.str[toker->cursor+1] == 'F')) { | |
| tokenizer_CharAdvance(toker, 2); | |
| } else if ((toker->src.str[toker->cursor] == 'D') && (toker->src.str[toker->cursor+1] == 'D')) { | |
| tokenizer_CharAdvance(toker, 2); | |
| } else if ((toker->src.str[toker->cursor] == 'D') && (toker->src.str[toker->cursor+1] == 'L')) { | |
| tokenizer_CharAdvance(toker, 2); | |
| } | |
| Txt_Span span = txtGetSpan(toker->src, begin, toker->cursor); | |
| tokenized_floating = atof(span.t.str + span.begin); | |
| } else { // Else, if it is an integer | |
| kind = Token_Kind_CONSTANT_INTEGER; | |
| Uint base; | |
| Uint digit_begin_index; | |
| if (toker->src.str[toker->cursor] == '0') { // If another base | |
| tokenizer_CharAdvance(toker, 1); | |
| if ((toker->src.str[toker->cursor] == 'x') || (toker->src.str[toker->cursor] == 'X')) { | |
| // Hexadecimal | |
| tokenizer_CharAdvance(toker, 1); | |
| base = 16; | |
| digit_begin_index = 2; | |
| while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) || | |
| ((toker->src.str[toker->cursor] <= 'F') && (toker->src.str[toker->cursor] >= 'A')) || | |
| ((toker->src.str[toker->cursor] <= 'f') && (toker->src.str[toker->cursor] >= 'a')) || | |
| (toker->src.str[toker->cursor] == '\'')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| } else if ((toker->src.str[toker->cursor] == 'b') || (toker->src.str[toker->cursor] == 'B')) { | |
| // Binary | |
| tokenizer_CharAdvance(toker, 1); | |
| base = 2; | |
| digit_begin_index = 2; | |
| while ((toker->src.str[toker->cursor] == '0') || (toker->src.str[toker->cursor] == '1') || (toker->src.str[toker->cursor] == '\'')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| } else { | |
| // Octal | |
| base = 8; | |
| digit_begin_index = 1; | |
| while (((toker->src.str[toker->cursor] <= '7') && (toker->src.str[toker->cursor] >= '0')) || | |
| (toker->src.str[toker->cursor] == '\'')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| } | |
| } else { // Decimal | |
| tokenizer_CharAdvance(toker, 1); | |
| base = 10; | |
| digit_begin_index = 0; | |
| while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) || | |
| (toker->src.str[toker->cursor] == '\'')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| } | |
| Txt_Span digit_span = txtGetSpan(toker->src, begin + digit_begin_index, toker->cursor); | |
| // Integer suffix | |
| if ((toker->src.str[toker->cursor] == 'u') || (toker->src.str[toker->cursor] == 'U')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| if (toker->src.str[toker->cursor] == 'l') { | |
| tokenizer_CharAdvance(toker, 1); | |
| if (toker->src.str[toker->cursor] == 'l') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| } else if (toker->src.str[toker->cursor] == 'L') { | |
| tokenizer_CharAdvance(toker, 1); | |
| if (toker->src.str[toker->cursor] == 'L') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| } else if (toker->src.str[toker->cursor] == 'w') { | |
| tokenizer_CharAdvance(toker, 1); | |
| if (toker->src.str[toker->cursor] == 'b') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| } else if (toker->src.str[toker->cursor] == 'W') { | |
| tokenizer_CharAdvance(toker, 1); | |
| if (toker->src.str[toker->cursor] == 'B') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| } | |
| } else if (toker->src.str[toker->cursor] == 'l') { | |
| tokenizer_CharAdvance(toker, 1); | |
| if (toker->src.str[toker->cursor] == 'l') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| if ((toker->src.str[toker->cursor] == 'u') || (toker->src.str[toker->cursor] == 'U')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| } else if (toker->src.str[toker->cursor] == 'L') { | |
| tokenizer_CharAdvance(toker, 1); | |
| if (toker->src.str[toker->cursor] == 'L') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| if ((toker->src.str[toker->cursor] == 'u') || (toker->src.str[toker->cursor] == 'U')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| } | |
| Uint64 accumulator = 0; | |
| Uint8 char_to_digit[128] = {}; | |
| char_to_digit['0'] = 0; | |
| char_to_digit['1'] = 1; | |
| char_to_digit['2'] = 2; | |
| char_to_digit['3'] = 3; | |
| char_to_digit['4'] = 4; | |
| char_to_digit['5'] = 5; | |
| char_to_digit['6'] = 6; | |
| char_to_digit['7'] = 7; | |
| char_to_digit['8'] = 8; | |
| char_to_digit['9'] = 9; | |
| char_to_digit['A'] = 10; char_to_digit['a'] = 10; | |
| char_to_digit['B'] = 11; char_to_digit['b'] = 11; | |
| char_to_digit['C'] = 12; char_to_digit['c'] = 12; | |
| char_to_digit['D'] = 13; char_to_digit['d'] = 13; | |
| char_to_digit['E'] = 14; char_to_digit['e'] = 14; | |
| char_to_digit['F'] = 15; char_to_digit['f'] = 15; | |
| for (Uint i = 0; txtspanIndex(digit_span, i); i++) { | |
| Char c = txtspanIndex(digit_span, i); | |
| if (c == '\'') { | |
| continue; | |
| } | |
| Uint8 digit = char_to_digit[cast_val(Uint, c)]; | |
| if (accumulator > (UINT64_MAX - digit)/base) { | |
| toker->read_token = (toker->err_func)(toker, "Integer too large to fit into Uint64", toker->err_data); | |
| return last_token; | |
| } | |
| accumulator = accumulator * base + digit; | |
| } | |
| tokenized_integer = accumulator; | |
| } | |
| } | |
| } break; | |
| case 'A': case 'a': case 'B': case 'b': case 'C': case 'c': | |
| case 'D': case 'd': case 'E': case 'e': case 'F': case 'f': | |
| case 'G': case 'g': case 'H': case 'h': case 'I': case 'i': | |
| case 'J': case 'j': case 'K': case 'k': case 'L': case 'l': | |
| case 'M': case 'm': case 'N': case 'n': case 'O': case 'o': | |
| case 'P': case 'p': case 'Q': case 'q': case 'R': case 'r': | |
| case 'S': case 's': case 'T': case 't': case 'U': case 'u': | |
| case 'V': case 'v': case 'W': case 'w': case 'X': case 'x': | |
| case 'Y': case 'y': case 'Z': case 'z': case '_': | |
| case '\'': case '"': { // Charater, string or identifier | |
| // If it is character | |
| if ((toker->src.str[toker->cursor] == '\'') || | |
| ((toker->src.str[toker->cursor] == 'u') && (toker->src.str[toker->cursor+1] == '8') && (toker->src.str[toker->cursor+2] == '\'')) || | |
| ((toker->src.str[toker->cursor] == 'u') && (toker->src.str[toker->cursor+1] == '\'')) || | |
| ((toker->src.str[toker->cursor] == 'U') && (toker->src.str[toker->cursor+1] == '\'')) || | |
| ((toker->src.str[toker->cursor] == 'L') && (toker->src.str[toker->cursor+1] == '\''))) { | |
| kind = Token_Kind_CONSTANT_CHARACTER; | |
| if ((toker->src.str[toker->cursor] == 'u') && (toker->src.str[toker->cursor+1] == '8')) { | |
| tokenizer_CharAdvance(toker, 2); | |
| } else if (toker->src.str[toker->cursor] == 'u') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 'U') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 'L') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| if (toker->src.str[toker->cursor] != '\'') { | |
| toker->read_token = (toker->err_func)(toker, "Open quote missing in character literal", toker->err_data); | |
| return last_token; | |
| } else { | |
| tokenizer_CharAdvance(toker, 1); // ' | |
| } | |
| if (toker->src.str[toker->cursor] == '\'') { | |
| toker->read_token = (toker->err_func)(toker, "character literal can not be empty", toker->err_data); | |
| return last_token; | |
| } | |
| if (toker->src.str[toker->cursor] == '\n') { | |
| toker->read_token = (toker->err_func)(toker, "character literal can not be a newline", toker->err_data); | |
| return last_token; | |
| } | |
| if (toker->src.str[toker->cursor] == '\\') { | |
| tokenizer_CharAdvance(toker, 1); | |
| /* */ if (toker->src.str[toker->cursor] == '\'') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == '"') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == '?') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == '\\') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 'a') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 'b') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 'f') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 'n') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 'r') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 't') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 'v') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if ((toker->src.str[toker->cursor] <= '7') && (toker->src.str[toker->cursor] >= '0')) { | |
| for (size_t i = 0; i < 3; i++) { | |
| if ((toker->src.str[toker->cursor] <= '7') && (toker->src.str[toker->cursor] >= '0')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| } | |
| } else if (toker->src.str[toker->cursor] == 'x') { | |
| tokenizer_CharAdvance(toker, 1); | |
| while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) || | |
| ((toker->src.str[toker->cursor] <= 'F') && (toker->src.str[toker->cursor] >= 'A')) || | |
| ((toker->src.str[toker->cursor] <= 'f') && (toker->src.str[toker->cursor] >= 'a'))) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| } else if (toker->src.str[toker->cursor] == 'u') { | |
| tokenizer_CharAdvance(toker, 1); | |
| for (size_t i = 0; i < 4; i++) { | |
| if (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) || | |
| ((toker->src.str[toker->cursor] <= 'F') && (toker->src.str[toker->cursor] >= 'A')) || | |
| ((toker->src.str[toker->cursor] <= 'f') && (toker->src.str[toker->cursor] >= 'a'))) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else { | |
| toker->read_token = (toker->err_func)(toker, "Universal character incomplete, needs four hex digits", toker->err_data); | |
| return last_token; | |
| } | |
| } | |
| } else if (toker->src.str[toker->cursor] == 'U') { | |
| tokenizer_CharAdvance(toker, 1); | |
| for (size_t i = 0; i < 8; i++) { | |
| if (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) || | |
| ((toker->src.str[toker->cursor] <= 'F') && (toker->src.str[toker->cursor] >= 'A')) || | |
| ((toker->src.str[toker->cursor] <= 'f') && (toker->src.str[toker->cursor] >= 'a'))) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else { | |
| toker->read_token = (toker->err_func)(toker, "Universal character incomplete, needs eight hex digits", toker->err_data); | |
| return last_token; | |
| } | |
| } | |
| } else { | |
| toker->read_token = (toker->err_func)(toker, "Unknown escape sequence in character", toker->err_data); | |
| return last_token; | |
| } | |
| } else { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| if (toker->src.str[toker->cursor] != '\'') { | |
| toker->read_token = (toker->err_func)(toker, "Closing quote missing in character literal", toker->err_data); | |
| return last_token; | |
| } else { | |
| tokenizer_CharAdvance(toker, 1); // ' | |
| } | |
| // If it is string | |
| } else if ((toker->src.str[toker->cursor] == '\"') || | |
| ((toker->src.str[toker->cursor] == 'u') && (toker->src.str[toker->cursor+1] == '8') && (toker->src.str[toker->cursor+2] == '"')) || | |
| ((toker->src.str[toker->cursor] == 'u') && (toker->src.str[toker->cursor+1] == '"')) || | |
| ((toker->src.str[toker->cursor] == 'U') && (toker->src.str[toker->cursor+1] == '"')) || | |
| ((toker->src.str[toker->cursor] == 'L') && (toker->src.str[toker->cursor+1] == '"'))) { | |
| kind = Token_Kind_CONSTANT_STRING; | |
| if ((toker->src.str[toker->cursor] == 'u') && (toker->src.str[toker->cursor+1] == '8')) { | |
| tokenizer_CharAdvance(toker, 2); | |
| } else if (toker->src.str[toker->cursor] == 'u') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 'U') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 'L') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| if (toker->src.str[toker->cursor] != '"') { | |
| toker->read_token = (toker->err_func)(toker, "Open quote missing in string literal", toker->err_data); | |
| return last_token; | |
| } else { | |
| tokenizer_CharAdvance(toker, 1); // " | |
| } | |
| if (toker->src.str[toker->cursor] == '"') { | |
| tokenizer_CharAdvance(toker, 1); // " | |
| } else { | |
| while (toker->src.str[toker->cursor] != '"') { | |
| if (toker->src.str[toker->cursor] == '\n') { | |
| toker->read_token = (toker->err_func)(toker, "Newline not allowed in a string literal", toker->err_data); | |
| return last_token; | |
| } | |
| if (toker->src.str[toker->cursor] == '\\') { | |
| tokenizer_CharAdvance(toker, 1); | |
| /* */ if (toker->src.str[toker->cursor] == '\'') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == '"') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == '?') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == '\\') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 'a') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 'b') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 'f') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 'n') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 'r') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 't') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == 'v') { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if ((toker->src.str[toker->cursor] <= '7') && (toker->src.str[toker->cursor] >= '0')) { | |
| for (size_t i = 0; i < 3; i++) { | |
| if ((toker->src.str[toker->cursor] <= '7') && (toker->src.str[toker->cursor] >= '0')) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| } | |
| } else if (toker->src.str[toker->cursor] == 'x') { | |
| tokenizer_CharAdvance(toker, 1); | |
| while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) || | |
| ((toker->src.str[toker->cursor] <= 'F') && (toker->src.str[toker->cursor] >= 'A')) || | |
| ((toker->src.str[toker->cursor] <= 'f') && (toker->src.str[toker->cursor] >= 'a'))) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| } else if (toker->src.str[toker->cursor] == 'u') { | |
| tokenizer_CharAdvance(toker, 1); | |
| for (size_t i = 0; i < 4; i++) { | |
| if (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) || | |
| ((toker->src.str[toker->cursor] <= 'F') && (toker->src.str[toker->cursor] >= 'A')) || | |
| ((toker->src.str[toker->cursor] <= 'f') && (toker->src.str[toker->cursor] >= 'a'))) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else { | |
| toker->read_token = (toker->err_func)(toker, "Universal character in string incomplete, needs four hex digits", toker->err_data); | |
| return last_token; | |
| } | |
| } | |
| } else if (toker->src.str[toker->cursor] == 'U') { | |
| tokenizer_CharAdvance(toker, 1); | |
| for (size_t i = 0; i < 8; i++) { | |
| if (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) || | |
| ((toker->src.str[toker->cursor] <= 'F') && (toker->src.str[toker->cursor] >= 'A')) || | |
| ((toker->src.str[toker->cursor] <= 'f') && (toker->src.str[toker->cursor] >= 'a'))) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } else { | |
| toker->read_token = (toker->err_func)(toker, "Universal character in string incomplete, needs eight hex digits", toker->err_data); | |
| return last_token; | |
| } | |
| } | |
| } else { | |
| toker->read_token = (toker->err_func)(toker, "Unknown escape sequence in string", toker->err_data); | |
| return last_token; | |
| } | |
| } else { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| } | |
| if (toker->src.str[toker->cursor] != '"') { | |
| toker->read_token = (toker->err_func)(toker, "Closing quote missing in string literal", toker->err_data); | |
| return last_token; | |
| } else { | |
| tokenizer_CharAdvance(toker, 1); // " | |
| } | |
| } | |
| } else { | |
| tokenizer_CharAdvance(toker, 1); | |
| char *ident = toker->src.str + toker->cursor; | |
| size_t count = 0; | |
| for (;;) { | |
| char c = ident[count+0]; | |
| if ((c >= 'a') && (c <= 'z')) { count++; continue; } | |
| if ((c >= 'A') && (c <= 'Z')) { count++; continue; } | |
| if ((c >= '0') && (c <= '9')) { count++; continue; } | |
| if (c == '_') { count++; continue; } | |
| break; | |
| } | |
| for (size_t i = 0; i < count; i++) { | |
| tokenizer_CharAdvance(toker, 1); | |
| } | |
| kind = Token_Kind_IDENTIFIER; | |
| } | |
| } break; | |
| case '(': case ')': | |
| case '{': case '}': case '~': case '?': | |
| case ';': case ',': { | |
| kind = cast_val(Token_Kind, toker->src.str[toker->cursor]); | |
| tokenizer_CharAdvance(toker, 1); | |
| } break; | |
| #define TOKEN_CASE_1_2(_ch0, _ch1, _kind1) \ | |
| _ch0: { \ | |
| tokenizer_CharAdvance(toker, 1); \ | |
| if (toker->src.str[toker->cursor] == _ch1) { \ | |
| kind = _kind1; \ | |
| tokenizer_CharAdvance(toker, 1); \ | |
| } else { \ | |
| kind = cast_val(Token_Kind, _ch0); \ | |
| } \ | |
| } break | |
| case TOKEN_CASE_1_2('=', '=', Token_Kind_EQUALITY); | |
| case TOKEN_CASE_1_2('!', '=', Token_Kind_NOTEQUAL); | |
| case TOKEN_CASE_1_2('#', '#', Token_Kind_CONCATENATE); | |
| case TOKEN_CASE_1_2(':', ':', Token_Kind_ATTRIBUTE_NAMESPACE); | |
| case TOKEN_CASE_1_2('*', '=', Token_Kind_MULTIPLY_ASSIGN); | |
| case TOKEN_CASE_1_2('/', '=', Token_Kind_DIVIDE_ASSIGN); | |
| case TOKEN_CASE_1_2('%', '=', Token_Kind_MODULUS_ASSIGN); | |
| case TOKEN_CASE_1_2('^', '=', Token_Kind_XOR_ASSIGN); | |
| case TOKEN_CASE_1_2('[', '[', Token_Kind_ATTRIBUTE_BEGIN); | |
| case TOKEN_CASE_1_2(']', ']', Token_Kind_ATTRIBUTE_END); | |
| #undef TOKEN_CASE_1_2 | |
| #define TOKEN_CASE_1_2_2(_ch0, _ch1, _kind1, _ch2, _kind2) \ | |
| _ch0: { \ | |
| tokenizer_CharAdvance(toker, 1); \ | |
| if (toker->src.str[toker->cursor] == _ch1) { \ | |
| kind = _kind1; \ | |
| tokenizer_CharAdvance(toker, 1); \ | |
| } else if (toker->src.str[toker->cursor] == _ch2) { \ | |
| kind = _kind2; \ | |
| tokenizer_CharAdvance(toker, 1); \ | |
| } else { \ | |
| kind = cast_val(Token_Kind, _ch0); \ | |
| } \ | |
| } break | |
| case TOKEN_CASE_1_2_2('|', '|', Token_Kind_LOGICAL_OR, '=', Token_Kind_OR_ASSIGN); | |
| case TOKEN_CASE_1_2_2('&', '&', Token_Kind_LOGICAL_AND, '=', Token_Kind_AND_ASSIGN); | |
| case TOKEN_CASE_1_2_2('+', '+', Token_Kind_INCREMENT, '=', Token_Kind_ADD_ASSIGN); | |
| #undef TOKEN_CASE_1_2_2 | |
| case '-': { | |
| tokenizer_CharAdvance(toker, 1); | |
| if (toker->src.str[toker->cursor] == '>') { | |
| kind = Token_Kind_PTR_DEREF; | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == '-') { | |
| kind = Token_Kind_DECREMENT; | |
| tokenizer_CharAdvance(toker, 1); | |
| } else if (toker->src.str[toker->cursor] == '=') { | |
| kind = Token_Kind_SUB_ASSIGN; | |
| tokenizer_CharAdvance(toker, 1); | |
| } else { | |
| kind = cast_val(Token_Kind, '-'); | |
| } | |
| } break; | |
| #define TOKEN_CASE_1_2_2_3(_ch0, _ch1, _kind1, _ch2, _kind2, _ch3, _kind3) \ | |
| _ch0: { \ | |
| tokenizer_CharAdvance(toker, 1); \ | |
| if (toker->src.str[toker->cursor] == _ch1) { \ | |
| tokenizer_CharAdvance(toker, 1); \ | |
| kind = _kind1; \ | |
| } else if (toker->src.str[toker->cursor] == _ch2) { \ | |
| tokenizer_CharAdvance(toker, 1); \ | |
| if (toker->src.str[toker->cursor] == _ch3) { \ | |
| tokenizer_CharAdvance(toker, 1); \ | |
| kind = _kind3; \ | |
| } else { \ | |
| kind = _kind2; \ | |
| } \ | |
| } else { \ | |
| kind = cast_val(Token_Kind, _ch0); \ | |
| } \ | |
| } break | |
| case TOKEN_CASE_1_2_2_3('<', '=', Token_Kind_LESSEQ, '<', Token_Kind_SHIFT_LEFT, '=', Token_Kind_SHIFT_LEFT_ASSIGN); | |
| case TOKEN_CASE_1_2_2_3('>', '=', Token_Kind_GREATEQ, '<', Token_Kind_SHIFT_RIGHT, '=', Token_Kind_SHIFT_RIGHT_ASSIGN); | |
| #undef TOKEN_CASE_1_2_2_3 | |
| default: { | |
| tokenizer_CharAdvance(toker, 1); | |
| toker->read_token = (toker->err_func)(toker, "Invalid token", toker->err_data); | |
| return last_token; | |
| } break; | |
| } | |
| toker->read_token = compound_init(Token, { | |
| .span = txtGetSpan(toker->src, begin, toker->cursor), | |
| .kind = kind, | |
| .line = toker->line, | |
| .column = toker->column, | |
| }); | |
| if (toker->read_token.kind == Token_Kind_CONSTANT_INTEGER) { | |
| toker->read_token.value.integer = tokenized_integer; | |
| } else if (toker->read_token.kind == Token_Kind_CONSTANT_FLOATING) { | |
| toker->read_token.value.floating = tokenized_floating; | |
| } | |
| return last_token; | |
| } | |
| pragma_clang("clang diagnostic pop"); | |
| /************************************************************************************************ | |
| * B-Tree | |
| */ | |
| // B-Tree implementation | |
| // Reference: Chapter 18 of Introduction to Algorithms - Fourth Edition by Cormen, et al. | |
| // Some changes have been made to make the implementation more amenable to future optimizations | |
| // (SIMD or otherwise). | |
| // First, the idea of "minimum degree" has been discarded in favour of having a maximum count. | |
| // This means that we store an even number of keys instead of an odd number. | |
| // Second, there is no leaf boolean; the leaf property is known by comparing the first child to nullptr. | |
| // WARN(naman): This BTree doesn't rebalances itself when keys are dropped. This means that the size | |
| // of the tree only ever goes up. Thus, don't use this in places where lots of deletions will | |
| // be taking place, since a unnecessarily large tree will slow down the lookups. | |
| // The value of M is chosen so that the all keys can be compared using 5 256-wide SSE instructions. | |
| // In addition, this also makes the BTree_Node align with cache line sizes of 64, 128, 256 and 512 bytes. | |
| #define M 20 | |
| typedef struct BTree_Node { | |
| // In the keys and data array, [M/2 - 1, M] elements have to be present | |
| // NOTE(naman): More often used members are on top together for cache friendliness. | |
| Uint64 keys[M]; // Called k in Cormen | |
| Size count; // Called n in Cormen | |
| struct BTree_Node *children[M + 1]; // Called c in Cormen | |
| void *data[M]; | |
| struct BTree_Node *next, *prev; // Unused for now | |
| } BTree_Node; | |
| static_assert(sizeof(BTree_Node) == 512, "BTree_Node's size is not expected"); | |
| typedef struct BTree { | |
| Memory_Push *parent_memory_push; | |
| BTree_Node *root; | |
| } BTree; | |
| header_function | |
| void btreeInit (BTree *b, Memory_Push *parent_memory_push) | |
| { | |
| memzero(b); | |
| b->parent_memory_push = parent_memory_push; | |
| b->root = cast_val(BTree_Node *, memPushAllotA(b->parent_memory_push, sizeof(*b->root), sizeof(*b->root))); | |
| } | |
| // NOTE(naman): There is no btreeDelete function since the only allocations are done on the | |
| // push allocator. | |
| header_function | |
| void* btree_FindReplace (BTree *b, Uint64 key, Bool replace_value, void *replacement) | |
| { | |
| BTree_Node *n = b->root; | |
| while (n != nullptr) { | |
| Size i; | |
| Bool found_equal = false; | |
| for (i = 0; i < n->count; i++) { | |
| if (n->keys[i] == key) { | |
| found_equal = true; | |
| break; | |
| } else if (n->keys[i] > key) { | |
| break; | |
| } | |
| } | |
| if (found_equal) { | |
| void *old = n->data[i]; | |
| if (replace_value) n->data[i] = replacement; | |
| return old; | |
| } else if (n->children[0] == nullptr) { // Leaf Node | |
| return nullptr; | |
| } else { | |
| // If greater found, i is pointing to the key just greater, meaning the i-th child is | |
| // between the just smaller and just greater keys. If greater was not found, then i is | |
| // equal to n->count, which also points to the last child. | |
| n = n->children[i]; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| header_function | |
| void* btreeSearch (BTree *b, Uint64 key) { | |
| void *result = btree_FindReplace(b, key, false, nullptr); | |
| return result; | |
| } | |
| header_function | |
| void* btreeReplace (BTree *b, Uint64 key, void *new_value) { | |
| void *result = btree_FindReplace(b, key, true, new_value); | |
| return result; | |
| } | |
| header_function | |
| void* btreeRemove (BTree *b, Uint64 key) { | |
| void *result = btree_FindReplace(b, key, true, nullptr); | |
| return result; | |
| } | |
| // NOTE(naman): Function returns the previous value, if any. | |
| header_function | |
| void btree_SplitChild (BTree *b, BTree_Node *parent, Size i) | |
| { | |
| // All indices in this function's comments (I, T, etc.) are zero indexed. Play close attention. | |
| // To mark them against regular numbers, they are written in capital. `i` and `I` are both the same, | |
| // `i` is used when talking about numbers (count, etc.), while `I` is used when talking about indices. | |
| // This function gets called if the I-th child of the node has become full (i.e., has m keys) | |
| // and need to be split. | |
| BTree_Node *full = parent->children[i]; | |
| BTree_Node *newnode = cast_val(BTree_Node *, memPushAllotA(b->parent_memory_push, sizeof(*newnode), sizeof(*newnode))); | |
| // We will move the last m/2-1 keys from `full` into `newnode`. Since `full` had m keys, this means that | |
| // `full` will be left with m/2+1 keys from [0...M/2] and m/2-1 keys from [M/2+1...M-1] wil become `newnode`'s keys | |
| // from [0..M/2-1]. | |
| newnode->count = M/2 - 1; | |
| memcpy(&newnode->keys[0], &full->keys[M/2 + 1], newnode->count * sizeof(newnode->keys[0])); | |
| memcpy(&newnode->data[0], &full->data[M/2 + 1], newnode->count * sizeof(newnode->data[0])); | |
| // If the `full` node is not a leaf, it has a bunch of children. Since the trailing keys got moved, | |
| // the children whose points fall between those keys also have to be moved. Because we moved m/2-1 keys, | |
| // there would be m/2 children associated with them that also need to be moved. This means that the | |
| // M/2-th key in `full` will have no right-child anymore. But that's okay, since the M/2-th | |
| // key will get get moved to `parent` anyways. So, we will move m/2 children from `full`'s [M/2+1...M] | |
| // to `newnode`'s [0...M/2-1]. | |
| if (full->children[0] != nullptr) { // Not a leaf | |
| memcpy(&newnode->children[0], &full->children[M/2+1], (newnode->count + 1) * sizeof(newnode->children[0])); | |
| } | |
| // Finally, we set the `full`'s count to m/2. Remember that `full` actually still has a total of m/2+1 elements. | |
| // However, the element on index M/2 will soon get moved to the `parent`. | |
| full->count = M/2; | |
| // But before we can move `full`'s M/2 element and attach `newnode` as a new child, we have to make space | |
| // for it in `parent`. First, let's move the children. Let parent->count be n. If the total number | |
| // of keys that the parent has is n from 0 to N-1, the children would be n+1 from 0 to N. Since the | |
| // `full` was attached at index I, we nee to move all children from [I+1...N] to [I+2...N+1], so that | |
| // we can add `newnode` as a child at index I+1. | |
| for (Size j = (parent->count + 1); j >= (i + 2); j--) { | |
| parent->children[j] = parent->children[j-1]; | |
| } | |
| parent->children[i+1] = newnode; | |
| // Now, the turn for keys. Since `full`'s M/2 element has to be move to `parent`'s I element (right between | |
| // the I-th and (I+1)-th child), we need to make space for it by moving elements from [I...N-1] to [I+1...N]. | |
| for (Size j = parent->count; j >= (i + 1); j--) { | |
| parent->keys[j] = parent->keys[j-1]; | |
| parent->data[j] = parent->data[j-1]; | |
| } | |
| // Now that there is a space in `parent`'s key array, let's put `full`'s M/2 element there. | |
| parent->keys[i] = full->keys[M/2]; | |
| parent->data[i] = full->data[M/2]; | |
| parent->count++; | |
| } | |
| pragma_msvc("warning ( push )"); | |
| pragma_msvc("warning ( disable: 6385 )"); // Analyze: READ_OVERRUN (Reading invalid data from 'n->keys' and 'n->children') | |
| header_function | |
| void* btreeInsert (BTree *b, Uint64 key, void *datum) | |
| { | |
| if (b->root->count == M) { | |
| // This is a clever hack. Cormen wanted to use the existing splitting function (btree_SplitChild) to spit the root too. | |
| // But splitting requires transferring one item to our parent, and the root has no parent. So, they temporarily create | |
| // an invalid tree by creating a new node with zero elements and setting the root as its child. But the splitting process | |
| // adds one element and one more child (the new splitted node) to the new root, thus making the tree valid again. | |
| BTree_Node *s = cast_val(BTree_Node *, memPushAllotA(b->parent_memory_push, sizeof(*s), sizeof(*s))); | |
| s->children[0] = b->root; | |
| b->root = s; | |
| btree_SplitChild(b, b->root, 0); | |
| } | |
| BTree_Node *n = b->root; | |
| while (n != nullptr) { | |
| Size i = n->count; | |
| if (n->children[0] == nullptr) { // Leaf | |
| if ((key >= n->keys[0]) && (key <= n->keys[i-1])) { // If the key is in the space of this node | |
| for (Size j = 0; j < i; j++) { // Check if the key has already been inserted | |
| if (key == n->keys[j]) { | |
| void *result = n->data[j]; | |
| n->data[j] = datum; | |
| return result; | |
| } | |
| } | |
| } | |
| while ((i >= 1) && (key < n->keys[i-1])) { // Else move the data | |
| n->keys[i] = n->keys[i-1]; | |
| n->data[i] = n->data[i-1]; | |
| i--; | |
| } | |
| n->keys[i] = key; // And copy the key | |
| n->data[i] = datum; | |
| n->count++; | |
| return nullptr; | |
| } else { | |
| while ((i >= 1) && (key < n->keys[i-1])) { | |
| i--; | |
| } | |
| if (n->children[i]->count == M) { | |
| btree_SplitChild(b, n, i); | |
| if (key > n->keys[i]) { | |
| i++; | |
| } | |
| } | |
| n = n->children[i]; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| pragma_msvc("warning ( pop )"); | |
| header_function | |
| void* btreeSwitch (BTree *b, Uint64 key, void *oldval, void *newval) | |
| { | |
| return oldval ? btreeReplace(b, key, newval) : btreeInsert(b, key, newval); | |
| } | |
| // Return false to prematuerely stop iteration | |
| #define BTREE_ITERATOR_FUNC(name) Bool name (void *funcdata, Uint64 key, void* value) | |
| header_function | |
| Bool btree_IterateNode (BTree_Node *bn, BTREE_ITERATOR_FUNC((*func)), void *funcdata) | |
| { | |
| if (bn == nullptr) return true; | |
| Size i; | |
| for (i = 0; i < bn->count; i++) { | |
| if (btree_IterateNode(bn->children[i], func, funcdata) == false) return false; | |
| if (func(funcdata, bn->keys[i], bn->data[i]) == false) return false; | |
| } | |
| if (btree_IterateNode(bn->children[i], func, funcdata) == false) return false; | |
| return true; | |
| } | |
| header_function | |
| Bool btreeIterate (BTree *b, BTREE_ITERATOR_FUNC((*func)), void *funcdata) | |
| { | |
| Bool result = btree_IterateNode(b->root, func, funcdata); | |
| return result; | |
| } | |
| #undef M | |
| /************************************************************************************************ | |
| * Hashmap | |
| */ | |
| // WARN(naman): Same performance characteristics as the BTree. See it's documentation for more details | |
| typedef struct Hashmap_Entry { | |
| struct Hashmap_Entry *next; | |
| Uint64 hash; | |
| void *key; | |
| void *value; | |
| } Hashmap_Entry; | |
| typedef struct Hashmap { | |
| Memory *parent_memory; | |
| BTree btree; | |
| Memory_Push btree_allocator; | |
| COMPARE_FUNC((*compare_func)); | |
| Memory_Slab entry_allocator; | |
| } Hashmap; | |
| header_function | |
| void hashmapCreate (Hashmap *hm, Uint entry_count, COMPARE_FUNC((*compare_func)), Memory *parent_memory) | |
| { | |
| memzero(hm); | |
| hm->compare_func = compare_func; | |
| hm->parent_memory = parent_memory; | |
| memPushCreate(&hm->btree_allocator, hm->parent_memory, HUNDRED * sizeof(*(hm->btree.root))); | |
| btreeInit(&hm->btree, &hm->btree_allocator); | |
| memSlabCreate(&hm->entry_allocator, hm->parent_memory, entry_count, sizeof(Hashmap_Entry)); | |
| } | |
| // Return false to prematuerely stop iteration | |
| #define HASHMAP_ITERATOR_FUNC(name) Bool name (void *funcdata, Hashmap_Entry *entry) | |
| struct Hashmap_Iterator_Data_ { | |
| HASHMAP_ITERATOR_FUNC((*func)); | |
| void *funcdata; | |
| }; | |
| header_function | |
| BTREE_ITERATOR_FUNC(hashmap_IterateNode) | |
| { | |
| unused_variable(key); | |
| struct Hashmap_Iterator_Data_ *data = cast_val(struct Hashmap_Iterator_Data_ *, funcdata); | |
| Hashmap_Entry *list = cast_val(Hashmap_Entry *, value); | |
| for (Hashmap_Entry *e = list, *en; e != nullptr; e = en) { | |
| en = e->next; | |
| if ((data->func)(data->funcdata, e) == false) return false; | |
| } | |
| return true; | |
| } | |
| header_function | |
| Bool hashmapIterate (Hashmap *hm, HASHMAP_ITERATOR_FUNC((*func)), void *funcdata) | |
| { | |
| struct Hashmap_Iterator_Data_ data = { | |
| .func = func, | |
| .funcdata = funcdata, | |
| }; | |
| Bool result = btree_IterateNode(hm->btree.root, hashmap_IterateNode, &data); | |
| return result; | |
| } | |
| header_function | |
| HASHMAP_ITERATOR_FUNC(hashmap_DeleteNodeList) | |
| { | |
| Hashmap *hm = cast_val(Hashmap *, funcdata); | |
| memSlabRemit(&hm->entry_allocator, entry); | |
| return true; | |
| } | |
| header_function | |
| void hashmapDelete (Hashmap *hm) | |
| { | |
| hashmapIterate(hm, hashmap_DeleteNodeList, hm); | |
| memSlabDelete(&hm->entry_allocator); | |
| memPushDelete(&hm->btree_allocator); | |
| memzero(hm); | |
| } | |
| // Returns the existing data from BTree and saves out matched entry into matched pointer | |
| header_function | |
| Hashmap_Entry* hashmap_FindEntry (Hashmap *hm, Uint64 hash, void *key, Hashmap_Entry **matched) | |
| { | |
| Hashmap_Entry *list = cast_val(Hashmap_Entry *, btreeSearch(&hm->btree, hash)); | |
| for (Hashmap_Entry *ei = list; ei != nullptr; ei = ei->next) { | |
| if (hm->compare_func(ei->key, key) == 0) { | |
| if (matched) *matched = ei; | |
| return list; | |
| } | |
| } | |
| return list; | |
| } | |
| header_function | |
| void hashmapInsert (Hashmap *hm, Uint64 hash, void *key, void *value) | |
| { | |
| Hashmap_Entry *matched = nullptr; | |
| Hashmap_Entry *list = hashmap_FindEntry(hm, hash, key, &matched); | |
| if (matched) { | |
| matched->value = value; | |
| return; | |
| } | |
| Hashmap_Entry *e = cast_ptr(Hashmap_Entry *, memSlabAllot(&hm->entry_allocator)); | |
| *e = compound_init(Hashmap_Entry, { | |
| .next = list, | |
| .hash = hash, | |
| .key = key, | |
| .value = value, | |
| }); | |
| btreeSwitch(&hm->btree, hash, list, e); | |
| } | |
| header_function | |
| void* hashmapSearch (Hashmap *hm, Uint64 hash, void *key) | |
| { | |
| Hashmap_Entry *matched = nullptr; | |
| hashmap_FindEntry(hm, hash, key, &matched); | |
| return matched ? matched->value : nullptr; | |
| } | |
| header_function | |
| void hashmapRemove (Hashmap *hm, Uint64 hash, void *key) | |
| { | |
| Hashmap_Entry *matched = nullptr; | |
| Hashmap_Entry *list = hashmap_FindEntry(hm, hash, key, &matched); | |
| if (matched) { | |
| if (list == matched) { | |
| btreeSwitch(&hm->btree, hash, list, list->next); | |
| } else { | |
| for (Hashmap_Entry *e = list; e != nullptr; e = e->next) { | |
| if (e->next == matched) { | |
| Hashmap_Entry *old = e->next; | |
| e->next = e->next->next; | |
| memSlabRemit(&hm->entry_allocator, old); | |
| return; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /****************************************************************************************** | |
| * Segmented Array (segar) | |
| * | |
| * Usage: | |
| * typedef segarOf(int) Segar_Int; | |
| * Segar_Int sgi = segarMake(&mempush); | |
| * segarAdd(&sgi, 42); | |
| * Uint32 sgi_len = segarLen(&sgi); // sgi_len == 1 | |
| * int val = segarGet(&sgi, 0); // val == 42 | |
| * int *ptr = segarGetPtr(&sgi, 0); // *ptr == 42 | |
| * int *data = segarAcquireContiguousBuffer(&sgi, &memory); | |
| * segarReleaseContiguousBuffer(data); | |
| * | |
| * Misc Functions (useful when doing low-level operations): | |
| * segarSegmentsCount(&sgi) | |
| * segarSegment(&sgi, segment_index) | |
| * segarSegmentLen(&sgi, segment_index) | |
| * segarTypeSize(&sgi) | |
| * | |
| * Formulae Derivation: | |
| * _________________________________________________________________________________________________ | |
| * | No. | Segment Index | Segment Size | Total Elements | Indices | | |
| * |--------|-------------------|------------------|--------------------|--------------------------| | |
| * | #1 | 0 | 1 | 1 | 0 | | |
| * | #2 | 1 | 2 | 3 | 1 2 | | |
| * | #3 | 2 | 4 | 7 | 3 4 5 6 | | |
| * | #4 | 3 | 8 | 15 | 7 8 9 10 11 12 13 14 | | |
| * | #5 | 4 | 16 | 31 | 15 16 17 18 19 20 21 22 | | |
| * | | | | | 23 24 25 26 27 28 29 30 | | |
| * |--------|-------------------|------------------|--------------------|--------------------------| | |
| * | | si | 2^si | 2^(si+1) - 1 | [2^si - 1, 2^(si+1) - 2] | | |
| * |--------|-------------------|------------------|--------------------|--------------------------| | |
| * | |
| * so, given an index I: | |
| * seg = log2(I + 1); // which segment the index `I` falls into | |
| * lo = (1u << seg) - 1; // The lowest index in that segment | |
| * rel_idx = I - lo; // The relative index of element at `I` locally within the `seg` segment | |
| */ | |
| #define SEGAR_SEGS_ 31 /* Setting to 32 creates overflow in some bitwise formulae */ | |
| // Usage: typedef segarOf(int) Segar_Int; | |
| #if defined(ENV_LANG_C) | |
| # define segarOf(_type) struct { Memory_Push *mempush; Uint32 length; Byte _pad[4]; _type **segments; } | |
| #elif defined(ENV_LANG_CXX) | |
| template<typename T> | |
| struct Segar_Template { Memory_Push *mempush; Uint32 length; Byte _pad[4]; T **segments; }; | |
| #define segarOf(_type) Segar_Template<_type> | |
| #endif | |
| pragma_msvc("warning ( push )"); | |
| pragma_msvc("warning ( disable: 4116 )"); // unnamed type definition in parentheses | |
| static_assert(sizeof(segarOf(int)) == 24, "Invalid Segar size"); | |
| pragma_msvc("warning ( pop )"); | |
| // Usage: Segar_Int sgi = segarMake(&mempush); | |
| // or: data.sgi = (typeof(data.sgi))segarMake(&mempush); | |
| #define segarMake(_mempush_ptr) { .mempush = (_mempush_ptr) } | |
| // Usage: Uint32 sgi_len = segarLen(&sgi); | |
| #if defined(ENV_LANG_C) | |
| # define segarLen(_segar_ptr) ((_segar_ptr)->length) | |
| #elif defined(ENV_LANG_CXX) | |
| template<typename T> | |
| Uint32 segarLen (const Segar_Template<T> *sp) { return sp->length; } | |
| #endif | |
| header_function Uint32 segar_GetSegmentIndexFromAbsoluteIndex (Uint32 abs_index); | |
| header_function Uint32 segar_GetCountOfElementsFromSegmentIndex (Uint32 segment_index, Uint32 length); | |
| header_function void* segar_GetPointerAtAbsoluteIndex (Memory_Push *mempush, | |
| Uint32 length, Uint32 absolute_index, Uint32 type_size, | |
| void ***segments); | |
| header_function void* segar_CopyDataIntoBuffer (void *buffer, Uint32 length, Uint32 type_size, void **segments); | |
| header_function void* segar_LinearizeDataIntoBuffer (Uint32 length, Uint32 type_size, void **segments, | |
| Memory *memory); | |
| header_function void segar_FreeLinearizedBuffer (void *buffer); | |
| // Usage: int *ip = segarGetPtr(&sgi, 0); | |
| #if defined(ENV_LANG_C) | |
| # define segarGetPtr(_segar_ptr, _index) \ | |
| (cast_ptr(typeof((_segar_ptr)->segments[0]), \ | |
| (segar_GetPointerAtAbsoluteIndex((_segar_ptr)->mempush, \ | |
| segarLen(_segar_ptr), \ | |
| (_index), \ | |
| sizeof(*((_segar_ptr)->segments[0])), \ | |
| (void***)&((_segar_ptr)->segments))))) | |
| #elif defined(ENV_LANG_CXX) | |
| template<typename T> | |
| T* segarGetPtr (const Segar_Template<T> *sp, Uint32 index) | |
| { | |
| pragma_clang("clang diagnostic push"); | |
| pragma_clang("clang diagnostic ignored \"-Wdeclaration-after-statement\""); | |
| T *result = cast_ptr(T*, segar_GetPointerAtAbsoluteIndex(sp->mempush, segarLen(sp), index, sizeof(T), | |
| cast_const(void***, cast_ptr(void**const*, &sp->segments)))); | |
| pragma_clang("clang diagnostic pop"); | |
| return result; | |
| } | |
| #endif | |
| // Usage: int i = segarGet(&sgi, 0); | |
| #if defined(ENV_LANG_C) | |
| # define segarGet(_segar_ptr, _index) (*(segarGetPtr(_segar_ptr, _index))) | |
| #elif defined(ENV_LANG_CXX) | |
| template<typename T> | |
| T segarGet (const Segar_Template<T> *sp, Uint32 index) | |
| { | |
| T *ptr = segarGetPtr(sp, index); | |
| T result = *ptr; | |
| return result; | |
| } | |
| #endif | |
| // Usage: segarAdd(&sgi, 1); | |
| #if defined(ENV_LANG_C) | |
| # define segarAdd(_segar_ptr, _value) \ | |
| (*(segarGetPtr(_segar_ptr, segarLen(_segar_ptr))) = (_value), \ | |
| ((_segar_ptr)->length++)) /* Using segar->length++ instead of segarLen(segar)++ to ensure the variable itself | |
| is incremented and not some temporary value which might be returned by segarLen() | |
| if the implementation of segarLen() changes */ | |
| #elif defined(ENV_LANG_CXX) | |
| template<typename T> | |
| Uint32 segarAdd (Segar_Template<T> *sp, T value) | |
| { | |
| T *ptr = segarGetPtr(sp, segarLen(sp)); | |
| *ptr = value; | |
| Uint32 result = sp->length++; // This weird return value is to make it match what C version above returns | |
| return result; | |
| } | |
| #endif | |
| // Usage: segarSpliceAt(&sgi, 0, -1); | |
| #if defined(ENV_LANG_C) | |
| # define segarSpliceAt(_segar_ptr, _index, _value) \ | |
| ((*cast_ptr(typeof((_segar_ptr)->segments[0]), \ | |
| (segar_MoveRightFromIndex((_segar_ptr)->mempush, \ | |
| segarLen(_segar_ptr), \ | |
| (_index), \ | |
| sizeof(*((_segar_ptr)->segments[0])), \ | |
| (void***)&((_segar_ptr)->segments)))) = (_value)), \ | |
| ((_segar_ptr)->length++)) /* Using segar->length++ instead of segarLen(segar)++ to ensure the variable itself | |
| is incremented and not some temporary value which might be returned by segarLen() | |
| if the implementation of segarLen() changes */ | |
| #elif defined(ENV_LANG_CXX) | |
| template<typename T> | |
| Uint32 segarSpliceAt (Segar_Template<T> *sp, Uint32 index, T value) | |
| { | |
| T *ptr = cast_ptr(T*, segar_MoveRightFromIndex(sp->mempush, segarLen(sp), index, sizeof(T), cast_ptr(void***, &sp->segments))); | |
| *ptr = value; | |
| Uint32 result = sp->length++; // This weird return value is to make it match what C version above returns | |
| return result; | |
| } | |
| #endif | |
| // Usage: segarSpliceAt(&sgi, 0, -1); | |
| #if defined(ENV_LANG_C) | |
| # define segarDeleteAt(_segar_ptr, _index) \ | |
| ((segar_MoveLeftOnIndex((_segar_ptr)->mempush, \ | |
| segarLen(_segar_ptr), \ | |
| (_index), \ | |
| sizeof(*((_segar_ptr)->segments[0])), \ | |
| (void***)&((_segar_ptr)->segments))), \ | |
| ((_segar_ptr)->length--)) | |
| #elif defined(ENV_LANG_CXX) | |
| template<typename T> | |
| Uint32 segarDeleteAt (Segar_Template<T> *sp, Uint32 index) | |
| { | |
| segar_MoveLeftOnIndex(sp->mempush, segarLen(sp), index, sizeof(T), cast_ptr(void***, &sp->segments)); | |
| Uint32 result = sp->length--; // This weird return value is to make it match what C version above returns | |
| return result; | |
| } | |
| #endif | |
| // int *data = segarAcquireContiguousBuffer(&sgi, &memory); | |
| #if defined(ENV_LANG_C) | |
| # define segarAcquireContiguousBuffer(_segar_ptr, _memory) \ | |
| segar_LinearizeDataIntoBuffer(segarLen(_segar_ptr), \ | |
| sizeof(*((_segar_ptr)->segments[0])), \ | |
| &((_segar_ptr)->segments), \ | |
| (_memory)) | |
| #elif defined(ENV_LANG_CXX) | |
| template<typename T> | |
| T* segarAcquireContiguousBuffer (const Segar_Template<T> *sp, Memory *memory) | |
| { | |
| T *result = cast_ptr(T*, segar_LinearizeDataIntoBuffer(segarLen(sp), sizeof(T), cast_ptr(void**, sp->segments), memory)); | |
| return result; | |
| } | |
| #endif | |
| // segarReleaseContiguousBuffer(data); | |
| #if defined(ENV_LANG_C) | |
| # define segarReleaseContiguousBuffer(_buffer) segar_FreeLinearizedBuffer(_buffer) | |
| #elif defined(ENV_LANG_CXX) | |
| template<typename T> | |
| void segarReleaseContiguousBuffer (T *buffer) { segar_FreeLinearizedBuffer(cast_ptr(void*, buffer)); } | |
| #endif | |
| #if defined(ENV_LANG_C) | |
| # define segarSegmentsCount(_segar_ptr) (segar_GetSegmentIndexFromAbsoluteIndex(segarLen(_segar_ptr) - 1) + 1) | |
| #elif defined(ENV_LANG_CXX) | |
| template<typename T> | |
| Uint32 segarSegmentsCount (const Segar_Template<T> *sp) | |
| { | |
| Uint32 len = segarLen(sp); | |
| Uint32 last_segment = segar_GetSegmentIndexFromAbsoluteIndex(len - 1) + 1; | |
| return last_segment; | |
| } | |
| #endif | |
| // int *data = segarAcquireContiguousPushBuffer(&sgi, &mempush); | |
| #if defined(ENV_LANG_C) | |
| # define segarAcquireContiguousPushBuffer(_segar_ptr, _mempush) \ | |
| segar_CopyDataIntoBuffer(memPushAllot((_mempush), \ | |
| (sizeof(*((_segar_ptr)->segments[0])) * \ | |
| segarLen(_segar_ptr))), \ | |
| segarLen(_segar_ptr), \ | |
| sizeof(*((_segar_ptr)->segments[0])), \ | |
| &((_segar_ptr)->segments)) | |
| #elif defined(ENV_LANG_CXX) | |
| template<typename T> | |
| T* segarAcquireContiguousPushBuffer (const Segar_Template<T> *sp, Memory_Push *mempush) | |
| { | |
| T *buffer = cast_ptr(typeof(buffer), memPushAllot(mempush, segarLen(sp) * sizeof(T))); | |
| segar_CopyDataIntoBuffer(buffer, segarLen(sp), sizeof(T), cast_ptr(void**, sp->segments)); | |
| return buffer; | |
| } | |
| #endif | |
| #if defined(ENV_LANG_C) | |
| # define segarSegment(_segar_ptr, _seg_idx) ((_segar_ptr)->segments[(_seg_idx)]) | |
| #elif defined(ENV_LANG_CXX) | |
| template<typename T> | |
| T* segarSegment (const Segar_Template<T> *sp, Uint32 seg_idx) | |
| { | |
| T *result = sp->segments[seg_idx]; | |
| return result; | |
| } | |
| #endif | |
| #if defined(ENV_LANG_C) | |
| # define segarSegmentLen(_segar_ptr, _seg_idx) (segar_GetCountOfElementsFromSegmentIndex((_seg_idx), segarLen(_segar_ptr))) | |
| #elif defined(ENV_LANG_CXX) | |
| template<typename T> | |
| Uint32 segarSegmentLen (const Segar_Template<T> *sp, Uint32 seg_idx) | |
| { | |
| Uint32 len = segarLen(sp); | |
| Uint32 seglen = segar_GetCountOfElementsFromSegmentIndex(seg_idx, len); | |
| return seglen; | |
| } | |
| #endif | |
| #if defined(ENV_LANG_C) | |
| # define segarTypeSize(_segar_ptr) (sizeof((_segar_ptr)->segments[0][0])) | |
| #elif defined(ENV_LANG_CXX) | |
| template<typename T> | |
| Uint32 segarTypeSize (const Segar_Template<T> *sp) | |
| { | |
| Uint32 size = sizeof(sp->segments[0][0]); | |
| return size; | |
| } | |
| #endif | |
| header_function Uint32 segar_GetSegmentIndexFromAbsoluteIndex (Uint32 abs_index) { Uint32 r = bitLog2(abs_index + 1); return r; } | |
| header_function Uint32 segar_GetSegmentSizeFromSegmentIndex (Uint32 seg_index) { Uint32 r = (1u << seg_index); return r; } | |
| header_function Uint32 segar_GetTotalElementsFromSegmentIndex (Uint32 seg_index) { Uint32 r = (1u << (seg_index + 1)) - 1; return r; } | |
| header_function Uint32 segar_GetSegmentLowBoundFromSegmentIndex (Uint32 seg_index) { Uint32 r = (1u << seg_index) - 1; return r; } | |
| header_function Uint32 segar_GetSegmentHighBoundFromSegmentIndex (Uint32 seg_index) { Uint32 r = (1u << (seg_index + 1)) - 2; return r; } | |
| header_function Uint32 segar_GetRelativeIndexFromAbsoluteIndex (Uint32 abs_index) | |
| { | |
| Uint32 seg_index = segar_GetSegmentIndexFromAbsoluteIndex(abs_index); | |
| Uint32 seg_low = segar_GetSegmentLowBoundFromSegmentIndex(seg_index); | |
| Uint32 rel_index = abs_index - seg_low; | |
| return rel_index; | |
| } | |
| header_function | |
| Uint32 segar_GetCountOfElementsFromSegmentIndex (Uint32 segment_index, Uint32 length) | |
| { | |
| Uint32 last_index = length - 1; | |
| Uint32 lo = segar_GetSegmentLowBoundFromSegmentIndex(segment_index); // Inclusive | |
| Uint32 rel_idx = last_index - lo; /* Relative index assuming that last_index was in segment, | |
| might be larger than the segment's maximum length */ | |
| Uint32 hi = segar_GetSegmentHighBoundFromSegmentIndex(segment_index); // Inclusive | |
| Uint32 length_segment = hi - lo; // If the entire segment was filled | |
| Uint32 length_maximum = rel_idx; // If the segment was only partially filled, might be larger than the segment's maximum length | |
| Uint32 length_actual = minimum(length_segment, length_maximum) + 1; /* Actual number of elements present, | |
| +1 is due to the inclusivity */ | |
| return length_actual; | |
| } | |
| header_function | |
| Uint32 segar_CheckAndGetSegmentIndex (Memory_Push *mempush, Uint32 length, Uint32 absolute_index, Uint32 type_size, void ***segments) | |
| { | |
| debugAssert(absolute_index <= length); // Equal for the segarAdd case | |
| Uint32 xxx = segar_GetSegmentLowBoundFromSegmentIndex(SEGAR_SEGS_); | |
| debugAssert(absolute_index <= xxx); | |
| Uint32 segment_index = segar_GetSegmentIndexFromAbsoluteIndex(absolute_index); | |
| debugAssert(segment_index < SEGAR_SEGS_); | |
| if (*segments == nullptr) { | |
| *segments = cast_ptr(void**, memPushAllot(mempush, SEGAR_SEGS_ * sizeof(**segments))); | |
| } | |
| if ((*segments)[segment_index] == nullptr) { | |
| Uint32 segment_size = segar_GetSegmentSizeFromSegmentIndex(segment_index); | |
| (*segments)[segment_index] = memPushAllot(mempush, type_size * segment_size); | |
| } | |
| return segment_index; | |
| } | |
| header_function | |
| Uint32 segar_CheckAndGetRelativeIndex (Uint32 length, Uint32 absolute_index) | |
| { | |
| debugAssert(absolute_index <= length); // Equal for the segarAdd case | |
| debugAssert(absolute_index <= segar_GetSegmentLowBoundFromSegmentIndex(SEGAR_SEGS_)); | |
| Uint32 segment_index = segar_GetSegmentIndexFromAbsoluteIndex(absolute_index); | |
| debugAssert(segment_index < SEGAR_SEGS_); | |
| Uint32 segment_size = segar_GetSegmentSizeFromSegmentIndex(segment_index); | |
| Uint32 relative_index = segar_GetRelativeIndexFromAbsoluteIndex(absolute_index); | |
| debugAssert(relative_index < segment_size); | |
| return relative_index; | |
| } | |
| header_function | |
| void* segar_GetPointerAtAbsoluteIndex (Memory_Push *mempush, Uint32 length, Uint32 absolute_index, Uint32 type_size, void ***segments) | |
| { | |
| Uint32 segment_index = segar_CheckAndGetSegmentIndex(mempush, length, absolute_index, type_size, segments); | |
| Uint32 relative_index = segar_CheckAndGetRelativeIndex(length, absolute_index); | |
| void *segment_ptr = (*segments)[segment_index]; | |
| Byte *segment_first_element_ptr = cast_ptr(Byte*, segment_ptr); | |
| Byte *ptr = segment_first_element_ptr + (type_size * relative_index); | |
| return cast_ptr(void*, ptr); | |
| } | |
| header_function | |
| void* segar_MoveRightFromIndex (Memory_Push *mempush, Uint32 length, Uint32 pivot_index, Uint32 type_size, void ***segments) | |
| { | |
| debugAssert(pivot_index < length); | |
| for (Uint32 i = length; i > pivot_index; i--) { | |
| void *right_elem_ptr = segar_GetPointerAtAbsoluteIndex(mempush, length, i, type_size, segments); | |
| void *left_elem_ptr = segar_GetPointerAtAbsoluteIndex(mempush, length, i - 1, type_size, segments); | |
| memcpy(right_elem_ptr, left_elem_ptr, type_size); | |
| } | |
| void *result = segar_GetPointerAtAbsoluteIndex(mempush, length, pivot_index, type_size, segments); | |
| memset(result, 0, type_size); | |
| return result; | |
| } | |
| header_function | |
| void segar_MoveLeftOnIndex (Memory_Push *mempush, Uint32 length, Uint32 pivot_index, Uint32 type_size, void ***segments) | |
| { | |
| debugAssert(pivot_index < length); | |
| for (Uint32 i = pivot_index; i < length; i++) { | |
| void *left_elem_ptr = segar_GetPointerAtAbsoluteIndex(mempush, length, i, type_size, segments); | |
| void *right_elem_ptr = segar_GetPointerAtAbsoluteIndex(mempush, length, i + 1, type_size, segments); | |
| memcpy(left_elem_ptr, right_elem_ptr, type_size); | |
| } | |
| void *rightmost_ptr = segar_GetPointerAtAbsoluteIndex(mempush, length, length - 1, type_size, segments); | |
| memset(rightmost_ptr, 0, type_size); | |
| } | |
| header_function | |
| void* segar_CopyDataIntoBuffer (void *buffer, Uint32 length, Uint32 type_size, void **segments) | |
| { | |
| Uint32 last_index = length - 1; | |
| Uint32 total_segments = segar_GetSegmentIndexFromAbsoluteIndex(last_index); | |
| for (Uint segidx = 0; segidx <= total_segments; segidx++) { | |
| Uint32 length_to_copy = segar_GetCountOfElementsFromSegmentIndex(segidx, length); | |
| Uint32 begin_offset = segar_GetSegmentLowBoundFromSegmentIndex(segidx) * type_size; | |
| Uint32 total_size = length_to_copy * type_size; // +1 because of both `hi` and `rel_idx` being inclusive | |
| memcpy(cast_ptr(Byte*, buffer) + begin_offset, segments[segidx], total_size); | |
| } | |
| return buffer; // To make the segarAcquireContiguousPushBuffer C macro work | |
| } | |
| header_function | |
| void* segar_LinearizeDataIntoBuffer (Uint32 length, Uint32 type_size, void **segments, Memory *memory) | |
| { | |
| void *buffer = nullptr; { | |
| void *allocation = memAllot(memory, (type_size * length) + sizeof(memory)); | |
| *cast_ptr(Memory**, allocation) = memory; | |
| buffer = cast_ptr(Byte*, allocation) + sizeof(memory); | |
| } | |
| segar_CopyDataIntoBuffer(buffer, length, type_size, segments); | |
| return buffer; | |
| } | |
| header_function | |
| void segar_FreeLinearizedBuffer (void *buffer) | |
| { | |
| void *allocation = cast_ptr(Memory**, buffer) - 1; | |
| Memory *memory = *cast_ptr(Memory**, allocation); | |
| memRemit(memory, allocation); | |
| } | |
| /****************************************************************************************** | |
| * Serialization Helper Function | |
| */ | |
| typedef struct Srlz_Memory_Loader { | |
| Byte *memory; | |
| Size offset; | |
| } Srlz_Memory_Loader; | |
| header_function | |
| SRLZ_FUNC(srlzMemoryLoad) | |
| { | |
| Srlz_Memory_Loader *loader = cast_ptr(typeof(loader), userdata); | |
| Byte *buf = cast_ptr(typeof(buf), data); | |
| memcpy(buf, loader->memory + loader->offset, size); | |
| loader->offset += size; | |
| return true; | |
| } | |
| typedef struct Srlz_Memory_Dumper { | |
| segarOf(Byte) bytes; | |
| } Srlz_Memory_Dumper; | |
| header_function | |
| SRLZ_FUNC(srlzMemoryDumper) | |
| { | |
| Srlz_Memory_Dumper *dumper = cast_ptr(typeof(dumper), userdata); | |
| Byte *bytes = cast_ptr(Byte*, data); | |
| for (Size i = 0; i < size; i++) { | |
| segarAdd(&dumper->bytes, bytes[i]); | |
| } | |
| return true; | |
| } | |
| /****************************************************************************************** | |
| * Intrusive Circular Doubly Linked List | |
| * Inspired from github.com/torvalds/linux/blob/master/include/linux/list.h | |
| */ | |
| typedef struct List_Node { | |
| struct List_Node *next, *prev; | |
| } List_Node; | |
| /* To get the node (container struct) holding the linked list node */ | |
| #define listThisNode(nodeptr, type, member) containerof((nodeptr), type, member) | |
| #define listNextNode(nodeptr, type, member) containerof((nodeptr)->next, type, member) | |
| #define listPrevNode(nodeptr, type, member) containerof((nodeptr)->prev, type, member) | |
| /* | |
| * Initialize the list ike this: | |
| * ***************************** | |
| * typedef struct N { | |
| * List_Node l; | |
| * } N; | |
| * | |
| * N n; | |
| * n.l = listInit(n.l); | |
| */ | |
| #define listInit(name) compound_init(List_Node, {&(name), &(name)}) | |
| #define listReset(ptr) do{(ptr)->next = (ptr); (ptr)->prev = (ptr);}while(0) | |
| header_function | |
| void list_Add (List_Node *newnode, List_Node *prev, List_Node *next) | |
| { | |
| next->prev = newnode; | |
| newnode->next = next; | |
| newnode->prev = prev; | |
| prev->next = newnode; | |
| } | |
| header_function | |
| void listAddAfter (List_Node *newnode, List_Node *after_this) | |
| { | |
| list_Add(newnode, after_this, after_this->next); | |
| } | |
| header_function | |
| void listAddBefore (List_Node *newnode, List_Node *before_this) | |
| { | |
| list_Add(newnode, before_this->prev, before_this); | |
| } | |
| header_function | |
| void list_RemoveNodeBetween (List_Node * prev, List_Node * next) | |
| { | |
| next->prev = prev; | |
| prev->next = next; | |
| } | |
| header_function | |
| void listRemove (List_Node *entry) | |
| { | |
| list_RemoveNodeBetween(entry->prev, entry->next); | |
| entry->next = nullptr; | |
| entry->prev = nullptr; | |
| } | |
| header_function | |
| void listRemoveAndInit (List_Node *entry) | |
| { | |
| list_RemoveNodeBetween(entry->prev, entry->next); | |
| listReset(entry); | |
| } | |
| header_function | |
| void listReplace(List_Node *old, List_Node *newnode) | |
| { | |
| newnode->next = old->next; | |
| newnode->next->prev = newnode; | |
| newnode->prev = old->prev; | |
| newnode->prev->next = newnode; | |
| } | |
| header_function | |
| void listReplaceAndInit(List_Node *old, List_Node *newnode) | |
| { | |
| listReplace(old, newnode); | |
| listReset(old); | |
| } | |
| header_function | |
| void listSwap(List_Node *entry1, List_Node *entry2) | |
| { | |
| List_Node *pos = entry2->prev; | |
| listRemove(entry2); | |
| listReplace(entry1, entry2); | |
| if (pos == entry1) pos = entry2; | |
| listAddAfter(entry1, pos); | |
| } | |
| header_function | |
| void listMoveAfter (List_Node *list, List_Node *after_this) | |
| { | |
| list_RemoveNodeBetween(list->prev, list->next); | |
| listAddAfter(list, after_this); | |
| } | |
| header_function | |
| void listMoveBefore (List_Node *list, List_Node *before_this) | |
| { | |
| list_RemoveNodeBetween(list->prev, list->next); | |
| listAddBefore(list, before_this); | |
| } | |
| header_function | |
| Bool listIsEmpty (List_Node *node) | |
| { | |
| Bool result = (node->next == node); | |
| return result; | |
| } | |
| // Splice in a List `list`, between the Nodes `node` and `node->next` | |
| header_function | |
| void list_Splice (List_Node *list, List_Node *node) | |
| { | |
| List_Node *first = list->next; | |
| List_Node *last = list->prev; | |
| List_Node *at = node->next; | |
| first->prev = node; | |
| node->next = first; | |
| last->next = at; | |
| at->prev = last; | |
| } | |
| // Splice in a List `list`, between the Nodes `node` and `node->next` | |
| header_function | |
| void listSplice (List_Node *list, List_Node *node) | |
| { | |
| if (!listIsEmpty(list)) list_Splice(list, node); | |
| } | |
| header_function | |
| void listSpliceInit (List_Node *list, List_Node *node) | |
| { | |
| if (!listIsEmpty(list)) { | |
| list_Splice(list, node); | |
| listReset(list); | |
| } | |
| } | |
| // NOTE(naman): The list head should be a placeholder terminal node with no associated metadata. Not doing this makes | |
| // safe iteration very difficult and/or tedious. Trust me, I've wasted too much time trying various schemes out. | |
| # define LIST_FOR_EACH(_idx, _head) \ | |
| for (List_Node \ | |
| * _idx = (_head)->next, \ | |
| * _idx##_2_ = (_head)->next->next; \ | |
| _idx != (_head); \ | |
| _idx = _idx##_2_, _idx##_2_ = _idx##_2_ -> next) | |
| # define LIST_FOR_EACH_REVERSE(_idx, _head) \ | |
| for (List_Node \ | |
| * _idx = (_head)->prev, \ | |
| * _idx##_2_ = (_head)->prev->prev; \ | |
| _idx != (_head); \ | |
| _idx = _idx##_2_, _idx##_2_ = _idx##_2_ -> prev) | |
| /****************************************************************************************** | |
| * Vinyas (.viny) | |
| * | |
| * Text Serialization Format | |
| * **** ************* ****** | |
| * | |
| * Syntax | |
| * ====== | |
| * (inspired by SDLang/KDL) | |
| * | |
| * Viny := Entry* | |
| * Entry := Key Tuple | |
| * | Key '[' Size ']' Array | |
| * | Key Value | |
| * Tuple := '{' Viny '}' | |
| * Array := '{' (Tuple | Value)* '}' | |
| * Value := '"' Char* '"' | |
| * | Uint64 | |
| * | Sint64 | |
| * | Float64 | |
| * Key := Identifier | |
| * Size := Uint64 | |
| * | |
| * Dump API (Write) | |
| * ================ | |
| * (inspired by Tsoding's jim.h) | |
| * | |
| * Viny_Dump vdCreate (Txt_Fmt_Pool *tp, Memory_Push *mempush) | |
| * Txt vdFinish (Viny_Dump *v) | |
| * | |
| * Viny_Dump_Tuple* vdRootTuple (Viny_Dump *v) | |
| * | |
| * Viny_Dump_Tuple* vdTupleBegin (Viny_Dump_Tuple *vt, const Char *name) | |
| * Viny_Dump_Tuple* vdTupleBegin (Viny_Dump_Array *va) | |
| * void vdTupleEnd (Viny_Dump_Tuple **vtp) | |
| * | |
| * Viny_Dump_Array* vdArrayBegin (Viny_Dump_Tuple *vt, const Char *name) // If size is unknown, pass zero | |
| * void vdArrayEnd (Viny_Dump_Array **vtp) | |
| * | |
| * void vdSint (Viny_Dump_Tuple *vt, const Char *key, Sint64 val) | |
| * void vdSint (Viny_Dump_Array *va, Sint64 val) | |
| * | |
| * void vdUint (Viny_Dump_Tuple *vt, const Char *key, Uint64 val) | |
| * void vdUint (Viny_Dump_Array *va, Uint64 val) | |
| * | |
| * void vdFloat (Viny_Dump_Tuple *vt, const Char *key, Float64 val) | |
| * void vdFloat (Viny_Dump_Array *va, Float64 val) | |
| * | |
| * void vdString (Viny_Dump_Tuple *vt, const Char *key, const Char *val) | |
| * void vdString (Viny_Dump_Array *va, const Char *val) | |
| * | |
| * Load API (Read) | |
| * =============== | |
| * (inspired by Tsoding's jimp.h) | |
| * | |
| * Viny_Load vlMake (Txt str, TOKENIZER_ERROR_FUNC((*err_func)), void *err_data) | |
| * | |
| * Viny_Load vlIsLeft (Viny_Load *v) | |
| * | |
| * Viny_Load_Tuple* vlRootTuple (Viny_Load *v) | |
| * | |
| * Viny_Load_Tuple* vlTupleBegin (Viny_Load_Tuple *vt, const Char *name) | |
| * Viny_Load_Tuple* vlTupleBegin (Viny_Load_Array *va) | |
| * Bool vlTupleEnd (Viny_Load_Tuple *vt) | |
| * | |
| * Viny_Load_Array* vlArrayBegin (Viny_Load_Tuple *vt, const Char *name) | |
| * Bool vlArrayEnd (Viny_Load_Array *va) | |
| * | |
| * void vlFailed (Viny_Load *v) // Increment by one token (or by an entire open-close brace group) | |
| * | |
| * void vlSint (Viny_Load_Tuple *vt, const Char *key, Sint64 *val) | |
| * void vlSint (Viny_Load_Array *va, Sint64 *val) | |
| * | |
| * void vlUint (Viny_Load_Tuple *vt, const Char *key, Uint64 *val) | |
| * void vlUint (Viny_Load_Array *va, Uint64 *val) | |
| * | |
| * void vlFloat (Viny_Load_Tuple *vt, const Char *key, Float64 *val) | |
| * void vlFloat (Viny_Load_Array *va, Float64 *val) | |
| * | |
| * void vlString (Viny_Load_Tuple *vt, const Char *key, Txt_Span *val) | |
| * void vlString (Viny_Load_Array *va, Txt_Span *val) | |
| * | |
| * Usage | |
| * ----- | |
| * | |
| Viny_Load v = vlMake(txtNewL(&mempush, str, len), func, data); | |
| while (vlIsLeft(&v)) { | |
| Viny_Load_Tuple *vt_root = vlRootTuple(&v); | |
| Viny_Load_Tuple *vt_tuple = nullptr; | |
| Viny_Load_Array *va_array = nullptr; | |
| if ((vt_tuple = vlTupleBegin(vt_root, "tuple"))) { | |
| // pre-initialize stuff here... | |
| while (!vlTupleEnd(&vt_tuple)) { | |
| Txt_Span string = {0}; | |
| Uint64 integer = {0}; | |
| if (vlString(vt_tuple, "string", &string)) { | |
| // process string here... | |
| } else if (vlUint(vt_tuple, "number", integer)) { | |
| // process integer here ... | |
| } else vlFailed(&v); | |
| } | |
| // post-process after parsing the tuple here... | |
| } else if ((va_array = vlArrayBegin(vt_root, "array"))) { | |
| // same as above ... | |
| } else vlFailed(&v); | |
| // post-process after parsing the root here... | |
| } | |
| * | |
| */ | |
| # define VINY_TUPLE_BEGIN_SELECTOR_INTERNAL_(_1,_2,_NAME,...) _NAME | |
| typedef struct Viny_Dump { | |
| Txt_Fmt fmt; | |
| Memory_Push *mempush; | |
| Sint indent; | |
| Byte _pad[4]; | |
| } Viny_Dump; | |
| typedef struct Viny_Dump_Tuple Viny_Dump_Tuple; | |
| typedef struct Viny_Dump_Array Viny_Dump_Array; | |
| header_function | |
| Viny_Dump vdCreate (Txt_Fmt_Pool *tp, Memory_Push *mempush) | |
| { | |
| Viny_Dump viny = { | |
| .fmt = txtfmtCreate(tp), | |
| .mempush = mempush, | |
| }; | |
| return viny; | |
| } | |
| header_function | |
| Txt vdFinish (Viny_Dump *v) | |
| { | |
| Txt result = txtfmtFinish(&v->fmt, v->mempush); | |
| memzero(v); | |
| return result; | |
| } | |
| // NOTE(naman): The first argument taken by these functions is always Viny_Dump, | |
| // but cast into various pointer types to enforce type safety, and | |
| // Vinyasa syntax through that. | |
| header_function | |
| Viny_Dump_Tuple* vdRootTuple (Viny_Dump *v) | |
| { | |
| return cast_ptr(Viny_Dump_Tuple*, v); | |
| } | |
| header_function | |
| Viny_Dump_Tuple* vdTupleBegin_InTuple (Viny_Dump_Tuple *vt, const Char *name) | |
| { | |
| Viny_Dump *v = cast_ptr(typeof(v), vt); | |
| txtfmtAppendF(&v->fmt, "%*s%s {\n", 4 * v->indent, "", name); | |
| v->indent++; | |
| return vt; | |
| } | |
| header_function | |
| Viny_Dump_Tuple* vdTupleBegin_InArray (Viny_Dump_Array *va) | |
| { | |
| Viny_Dump *v = cast_ptr(typeof(v), va); | |
| txtfmtAppendF(&v->fmt, "%*s{\n", 4 * v->indent, ""); | |
| v->indent++; | |
| return cast_ptr(Viny_Dump_Tuple*, va); | |
| } | |
| #if defined(ENV_LANG_C) | |
| # define vdTupleBegin_1(_vv) \ | |
| _Generic((_vv) \ | |
| , Viny_Dump_Tuple* : vdTupleBegin_InTuple \ | |
| , Viny_Dump_Array* : vdTupleBegin_InArray \ | |
| )(_vv) | |
| # define vdTupleBegin_2(_vv, _name) \ | |
| _Generic((_vv) \ | |
| , Viny_Dump_Tuple* : vdTupleBegin_InTuple \ | |
| , Viny_Dump_Array* : vdTupleBegin_InArray \ | |
| )(_vv, _name) | |
| #define vdTupleBegin(...) VINY_TUPLE_BEGIN_SELECTOR_INTERNAL_(__VA_ARGS__, vdTupleBegin_2, vdTupleBegin_1)(__VA_ARGS__) | |
| #elif defined(ENV_LANG_CXX) | |
| header_function Viny_Dump_Tuple* vdTupleBegin (Viny_Dump_Tuple *vt, const Char *name) { return vdTupleBegin_InTuple(vt, name); } | |
| header_function Viny_Dump_Tuple* vdTupleBegin (Viny_Dump_Array *va) { return vdTupleBegin_InArray(va); } | |
| #endif | |
| header_function | |
| void vdTupleEnd (Viny_Dump_Tuple **vtp) | |
| { | |
| Viny_Dump *v = cast_ptr(typeof(v), *vtp); | |
| v->indent--; | |
| txtfmtAppendF(&v->fmt, "%*s}\n", 4 * v->indent, ""); | |
| *vtp = nullptr; | |
| return; | |
| } | |
| header_function | |
| Viny_Dump_Array* vdArrayBegin (Viny_Dump_Tuple *vt, const Char *name) | |
| { | |
| Viny_Dump *v = cast_ptr(typeof(v), vt); | |
| txtfmtAppendF(&v->fmt, "%*s%s[] {\n", 4 * v->indent, "", name); | |
| v->indent++; | |
| return cast_ptr(Viny_Dump_Array*, vt); | |
| } | |
| header_function | |
| void vdArrayEnd (Viny_Dump_Array **vap) | |
| { | |
| Viny_Dump *v = cast_ptr(typeof(v), *vap); | |
| v->indent--; | |
| txtfmtAppendF(&v->fmt, "%*s}\n", 4 * v->indent, ""); | |
| *vap = nullptr; | |
| return; | |
| } | |
| header_function | |
| void vdSint_Tuple (Viny_Dump_Tuple *vt, const Char *key, Sint64 val) | |
| { | |
| Viny_Dump *v = cast_ptr(typeof(v), vt); | |
| debugAssert(key && strlen(key)); | |
| txtfmtAppendF(&v->fmt, "%*s%s %lld\n", 4 * v->indent, "", key, val); | |
| } | |
| header_function | |
| void vdSint_Array (Viny_Dump_Array *va, Sint64 val) | |
| { | |
| Viny_Dump *v = cast_ptr(typeof(v), va); | |
| txtfmtAppendF(&v->fmt, "%*s%lld\n", 4 * v->indent, "", val); | |
| } | |
| #if defined(ENV_LANG_C) | |
| # define vdSint(_vv, ...) \ | |
| _Generic((_vv) \ | |
| , Viny_Dump_Tuple *: vdSint_Tuple \ | |
| , Viny_Dump_Array *: vdSint_Array \ | |
| )(_vv, __VA_ARGS__) | |
| #elif defined(ENV_LANG_CXX) | |
| header_function void vdSint (Viny_Dump_Tuple *vt, const Char *key, Sint64 val) { return vdSint_Tuple(vt, key, val); } | |
| header_function void vdSint (Viny_Dump_Array *va, Sint64 val) { return vdSint_Array(va, val); } | |
| #endif | |
| header_function | |
| void vdUint_Tuple (Viny_Dump_Tuple *vt, const Char *key, Uint64 val) | |
| { | |
| Viny_Dump *v = cast_ptr(typeof(v), vt); | |
| debugAssert(key && strlen(key)); | |
| txtfmtAppendF(&v->fmt, "%*s%s %llu\n", 4 * v->indent, "", key, val); | |
| } | |
| header_function | |
| void vdUint_Array (Viny_Dump_Array *va, Uint64 val) | |
| { | |
| Viny_Dump *v = cast_ptr(typeof(v), va); | |
| txtfmtAppendF(&v->fmt, "%*s%llu\n", 4 * v->indent, "", val); | |
| } | |
| #if defined(ENV_LANG_C) | |
| # define vdUint(_vv, ...) \ | |
| _Generic((_vv) \ | |
| , Viny_Dump_Tuple *: vdUint_Tuple \ | |
| , Viny_Dump_Array *: vdUint_Array \ | |
| )(_vv, __VA_ARGS__) | |
| #elif defined(ENV_LANG_CXX) | |
| header_function void vdUint (Viny_Dump_Tuple *vt, const Char *key, Uint64 val) { return vdUint_Tuple(vt, key, val); } | |
| header_function void vdUint (Viny_Dump_Array *va, Uint64 val) { return vdUint_Array(va, val); } | |
| #endif | |
| header_function | |
| void vdFloat_Tuple (Viny_Dump_Tuple *vt, const Char *key, Float64 val) | |
| { | |
| Viny_Dump *v = cast_ptr(typeof(v), vt); | |
| debugAssert(key && strlen(key)); | |
| txtfmtAppendF(&v->fmt, "%*s%s %f <%a> /* Remove the hex if editing decimal */\n", 4 * v->indent, "", key, val, val); | |
| } | |
| header_function | |
| void vdFloat_Array (Viny_Dump_Array *va, Float64 val) | |
| { | |
| Viny_Dump *v = cast_ptr(typeof(v), va); | |
| txtfmtAppendF(&v->fmt, "%*s%f <%a> /* Remove the hex if editing decimal */\n", 4 * v->indent, "", val, val); | |
| } | |
| #if defined(ENV_LANG_C) | |
| # define vdFloat(_vv, ...) \ | |
| _Generic((_vv) \ | |
| , Viny_Dump_Tuple *: vdFloat_Tuple \ | |
| , Viny_Dump_Array *: vdFloat_Array \ | |
| )(_vv, __VA_ARGS__) | |
| #elif defined(ENV_LANG_CXX) | |
| header_function void vdFloat (Viny_Dump_Tuple *vt, const Char *key, Float64 val) { return vdFloat_Tuple(vt, key, val); } | |
| header_function void vdFloat (Viny_Dump_Array *va, Float64 val) { return vdFloat_Array(va, val); } | |
| #endif | |
| header_function void vd_StringHelper (Viny_Dump *v, const Char *val); | |
| header_function | |
| void vdString_Tuple (Viny_Dump_Tuple *vt, const Char *key, const Char *val) | |
| { | |
| Viny_Dump *v = cast_ptr(typeof(v), vt); | |
| debugAssert(key && strlen(key)); | |
| txtfmtAppendF(&v->fmt, "%*s%s \"", 4 * v->indent, "", key); | |
| if (val) vd_StringHelper(v, val); | |
| txtfmtAppendF(&v->fmt, "\"\n"); | |
| } | |
| header_function | |
| void vdString_Array (Viny_Dump_Array *va, const Char *val) | |
| { | |
| Viny_Dump *v = cast_ptr(typeof(v), va); | |
| txtfmtAppendF(&v->fmt, "%*s\"", 4 * v->indent, ""); | |
| if (val) vd_StringHelper(v, val); | |
| txtfmtAppendF(&v->fmt, "\"\n"); | |
| } | |
| #if defined(ENV_LANG_C) | |
| # define vdString(_vv, ...) \ | |
| _Generic((_vv) \ | |
| , Viny_Dump_Tuple *: vdString_Tuple \ | |
| , Viny_Dump_Array *: vdString_Array \ | |
| )(_vv, __VA_ARGS__) | |
| #elif defined(ENV_LANG_CXX) | |
| header_function void vdString (Viny_Dump_Tuple *vt, const Char *key, const Char *val) { return vdString_Tuple(vt, key, val); } | |
| header_function void vdString (Viny_Dump_Array *va, const Char *val) { return vdString_Array(va, val); } | |
| #endif | |
| header_function | |
| void vd_StringHelper (Viny_Dump *v, const Char *val) | |
| { | |
| Sint begin = 0, end = 0; | |
| while (val[end]) { | |
| switch (val[end]) { // Skip any special characters (since we don't want to be handling them while parsing) | |
| case '\'': case '\"': case '\?': case '\\': case '\a': | |
| case '\b': case '\f': case '\n': case '\r': case '\t': | |
| case '\v': { | |
| if (end > begin) { | |
| txtfmtAppendF(&v->fmt, "%.*s", end - begin, val + begin); | |
| } | |
| end++; | |
| begin = end; | |
| } break; | |
| default: end++; break; | |
| } | |
| } | |
| if (end > begin) { | |
| txtfmtAppendF(&v->fmt, "%.*s", end - begin, val + begin); | |
| } | |
| } | |
| typedef struct Viny_Load { | |
| Txt txt; | |
| Tokenizer toker; | |
| } Viny_Load; | |
| typedef struct Viny_Load_Tuple Viny_Load_Tuple; | |
| typedef struct Viny_Load_Array Viny_Load_Array; | |
| header_function | |
| Viny_Load vlMake (Txt str, TOKENIZER_ERROR_FUNC((*err_func)), void *err_data) | |
| { | |
| Viny_Load viny = { | |
| .txt = str, | |
| .toker = tokenizerMake(str, err_func, err_data), | |
| }; | |
| return viny; | |
| } | |
| header_function | |
| Bool vlIsLeft (Viny_Load *v) | |
| { | |
| Bool result = tokenizerPeekToken(&v->toker).kind != Token_Kind_EOF; | |
| return result; | |
| } | |
| header_function | |
| Viny_Load_Tuple* vlRootTuple (Viny_Load *v) | |
| { | |
| return cast_ptr(Viny_Load_Tuple*, v); | |
| } | |
| // NOTE(naman): The first argument taken by these functions is always Viny_Load, | |
| // but cast into various pointer types to enforce type safety, and | |
| // Vinyasa syntax through that. | |
| // NOTE(naman): All of these functions will restore the tokenizer state if | |
| // they are returning false. In case of failure to parse, we | |
| // move to next token only in the vl's tokenizerGetToken(). | |
| header_function | |
| Viny_Load_Tuple* vlTupleBegin_InTuple (Viny_Load_Tuple *vt, const Char *key) | |
| { | |
| Viny_Load *v = cast_ptr(typeof(v), vt); | |
| Tokenizer toker_copy = v->toker; | |
| Token keytok = tokenizerGetToken(&v->toker); | |
| if (keytok.kind != Token_Kind_IDENTIFIER) goto not_matched; | |
| if (!txtspanIsEqualStr(keytok.span, key)) goto not_matched; | |
| if (tokenizerGetToken(&v->toker).kind != '{') goto not_matched; | |
| return vt; | |
| not_matched: | |
| v->toker = toker_copy; | |
| return nullptr; | |
| } | |
| header_function | |
| Viny_Load_Tuple* vlTupleBegin_InArray (Viny_Load_Array *va) | |
| { | |
| Viny_Load *v = cast_ptr(typeof(v), va); | |
| Tokenizer toker_copy = v->toker; | |
| if (tokenizerGetToken(&v->toker).kind != '{') goto not_matched; | |
| return cast_ptr(Viny_Load_Tuple*, va); | |
| not_matched: | |
| v->toker = toker_copy; | |
| return nullptr; | |
| } | |
| #if defined(ENV_LANG_C) | |
| # define vlTupleBegin_1(_vv) \ | |
| _Generic((_vv) \ | |
| , Viny_Dump_Tuple* : vlTupleBegin_InTuple \ | |
| , Viny_Dump_Array* : vlTupleBegin_InArray \ | |
| )(_vv) | |
| # define vlTupleBegin_2(_vv, _key) \ | |
| _Generic((_vv) \ | |
| , Viny_Dump_Tuple* : vlTupleBegin_InTuple \ | |
| , Viny_Dump_Array* : vlTupleBegin_InArray \ | |
| )(_vv, _key) | |
| #define vlTupleBegin(...) VINY_TUPLE_BEGIN_SELECTOR_INTERNAL_(__VA_ARGS__, vlTupleBegin_2, vlTupleBegin_1)(__VA_ARGS__) | |
| #elif defined(ENV_LANG_CXX) | |
| header_function Viny_Load_Tuple* vlTupleBegin (Viny_Load_Tuple *vt, const Char *key) { return vlTupleBegin_InTuple(vt, key); } | |
| header_function Viny_Load_Tuple* vlTupleBegin (Viny_Load_Array *va) { return vlTupleBegin_InArray(va); } | |
| #endif | |
| header_function | |
| Bool vlTupleEnd (Viny_Load_Tuple **vtp) | |
| { | |
| Viny_Load *v = cast_ptr(typeof(v), *vtp); | |
| if ((tokenizerPeekToken(&v->toker).kind == '}') || (tokenizerPeekToken(&v->toker).kind == Token_Kind_EOF)) { | |
| tokenizerGetToken(&v->toker); | |
| *vtp = nullptr; | |
| return true; | |
| } | |
| return false; | |
| } | |
| header_function | |
| Viny_Load_Array* vlArrayBegin (Viny_Load_Tuple *vt, const Char *key) | |
| { | |
| Viny_Load *v = cast_ptr(typeof(v), vt); | |
| Tokenizer toker_copy = v->toker; | |
| Token keytok = tokenizerGetToken(&v->toker); | |
| if (keytok.kind != Token_Kind_IDENTIFIER) goto not_matched; | |
| if (!txtspanIsEqualStr(keytok.span, key)) goto not_matched; | |
| if (tokenizerGetToken(&v->toker).kind != '[') goto not_matched; | |
| if (tokenizerGetToken(&v->toker).kind != ']') goto not_matched; | |
| if (tokenizerGetToken(&v->toker).kind != '{') goto not_matched; | |
| return cast_ptr(Viny_Load_Array*, vt); | |
| not_matched: | |
| v->toker = toker_copy; | |
| return nullptr; | |
| } | |
| header_function | |
| Bool vlArrayEnd (Viny_Load_Array **vap) | |
| { | |
| Viny_Load *v = cast_ptr(typeof(v), *vap); | |
| if ((tokenizerPeekToken(&v->toker).kind == '}') || (tokenizerPeekToken(&v->toker).kind == Token_Kind_EOF)) { | |
| tokenizerGetToken(&v->toker); | |
| *vap = nullptr; | |
| return true; | |
| } | |
| return false; | |
| } | |
| header_function | |
| void vlFailed (Viny_Load *v) | |
| { | |
| // Skip one token, and if that token was an open brace, skip until the matching close brace | |
| if (tokenizerGetToken(&v->toker).kind == '{') { | |
| Uint open_brace_count = 1; | |
| while (open_brace_count && vlIsLeft(v)) { | |
| Token tok = tokenizerGetToken(&v->toker); | |
| if (tok.kind == '{') { | |
| open_brace_count++; | |
| } else if (tok.kind == '}') { | |
| open_brace_count--; | |
| } | |
| } | |
| } | |
| } | |
| header_function | |
| Bool vlSint_Helper (Viny_Load *v, Sint64 *valptr) | |
| { | |
| Token valtok = tokenizerGetToken(&v->toker); | |
| Bool negative = false; | |
| if (valtok.kind == '-') { | |
| negative = true; | |
| valtok = tokenizerGetToken(&v->toker); | |
| } | |
| if (valtok.kind != Token_Kind_CONSTANT_INTEGER) return false; | |
| Uint64 value = maximum(valtok.value.integer, cast_val(Uint64, INT64_MAX)); | |
| *valptr = cast_val(Sint64, value); | |
| if (negative) { | |
| *valptr = -(*valptr); | |
| } | |
| return true; | |
| } | |
| header_function | |
| Bool vlSint_Tuple (Viny_Load_Tuple *vt, const Char *key, Sint64 *valptr) | |
| { | |
| Viny_Load *v = cast_ptr(typeof(v), vt); | |
| Tokenizer toker_copy = v->toker; | |
| Token keytok = tokenizerGetToken(&v->toker); | |
| if (keytok.kind != Token_Kind_IDENTIFIER) goto not_matched; | |
| if (!txtspanIsEqualStr(keytok.span, key)) goto not_matched; | |
| if (!vlSint_Helper(v, valptr)) goto not_matched; | |
| return true; | |
| not_matched: | |
| v->toker = toker_copy; | |
| return false; | |
| } | |
| header_function | |
| Bool vlSint_Array (Viny_Load_Array *vt, Sint64 *valptr) | |
| { | |
| Viny_Load *v = cast_ptr(typeof(v), vt); | |
| Tokenizer toker_copy = v->toker; | |
| if (!vlSint_Helper(v, valptr)) goto not_matched; | |
| return true; | |
| not_matched: | |
| v->toker = toker_copy; | |
| return false; | |
| } | |
| #if defined(ENV_LANG_C) | |
| # define vlSint(_vv, ...) \ | |
| _Generic((_vv) \ | |
| , Viny_Load_Tuple *: vlSint_Tuple \ | |
| , Viny_Load_Array *: vlSint_Array \ | |
| )(_vv, __VA_ARGS__) | |
| #elif defined(ENV_LANG_CXX) | |
| header_function Bool vlSint (Viny_Load_Tuple *vt, const Char *key, Sint64 *val) { return vlSint_Tuple(vt, key, val); } | |
| header_function Bool vlSint (Viny_Load_Array *va, Sint64 *val) { return vlSint_Array(va, val); } | |
| #endif | |
| header_function | |
| Bool vlUint_Tuple (Viny_Load_Tuple *vt, const Char *key, Uint64 *valptr) | |
| { | |
| Viny_Load *v = cast_ptr(typeof(v), vt); | |
| Tokenizer toker_copy = v->toker; | |
| Token keytok = tokenizerGetToken(&v->toker); | |
| if (keytok.kind != Token_Kind_IDENTIFIER) goto not_matched; | |
| if (!txtspanIsEqualStr(keytok.span, key)) goto not_matched; | |
| Token valtok = tokenizerGetToken(&v->toker); | |
| if (valtok.kind != Token_Kind_CONSTANT_INTEGER) goto not_matched; | |
| *valptr = valtok.value.integer; | |
| return true; | |
| not_matched: | |
| v->toker = toker_copy; | |
| return false; | |
| } | |
| header_function | |
| Bool vlUint_Array (Viny_Load_Array *va, Uint64 *valptr) | |
| { | |
| Viny_Load *v = cast_ptr(typeof(v), va); | |
| Tokenizer toker_copy = v->toker; | |
| Token valtok = tokenizerGetToken(&v->toker); | |
| if (valtok.kind != Token_Kind_CONSTANT_INTEGER) goto not_matched; | |
| *valptr = valtok.value.integer; | |
| return true; | |
| not_matched: | |
| v->toker = toker_copy; | |
| return false; | |
| } | |
| #if defined(ENV_LANG_C) | |
| # define vlUint(_vv, ...) \ | |
| _Generic((_vv) \ | |
| , Viny_Load_Tuple *: vlUint_Tuple \ | |
| , Viny_Load_Array *: vlUint_Array \ | |
| )(_vv, __VA_ARGS__) | |
| #elif defined(ENV_LANG_CXX) | |
| header_function Bool vlUint (Viny_Load_Tuple *vt, const Char *key, Uint64 *val) { return vlUint_Tuple(vt, key, val); } | |
| header_function Bool vlUint (Viny_Load_Array *va, Uint64 *val) { return vlUint_Array(va, val); } | |
| #endif | |
| header_function | |
| Bool vlFloat_Tuple (Viny_Load_Tuple *vt, const Char *key, Float64 *valptr) | |
| { | |
| Viny_Load *v = cast_ptr(typeof(v), vt); | |
| Tokenizer toker_copy = v->toker; | |
| Token keytok = tokenizerGetToken(&v->toker); | |
| if (keytok.kind != Token_Kind_IDENTIFIER) goto not_matched; | |
| if (!txtspanIsEqualStr(keytok.span, key)) goto not_matched; | |
| Token valdectok = tokenizerGetToken(&v->toker); | |
| if (valdectok.kind != Token_Kind_CONSTANT_FLOATING) goto not_matched; | |
| *valptr = valdectok.value.floating; | |
| if (tokenizerGetToken(&v->toker).kind == '<') { | |
| Token valhextok = tokenizerGetToken(&v->toker); | |
| if (valhextok.kind != Token_Kind_CONSTANT_FLOATING) goto not_matched; | |
| if (tokenizerGetToken(&v->toker).kind != '>') goto not_matched; | |
| *valptr = valhextok.value.floating; | |
| } | |
| return true; | |
| not_matched: | |
| v->toker = toker_copy; | |
| return false; | |
| } | |
| header_function | |
| Bool vlFloat_Array (Viny_Load_Array *va, Float64 *valptr) | |
| { | |
| Viny_Load *v = cast_ptr(typeof(v), va); | |
| Tokenizer toker_copy = v->toker; | |
| Token valdectok = tokenizerGetToken(&v->toker); | |
| if (valdectok.kind != Token_Kind_CONSTANT_FLOATING) goto not_matched; | |
| *valptr = valdectok.value.floating; | |
| if (tokenizerGetToken(&v->toker).kind == '<') { | |
| Token valhextok = tokenizerGetToken(&v->toker); | |
| if (valhextok.kind != Token_Kind_CONSTANT_FLOATING) goto not_matched; | |
| if (tokenizerGetToken(&v->toker).kind != '>') goto not_matched; | |
| *valptr = valhextok.value.floating; | |
| } | |
| return true; | |
| not_matched: | |
| v->toker = toker_copy; | |
| return false; | |
| } | |
| #if defined(ENV_LANG_C) | |
| # define vlFloat(_vv, ...) \ | |
| _Generic((_vv) \ | |
| , Viny_Load_Tuple *: vlFloat_Tuple \ | |
| , Viny_Load_Array *: vlFloat_Array \ | |
| )(_vv, __VA_ARGS__) | |
| #elif defined(ENV_LANG_CXX) | |
| header_function Bool vlFloat (Viny_Load_Tuple *vt, const Char *key, Float64 *val) { return vlFloat_Tuple(vt, key, val); } | |
| header_function Bool vlFloat (Viny_Load_Array *va, Float64 *val) { return vlFloat_Array(va, val); } | |
| #endif | |
| header_function | |
| Bool vlString_Tuple (Viny_Load_Tuple *vt, const Char *key, Txt_Span *valptr) | |
| { | |
| Viny_Load *v = cast_ptr(typeof(v), vt); | |
| Tokenizer toker_copy = v->toker; | |
| Token keytok = tokenizerGetToken(&v->toker); | |
| if (keytok.kind != Token_Kind_IDENTIFIER) goto not_matched; | |
| if (!txtspanIsEqualStr(keytok.span, key)) goto not_matched; | |
| Token valtok = tokenizerGetToken(&v->toker); | |
| if (valtok.kind != Token_Kind_CONSTANT_STRING) goto not_matched; | |
| *valptr = valtok.span; | |
| valptr->begin++; // Skip the opening double-quote | |
| valptr->end--; // Ignore the closing double-quote | |
| return true; | |
| not_matched: | |
| v->toker = toker_copy; | |
| return false; | |
| } | |
| header_function | |
| Bool vlString_Array (Viny_Load_Array *va, Txt_Span *valptr) | |
| { | |
| Viny_Load *v = cast_ptr(typeof(v), va); | |
| Tokenizer toker_copy = v->toker; | |
| Token valtok = tokenizerGetToken(&v->toker); | |
| if (valtok.kind != Token_Kind_CONSTANT_STRING) goto not_matched; | |
| *valptr = valtok.span; | |
| valptr->begin++; // Skip the opening double-quote | |
| valptr->end--; // Ignore the closing double-quote | |
| return true; | |
| not_matched: | |
| v->toker = toker_copy; | |
| return false; | |
| } | |
| #if defined(ENV_LANG_C) | |
| # define vlString(_vv, ...) \ | |
| _Generic((_vv) \ | |
| , Viny_Load_Tuple *: vlString_Tuple \ | |
| , Viny_Load_Array *: vlString_Array \ | |
| )(_vv, __VA_ARGS__) | |
| #elif defined(ENV_LANG_CXX) | |
| header_function Bool vlString (Viny_Load_Tuple *vt, const Char *key, Txt_Span *val) { return vlString_Tuple(vt, key, val); } | |
| header_function Bool vlString (Viny_Load_Array *va, Txt_Span *val) { return vlString_Array(va, val); } | |
| #endif | |
| /****************************************************************************************** | |
| * Path | |
| */ | |
| typedef struct Path { | |
| Txt _data; // The type her eshould be OSString of some kind (maybe WTF-8)? | |
| } Path; | |
| typedef segarOf(const Path*) Paths; | |
| # if defined(ENV_OS_WINDOWS) | |
| # define PATH_SEPARATOR '\\' | |
| # elif defined(ENV_OS_LINUX) | |
| # define PATH_SEPARATOR '/' | |
| # endif | |
| header_function | |
| const Char* pathGetStr (const Path *path) | |
| { | |
| debugAssert(path != nullptr); | |
| if (path == nullptr) return nullptr; | |
| const Char *result = txtStr(path->_data); | |
| return result; | |
| } | |
| header_function | |
| Size pathGetStrlen (const Path *path) | |
| { | |
| debugAssert(path != nullptr); | |
| if (path == nullptr) return 0; | |
| Size result = txtLen(path->_data); | |
| return result; | |
| } | |
| // NOTE(naman): When creating a new path using pathCreate or pathJoin, if the last character is | |
| // a separator, then the returned path also ends with a valid separator. | |
| header_function | |
| const Path* pathCreateL (Memory_Push *mempush, const Char *dirty_path, Size dirty_len) | |
| { | |
| Txt clean_path = txt_Allocate(mempush, dirty_len + 1); | |
| Size clean_idx = 0; | |
| for (Size dirty_idx = 0; dirty_idx < dirty_len; dirty_idx++, clean_idx++) { | |
| Bool separator_found = false; | |
| while ((dirty_path[dirty_idx] == '\\') || (dirty_path[dirty_idx] == '/')) { | |
| separator_found = true; | |
| dirty_idx++; | |
| } | |
| if (separator_found) { | |
| clean_path.str[clean_idx] = PATH_SEPARATOR; | |
| dirty_idx--; // dirty_idx is pointing to the first character after the separators | |
| } else { | |
| clean_path.str[clean_idx] = dirty_path[dirty_idx]; | |
| } | |
| } | |
| Txt_Header_ *hdr = containerof(clean_path.str, Txt_Header_, str); | |
| hdr->len = cast_val(Uint32, clean_idx); | |
| Path *result = cast_ptr(typeof(result), memPushAllot(mempush, sizeof(*result))); | |
| result->_data = clean_path; | |
| return result; | |
| } | |
| header_function | |
| const Path* pathCreate (Memory_Push *mempush, const Char *dirty_path) | |
| { | |
| Size dirty_len = strlen(dirty_path); | |
| const Path *result = pathCreateL(mempush, dirty_path, dirty_len); | |
| return result; | |
| } | |
| #define pathJoin(_tfp, _mp, ...) path_Join((_tfp), (_mp), __VA_ARGS__, nullptr) | |
| header_function | |
| const Path* path_Join (Txt_Fmt_Pool *tfp, Memory_Push *mempush, const Path *root, ...) | |
| { | |
| Txt_Fmt fmt = txtfmtCreate(tfp); | |
| txtfmtAppendC(&fmt, pathGetStr(root)); | |
| { | |
| va_list args; | |
| va_start(args, root); | |
| const Char *p = va_arg(args, const Char *); | |
| while (p) { | |
| txtfmtAppendF(&fmt, "%c%s", PATH_SEPARATOR, p); | |
| p = va_arg(args, const Char *); | |
| } | |
| va_end(args); | |
| } | |
| Char *path = txtStr(txtfmtFinish(&fmt, mempush)); | |
| const Path* result = pathCreate(mempush, path); | |
| return result; | |
| } | |
| header_function | |
| Txt pathGetName (Memory_Push *mempush, const Path *path) | |
| { | |
| const Char *last_separator = nullptr; { | |
| pragma_msvc("warning ( push )"); | |
| pragma_msvc("warning ( disable: 6293 )"); // Ill-defined for loop | |
| // The loop starts with "-2" since the last character being separator should be ignored anyways, and | |
| // if it is not a separator, then no harm no foul. | |
| for (Size i = pathGetStrlen(path) - 2; i < pathGetStrlen(path); i--) { | |
| if (pathGetStr(path)[i] == PATH_SEPARATOR) { | |
| last_separator = pathGetStr(path) + i + 1; | |
| break; | |
| } | |
| } | |
| pragma_msvc("warning ( pop )"); | |
| } | |
| if (last_separator == nullptr) { // No separator found, the path may just be a single filename with no directory | |
| last_separator = pathGetStr(path); | |
| } | |
| Txt name = txtNew(mempush, last_separator); | |
| return name; | |
| } | |
| header_function | |
| const Path* pathGetParent (Memory_Push *mempush, const Path *path) | |
| { | |
| // Get pointer to the last character | |
| const Char *end_ptr = pathGetStr(path) + pathGetStrlen(path) - 1; | |
| // Move one step back if the last character is a separator | |
| if (*end_ptr == PATH_SEPARATOR) { | |
| if ((pathGetStrlen(path) > 1) && | |
| (end_ptr[0] != ':') && | |
| (end_ptr[-1] != ':')) { // To avoid the edge case of root directory on Windows or Unix | |
| end_ptr--; | |
| } else { | |
| return nullptr; | |
| } | |
| } | |
| for (const Char *begin_ptr = pathGetStr(path); (begin_ptr != end_ptr) && (*end_ptr != PATH_SEPARATOR); end_ptr--); | |
| if (end_ptr == pathGetStr(path)) { | |
| return nullptr; | |
| } else { | |
| debugAssert(*end_ptr == PATH_SEPARATOR); | |
| // NOTE(naman): pathCreate calls path_EnsureParent. That function then calls this function. | |
| // So, if we were to try to create a path using pathCreate below, we would end | |
| // up in a circular loop trying to get the parent of the parent of the parent | |
| // and so on until we reached root and this function returned NULL. | |
| // To avoid that, we manually create the Path* variable instead of going | |
| // through the pathCreate route. It should be okay, since the argument is | |
| // also a Path* variable, so it must have already gotten cleaned up having | |
| // gone through the pathCreate gauntlet. | |
| const Path *result = pathCreateL(mempush, pathGetStr(path), cast_val(Size, end_ptr - pathGetStr(path))); | |
| return result; | |
| } | |
| } | |
| header_function | |
| Txt pathCanonicalize (Memory_Push *mempush, const Path *path) | |
| { | |
| Txt copy = txtNew(mempush, pathGetStr(path)); | |
| for (Size i = 0; i < txtLen(copy); i++) { | |
| if ((txtStr(copy)[i] == '/') || (txtStr(copy)[i] == '\\')) { | |
| txtStr(copy)[i] = '/'; | |
| } | |
| } | |
| return copy; | |
| } | |
| pragma_clang("clang diagnostic pop"); | |
| pragma_msvc("warning ( pop )"); | |
| #define STD_H_INCLUDE_GUARD | |
| #endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Creator: Naman Dixit | |
| * Notice: © Copyright 2025 Naman Dixit | |
| */ | |
| #pragma once | |
| #include "std.h" | |
| /* Memory ***********************************************************************/ | |
| MEMORY_ALLOT_FUNC(osMemoryAllot); | |
| MEMORY_REMIT_FUNC(osMemoryRemit); | |
| /* Console Output ***********************************************************************/ | |
| void osLogOut (const Char *fmt, ...); | |
| void osLogErr (const Char *fmt, ...); | |
| /* Assertion ***********************************************************************/ | |
| Bool os_Assert(const Char *filename, Sint line, const Char *funcname, Bool condition, const Char *condition_str, const Char *fmt, ...); | |
| # define osAssert(_cond, _fmt, ...) os_Assert(__FILE__, __LINE__, __func__, !!(_cond), #_cond, (_fmt) __VA_OPT__(,) __VA_ARGS__) | |
| #if defined(BUILD_INTERNAL) | |
| # define osAssertInternal(_cond, _fmt, ...) os_Assert(__FILE__, __LINE__, __func__, !!(_cond), #_cond, (_fmt) __VA_OPT__(,) __VA_ARGS__) | |
| #else | |
| # define osAssertInternal(_cond, _fmt, ...) (void)(_msg), (void)(_cond) | |
| #endif | |
| #if defined(BUILD_SLOW) | |
| # define osAssertSlow(_cond, _fmt, ...) os_Assert(__FILE__, __LINE__, __func__, !!(_cond), #_cond, (_fmt) __VA_OPT__(,) __VA_ARGS__) | |
| #else | |
| # define osAssertSlow(_cond, _fmt, ...) (void)(_msg), (void)(_cond) | |
| #endif | |
| /* File System ***********************************************************************/ | |
| const Path* osGetProgramDirectory (Memory_Push *mempush); | |
| const Path* osGetWorkingDirectory (Memory_Push *mempush); | |
| const Path* osGetSettingDirectory (Memory_Push *mempush, const Char *organization, const Char *application); | |
| Bool osCreateDirectory (const Path *path); | |
| Bool osPathMove (Txt_Fmt_Pool *tfp, Memory_Push *mempush, const Path *oldpath, const Path *newpath); | |
| Bool osPathCopy (Txt_Fmt_Pool *tfp, Memory_Push *mempush, const Path *source, const Path *destination); | |
| Bool osPathDelete (const Path *path); | |
| Bool osDoesPathExists (const Path *path); | |
| Uint64 osPathGetChangedTime (const Path *path); | |
| typedef struct OS_Path_Components { | |
| const Path *path; | |
| Txt name; | |
| const Path *parent; | |
| const Path *root; | |
| Txt path_relative_to_root; | |
| } OS_Path_Components; | |
| typedef enum OS_Path_Recursor_Result { | |
| OS_Path_Recursor_Result_CONTINUE, | |
| OS_Path_Recursor_Result_SUCCESS, | |
| OS_Path_Recursor_Result_FAILURE, | |
| } OS_Path_Recursor_Result; | |
| #define OS_PATH_RECURSOR_FUNC(_name) \ | |
| OS_Path_Recursor_Result \ | |
| _name (Txt_Fmt_Pool *tfp, Memory_Push *mempush, \ | |
| const OS_Path_Components *iter, \ | |
| Bool is_folder, /* true=folder, false=file*/ \ | |
| void *userdata) | |
| Bool osPathRecursorLaunch (Txt_Fmt_Pool *tfp, Memory_Push *mempush, | |
| const Path *folder, OS_PATH_RECURSOR_FUNC((*iterator)), void *userdata); | |
| Bool osPathRecursorRecurse (Txt_Fmt_Pool *tfp, Memory_Push *mempush, | |
| const OS_Path_Components *iter, OS_PATH_RECURSOR_FUNC((*iterator)), void *userdata); | |
| typedef segarOf(OS_Path_Components) OS_Path_Collection; | |
| Bool osCollectFilePaths (Txt_Fmt_Pool *tfp, Memory_Push *mempush, | |
| const Path *root, const Txt prefix, const Txt suffix, Bool recursive, | |
| OS_Path_Collection *output_file_list); | |
| void* osFileLoad (Memory *memory, const Path *path, Size *size); | |
| Bool osFileDump (void *data, Size size, const Path *path); | |
| /* Process Control ***********************************************************************/ | |
| typedef struct OS_Command { | |
| Memory_Push *mempush; | |
| segarOf(const Char *) argv; | |
| } OS_Command; | |
| OS_Command osCommandInit (Memory_Push *mempush); | |
| void osCommandAppend (OS_Command *cmd, const Char *arg); | |
| Sint osCommandExecute (OS_Command *cmd, Memory_Push *mempush, Txt *output); | |
| /* Environment *************************************************************************/ | |
| const Char* osEnvironmentVariableGet(const Char *key); | |
| const Char* osEnvironmentVariableSet(const Char *key, const Char *val, Bool overwrite); | |
| /* Time *************************************************************************/ | |
| typedef struct OS_Time { | |
| Uint16 year; | |
| Uint8 month; // [1, 12] | |
| Uint8 day; // [1, 31] | |
| Uint8 hour; // [0, 23] | |
| Uint8 minute; // [0, 59] | |
| Uint8 second; // [0, 60] | |
| Uint8 day_of_week; // [0, 6] | |
| Uint32 nanosecond; // [0, 999999999] | |
| Sint32 utc_offset; // Seconds east of UTC | |
| } OS_Time; | |
| Uint64 osGetNanosecondsFromEpoch (void); | |
| OS_Time osGetTime (Bool local_time); | |
| Uint64 osTickCounter (void); | |
| Uint64 osTickGetRate (void); | |
| /////////////////////////////////////////////////////////////////////////////////////////// | |
| /////////////////////////////////////////////////////////////////////////////////////////// | |
| /////////////////////////////////////////////////////////////////////////////////////////// | |
| //////////////////////////////////IMPLEMENTATION/////////////////////////////////////////// | |
| /////////////////////////////////////////////////////////////////////////////////////////// | |
| /////////////////////////////////////////////////////////////////////////////////////////// | |
| /////////////////////////////////////////////////////////////////////////////////////////// | |
| #if defined(SDL_h_) | |
| /* Memory ***********************************************************************/ | |
| MEMORY_ALLOT_FUNC(osMemoryAllot) | |
| { | |
| unused_variable(userdata); | |
| void *data = SDL_calloc(1, amount); | |
| return data; | |
| } | |
| MEMORY_REMIT_FUNC(osMemoryRemit) | |
| { | |
| unused_variable(userdata); | |
| SDL_free(ptr); | |
| } | |
| /* Console Output ***********************************************************************/ | |
| with_clang_gcc(__attribute__ (( format( __printf__, 1, 2 )))) | |
| void osLogOut (const Char *fmt, ...) | |
| { | |
| va_list args; | |
| va_start(args, fmt); | |
| SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, fmt, args); | |
| va_end(args); | |
| } | |
| with_clang_gcc(__attribute__ (( format( __printf__, 1, 2 )))) | |
| void osLogErr (const Char *fmt, ...) | |
| { | |
| va_list args; | |
| va_start(args, fmt); | |
| SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_ERROR, fmt, args); | |
| va_end(args); | |
| } | |
| /* Assertion ***********************************************************************/ | |
| with_clang_gcc(__attribute__ (( format( __printf__, 6, 7 )))) | |
| Bool os_Assert (const Char *filename, Sint line, const Char *funcname, | |
| Bool condition, const Char *condition_str, | |
| const Char *fmt, ...) | |
| { | |
| if (!condition) { | |
| SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Assertion failed: %s in %s (at %s:%d)", condition_str, funcname, filename, line); | |
| va_list args; | |
| va_start(args, fmt); | |
| SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_ERROR, fmt, args); | |
| va_end(args); | |
| SDL_assert(condition); | |
| } | |
| return condition; | |
| } | |
| /* File System ***********************************************************************/ | |
| const Path* osGetProgramDirectory (Memory_Push *mempush) | |
| { | |
| const char *basepath = SDL_GetBasePath(); | |
| const Path* result = pathCreate(mempush, basepath); | |
| osAssert(result != nullptr, "Couldn't get the program directory: %s", SDL_GetError()); | |
| return result; | |
| } | |
| const Path* osGetWorkingDirectory (Memory_Push *mempush) | |
| { | |
| const char *cwd = SDL_GetCurrentDirectory(); | |
| const Path* result = pathCreate(mempush, cwd); | |
| osAssert(result != nullptr, "Couldn't get the working directory: %s", SDL_GetError()); | |
| return result; | |
| } | |
| const Path* osGetSettingDirectory (Memory_Push *mempush, const Char *organization, const Char *application) | |
| { | |
| const char *cwd = SDL_GetPrefPath(organization, application); | |
| const Path* result = pathCreate(mempush, cwd); | |
| osAssert(result != nullptr, "Couldn't get the setting directory: %s", SDL_GetError()); | |
| return result; | |
| } | |
| Bool osCreateDirectory (const Path *path) | |
| { | |
| SDL_PathInfo info; | |
| if (SDL_GetPathInfo(pathGetStr(path), &info)) { | |
| if (info.type == SDL_PATHTYPE_DIRECTORY) { | |
| return true; // Already exists | |
| } else if ((info.type == SDL_PATHTYPE_FILE) || (info.type == SDL_PATHTYPE_OTHER)) { | |
| return false; // Something else exists of that name | |
| } | |
| } | |
| if (SDL_CreateDirectory(pathGetStr(path))) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| Bool osPathMove (Txt_Fmt_Pool *tfp, Memory_Push *mempush, const Path *oldpath, const Path *newpath) | |
| { | |
| if (SDL_RenamePath(pathGetStr(oldpath), pathGetStr(newpath))) { | |
| return true; | |
| } else { // The newpath and oldpath might be on different drives. First copy to new, then delete old. | |
| SDL_PathInfo old_info; | |
| if (!osAssert(SDL_GetPathInfo(pathGetStr(oldpath), &old_info), | |
| "Could find information about %s: %s", pathGetStr(oldpath), SDL_GetError())) { | |
| return false; | |
| } | |
| if (!osAssert(old_info.type != SDL_PATHTYPE_NONE, | |
| "Source file %s doesn't exist", pathGetStr(oldpath))) { | |
| return false; | |
| } | |
| if (!osAssert((old_info.type == SDL_PATHTYPE_FILE) || (old_info.type == SDL_PATHTYPE_DIRECTORY), | |
| "Source %s is not a file or directory", pathGetStr(oldpath))) { | |
| return false; | |
| } | |
| if (old_info.type == SDL_PATHTYPE_FILE) { | |
| if (osPathCopy(tfp, mempush, oldpath, newpath)) { | |
| SDL_RemovePath(pathGetStr(oldpath)); // No check, since there is nothing to be done in case of failure anyway | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } else if (old_info.type == SDL_PATHTYPE_DIRECTORY) { | |
| // TODO(naman): Implement directory renaming. First, create a directory of the name of | |
| // destination path. Then if the directory is not empty, recurse over its | |
| // contents and manually copy them one by one in the destination directory. | |
| // Finally, delete the source directory (directly if empty, else first delete | |
| // its contents and then delete it). | |
| return osAssert(false, "Directory moving across drives hasn't yet been implemented"); | |
| } | |
| return false; | |
| } | |
| } | |
| Bool osPathCopy (Txt_Fmt_Pool *tfp, Memory_Push *mempush, const Path *source, const Path *destination) | |
| { | |
| SDL_PathInfo source_info; | |
| if (!osAssert(SDL_GetPathInfo(pathGetStr(source), &source_info), | |
| "Could find information about %s: %s", pathGetStr(source), SDL_GetError())) { | |
| return false; | |
| } | |
| if (!osAssert(source_info.type != SDL_PATHTYPE_NONE, | |
| "Source file %s doesn't exist", pathGetStr(source))) { | |
| return false; | |
| } | |
| if (!osAssert((source_info.type == SDL_PATHTYPE_FILE) || (source_info.type == SDL_PATHTYPE_DIRECTORY), | |
| "Source %s is not a file or directory", pathGetStr(source))) { | |
| return false; | |
| } | |
| if (source_info.type == SDL_PATHTYPE_FILE) { | |
| // We first copy to a temporary file in order to maintain atomicity (to prevent half-copied files). | |
| Txt dest_temp = txtFormat(tfp, mempush, "%s.%x%x.partial_copy", pathGetStr(destination), SDL_rand_bits(), SDL_rand_bits()); | |
| if (SDL_CopyFile(pathGetStr(source), txtStr(dest_temp))) { | |
| Bool result = SDL_CopyFile(txtStr(dest_temp), pathGetStr(destination)); | |
| SDL_RemovePath(txtStr(dest_temp)); // No check, since there is nothing to be done in case of failure anyway | |
| return result; | |
| } else { | |
| return false; | |
| } | |
| } else if (source_info.type == SDL_PATHTYPE_DIRECTORY) { | |
| return osAssert(false, "Directory copying hasn't yet been implemented"); | |
| } | |
| return false; | |
| } | |
| Bool osPathDelete (const Path *path) | |
| { | |
| SDL_PathInfo info; | |
| if (!osAssert(SDL_GetPathInfo(pathGetStr(path), &info), | |
| "Could find information about %s: %s", pathGetStr(path), SDL_GetError())) { | |
| return false; | |
| } | |
| if (!osAssert(info.type != SDL_PATHTYPE_NONE, | |
| "Path %s doesn't exist", pathGetStr(path))) { | |
| return false; | |
| } | |
| if (!osAssert((info.type == SDL_PATHTYPE_FILE) || (info.type == SDL_PATHTYPE_DIRECTORY), | |
| "%s is not a file or directory", pathGetStr(path))) { | |
| return false; | |
| } | |
| if (info.type == SDL_PATHTYPE_FILE) { | |
| Bool result = SDL_RemovePath(pathGetStr(path)); | |
| return result; | |
| } else if (info.type == SDL_PATHTYPE_DIRECTORY) { | |
| // TODO(naman): Implement directory deleting by recursing over its contents. | |
| return osAssert(false, "Directory deleting hasn't yet been implemented"); | |
| } | |
| return false; | |
| } | |
| Bool osDoesPathExists (const Path *path) | |
| { | |
| Bool result = SDL_GetPathInfo(pathGetStr(path), nullptr); | |
| return result; | |
| } | |
| Uint64 osPathGetChangedTime (const Path *path) | |
| { | |
| Uint64 result = 0; | |
| SDL_PathInfo info; | |
| if (SDL_GetPathInfo(pathGetStr(path), &info)) { | |
| result = cast_val(Uint64, info.modify_time); | |
| } | |
| return result; | |
| } | |
| struct OS_Path_Recursor_Data_ { | |
| Txt_Fmt_Pool *tfp; | |
| Memory_Push *mempush; | |
| OS_PATH_RECURSOR_FUNC((*iterator)); | |
| void *userdata; | |
| const OS_Path_Components *iter; | |
| }; | |
| #if defined(ENV_LANG_CXX) | |
| extern "C" | |
| #endif | |
| SDL_EnumerationResult SDLCALL path_Iterator (void *userdata, const char *dirname, const char *fname); | |
| SDL_EnumerationResult SDLCALL path_Iterator (void *userdata, const char *dirname, const char *fname) | |
| { | |
| struct OS_Path_Recursor_Data_ *data = cast_ptr(typeof(data), userdata); | |
| const OS_Path_Components *iter = data->iter; | |
| SDL_assert_paranoid(txtEq(pathCreate(data->mempush, dirname)->_data, iter->parent->_data)); // Just to check | |
| const Path *path = pathJoin(data->tfp, data->mempush, iter->parent, fname); | |
| SDL_PathInfo pathinfo; | |
| SDL_assert(SDL_GetPathInfo(pathGetStr(path), &pathinfo)); // Should never fail, since SDL itself gave us the path | |
| Bool path_is_folder = false; // assignment to prevent warnings | |
| switch (pathinfo.type) { | |
| case SDL_PATHTYPE_FILE: path_is_folder = false; break; | |
| case SDL_PATHTYPE_DIRECTORY: path_is_folder = true; break; | |
| case SDL_PATHTYPE_NONE: case SDL_PATHTYPE_OTHER: default: return SDL_ENUM_CONTINUE; /* Don't iterate over other stuff */ | |
| } | |
| const Char *relative_path_ptr = pathGetStr(path) + pathGetStrlen(iter->root); | |
| if (pathGetStr(iter->root)[pathGetStrlen(iter->root) - 1] != PATH_SEPARATOR) { | |
| relative_path_ptr++; | |
| } | |
| Txt path_relative_to_root = txtNew(data->mempush, relative_path_ptr); | |
| const OS_Path_Components iter_new = { | |
| .path = path, | |
| .name = txtNew(data->mempush, fname), | |
| .parent = iter->parent, | |
| .root = iter->root, | |
| .path_relative_to_root = path_relative_to_root, | |
| }; | |
| OS_Path_Recursor_Result result_raw = data->iterator(data->tfp, data->mempush, &iter_new, path_is_folder, data->userdata); | |
| switch (result_raw) { | |
| default: /* Won't ever come */ | |
| case OS_Path_Recursor_Result_CONTINUE: return SDL_ENUM_CONTINUE; | |
| case OS_Path_Recursor_Result_SUCCESS: return SDL_ENUM_SUCCESS; | |
| case OS_Path_Recursor_Result_FAILURE: return SDL_ENUM_FAILURE; | |
| } | |
| } | |
| Bool osPathRecursorLaunch (Txt_Fmt_Pool *tfp, Memory_Push *mempush, const Path *folder, OS_PATH_RECURSOR_FUNC((*iterator)), void *userdata) | |
| { | |
| const OS_Path_Components iter = { | |
| .parent = folder, | |
| .root = folder, | |
| }; | |
| struct OS_Path_Recursor_Data_ data = { | |
| .tfp = tfp, | |
| .mempush = mempush, | |
| .iterator = iterator, | |
| .userdata = userdata, | |
| .iter = &iter, | |
| }; | |
| Bool result = SDL_EnumerateDirectory(pathGetStr(folder), path_Iterator, &data); | |
| return result; | |
| } | |
| Bool osPathRecursorRecurse (Txt_Fmt_Pool *tfp, Memory_Push *mempush, const OS_Path_Components *iter, | |
| OS_PATH_RECURSOR_FUNC((*iterator)), void *userdata) | |
| { | |
| const Path *parent = iter->path; | |
| SDL_PathInfo parent_info; | |
| SDL_assert(SDL_GetPathInfo(pathGetStr(parent), &parent_info)); | |
| SDL_assert_release(parent_info.type == SDL_PATHTYPE_DIRECTORY); | |
| const OS_Path_Components iter_new = { | |
| .parent = parent, | |
| .root = iter->root, | |
| }; | |
| struct OS_Path_Recursor_Data_ data = { | |
| .tfp = tfp, | |
| .mempush = mempush, | |
| .iterator = iterator, | |
| .userdata = userdata, | |
| .iter = &iter_new, | |
| }; | |
| Bool result = SDL_EnumerateDirectory(pathGetStr(parent), path_Iterator, &data); | |
| return result; | |
| } | |
| struct Path_Collector_Data { | |
| OS_Path_Collection *file_list; | |
| Txt prefix, suffix; | |
| Bool recursive; | |
| Byte _pad[7]; | |
| }; | |
| OS_PATH_RECURSOR_FUNC(osCollectFilePaths_Iterator); | |
| OS_PATH_RECURSOR_FUNC(osCollectFilePaths_Iterator) | |
| { | |
| struct Path_Collector_Data *data = cast_ptr(typeof(data), userdata); | |
| if (is_folder) { | |
| if (data->recursive) { | |
| if (osPathRecursorRecurse(tfp, mempush, iter, osCollectFilePaths_Iterator, userdata)) { | |
| return OS_Path_Recursor_Result_CONTINUE; | |
| } else { | |
| return OS_Path_Recursor_Result_FAILURE; | |
| } | |
| } else { | |
| return OS_Path_Recursor_Result_CONTINUE; | |
| } | |
| } else { | |
| Bool prefix_matched = true; | |
| Bool suffix_matched = true; | |
| if (txtStr(data->prefix)) { | |
| Bool name_is_long_enough = pathGetStrlen(iter->path) >= txtLen(data->prefix); | |
| Bool prefix_is_same = strleq(pathGetStr(iter->path), txtLen(data->prefix), txtStr(data->prefix)); | |
| prefix_matched = name_is_long_enough && prefix_is_same; | |
| } | |
| if (txtStr(data->suffix)) { | |
| Bool name_is_long_enough = pathGetStrlen(iter->path) >= txtLen(data->suffix); | |
| const Char *path_suffix = pathGetStr(iter->path) + pathGetStrlen(iter->path) - txtLen(data->suffix); | |
| Bool suffix_is_same = streq(path_suffix, txtStr(data->suffix)); | |
| suffix_matched = name_is_long_enough && suffix_is_same; | |
| } | |
| if (prefix_matched && suffix_matched) { | |
| segarAdd(data->file_list, *iter); | |
| } | |
| return OS_Path_Recursor_Result_CONTINUE; | |
| } | |
| } | |
| Bool osCollectFilePaths (Txt_Fmt_Pool *tfp, Memory_Push *mempush, | |
| const Path *root, const Txt prefix, const Txt suffix, Bool recursive, | |
| OS_Path_Collection *output_file_list) | |
| { | |
| struct Path_Collector_Data iter_data = { | |
| .file_list = output_file_list, | |
| .prefix = prefix, | |
| .suffix = suffix, | |
| .recursive = recursive, | |
| }; | |
| Bool result = osPathRecursorLaunch(tfp, mempush, root, osCollectFilePaths_Iterator, &iter_data); | |
| return result; | |
| } | |
| void* osFileLoad (Memory *memory, const Path *path, Size *size) | |
| { | |
| Size datasize; | |
| void *data = SDL_LoadFile(pathGetStr(path), &datasize); | |
| osAssert(data != nullptr, "Could read file %s: %s", pathGetStr(path), SDL_GetError()); | |
| if (size) { | |
| *size = datasize; | |
| } | |
| void *result = memAllot(memory, datasize + 1); | |
| memcpy(result, data, datasize + 1); | |
| SDL_free(data); | |
| return result; | |
| } | |
| Bool osFileDump (void *data, Size size, const Path *path) | |
| { | |
| Bool result = osAssert(SDL_SaveFile(pathGetStr(path), data, size), "Couldn't save file %s: %s", pathGetStr(path), SDL_GetError()); | |
| return result; | |
| } | |
| /* Process Control ***********************************************************************/ | |
| OS_Command osCommandInit (Memory_Push *mempush) | |
| { | |
| OS_Command cmd = { | |
| .mempush = mempush, | |
| .argv = segarMake(mempush), | |
| }; | |
| return cmd; | |
| } | |
| void osCommandAppend (OS_Command *cmd, const Char *arg) | |
| { | |
| segarAdd(&cmd->argv, arg); | |
| } | |
| Sint osCommandExecute (OS_Command *cmd, Memory_Push *mempush, Txt *output) | |
| { | |
| osCommandAppend(cmd, nullptr); | |
| const Char **argv = segarAcquireContiguousPushBuffer(&cmd->argv, mempush); | |
| SDL_PropertiesID props = SDL_CreateProperties(); | |
| SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, argv); | |
| SDL_SetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN, true); | |
| if (output) { | |
| SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP); | |
| } | |
| SDL_Process *process = SDL_CreateProcessWithProperties(props); | |
| osAssert(process != nullptr, "Couldn't create process : %s", SDL_GetError()); | |
| Sint exitcode = 0; | |
| if (output) { | |
| Size outsize; | |
| void *out = SDL_ReadProcess(process, &outsize, &exitcode); | |
| *output = txtNewL(cmd->mempush, cast_ptr(Char*, out), outsize); | |
| } else { | |
| SDL_WaitProcess(process, true, &exitcode); | |
| } | |
| SDL_DestroyProcess(process); | |
| SDL_DestroyProperties(props); | |
| return exitcode; | |
| } | |
| /* Environment *************************************************************************/ | |
| const Char* osEnvironmentVariableGet(const Char *key) | |
| { | |
| SDL_Environment *env = SDL_GetEnvironment(); | |
| osAssert(env != nullptr, "Couldn't get the process environment: %s", SDL_GetError()); | |
| const Char *result = SDL_GetEnvironmentVariable(env, key); | |
| return result; | |
| } | |
| const Char* osEnvironmentVariableSet(const Char *key, const Char *val, Bool overwrite) | |
| { | |
| SDL_Environment *env = SDL_GetEnvironment(); | |
| osAssert(env != nullptr, "Couldn't get the process environment"); | |
| const Char *result = SDL_GetEnvironmentVariable(env, key); | |
| osAssert(SDL_SetEnvironmentVariable(env, key, val, overwrite), | |
| "Couldn't set the envionment variable %s to %s: %s", key, val, SDL_GetError()); | |
| return result; // Return the old value | |
| } | |
| /* Time *************************************************************************/ | |
| Uint64 osGetNanosecondsFromEpoch (void) | |
| { | |
| SDL_Time ns; | |
| osAssert(SDL_GetCurrentTime(&ns), "Couldn't get nanoseconds from epoch: %s", SDL_GetError()); | |
| Uint64 result = cast_val(typeof(result), ns); | |
| return result; | |
| } | |
| OS_Time osGetTime (Bool local_time) | |
| { | |
| Uint64 ns = osGetNanosecondsFromEpoch(); | |
| SDL_DateTime time; | |
| osAssert(SDL_TimeToDateTime(cast_val(SDL_Time, ns), &time, local_time), "Couldn't convert nanoseconds into time: %s", SDL_GetError()); | |
| OS_Time result = { | |
| .year = cast_val(Uint16, time.year), | |
| .month = cast_val(Uint8, time.month), | |
| .day = cast_val(Uint8, time.day), | |
| .hour = cast_val(Uint8, time.hour), | |
| .minute = cast_val(Uint8, time.minute), | |
| .second = cast_val(Uint8, time.second), | |
| .day_of_week = cast_val(Uint8, time.day_of_week), | |
| .nanosecond = cast_val(Uint32, time.nanosecond), | |
| .utc_offset = time.utc_offset, | |
| }; | |
| return result; | |
| } | |
| Uint64 osTickCounter (void) | |
| { | |
| Uint64 result = SDL_GetPerformanceCounter(); | |
| return result; | |
| } | |
| Uint64 osTickGetRate (void) | |
| { | |
| Uint64 result = SDL_GetPerformanceFrequency(); | |
| return result; | |
| } | |
| #else | |
| # error Platform not supported | |
| #endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment