#pragma once // No BOM and only basic ASCII in this header, or a neko will die #include "util/types.hpp" #include namespace fmt { template static std::string format(const CharT(&)[N], const Args&...); } template struct fmt_unveil { static_assert(sizeof(T) > 0, "fmt_unveil<> error: incomplete type"); using type = T; static inline u64 get(const T& arg) { return reinterpret_cast(&arg); } // Temporary value container (can possibly be created by other fmt_unveil<> specializations) struct u64_wrapper { T arg; // Allow implicit conversion operator u64() const { return reinterpret_cast(&arg); } }; // This overload resolution takes the precedence static inline u64_wrapper get(T&& arg) { return u64_wrapper{std::move(arg)}; } }; template struct fmt_unveil::value && sizeof(T) <= 8 && alignof(T) <= 8>> { using type = T; static inline u64 get(T arg) { return static_cast(arg); } }; template struct fmt_unveil::value && sizeof(T) <= 8 && alignof(T) <= 8>> { using type = T; // Convert FP to f64 and reinterpret as u64 static inline u64 get(const f64& arg) { return std::bit_cast(arg); } }; template struct fmt_unveil::value>> { using type = T; static inline u64 get(T arg) { return static_cast>(arg); } }; template struct fmt_unveil { using type = std::add_const_t*; static inline u64 get(type arg) { return reinterpret_cast(arg); } }; template struct fmt_unveil { using type = std::add_const_t*; static inline u64 get(type arg) { return reinterpret_cast(arg); } }; template struct fmt_unveil, void> { using type = typename fmt_unveil::type; static inline auto get(const se_t& arg) { return fmt_unveil::get(arg); } }; // String type format provider, also type classifier (format() called if an argument is formatted as "%s") template struct fmt_class_string { // Formatting function (must be explicitly specialized) static void format(std::string& out, u64 arg); // Helper typedef (visible in format()) using type = T; // Helper function (converts arg to object reference) static SAFE_BUFFERS FORCE_INLINE const T& get_object(u64 arg) { return *reinterpret_cast(static_cast(arg)); } // Enum -> string function type using convert_t = const char*(*)(T value); // Helper function (safely converts arg to enum value) static SAFE_BUFFERS FORCE_INLINE void format_enum(std::string& out, u64 arg, convert_t convert) { const auto value = static_cast>(arg); // Check narrowing if (static_cast(value) == arg) { if (const char* str = convert(static_cast(value))) { out += str; return; } } // Fallback to underlying type formatting fmt_class_string>::format(out, static_cast(value)); } // Helper function (bitset formatting) static SAFE_BUFFERS FORCE_INLINE void format_bitset(std::string& out, u64 arg, const char* prefix, const char* delim, const char* suffix, void (*fmt)(std::string&, u64)) { // Start from raw value fmt_class_string::format(out, arg); out += prefix; for (u64 i = 0; i < 63; i++) { const u64 mask = 1ull << i; if (arg & mask) { fmt(out, i); if (arg >> (i + 1)) { out += delim; } } } if (arg & (1ull << 63)) { fmt(out, 63); } out += suffix; } // Helper constant (may be used in format_enum as lambda return value) static constexpr const char* unknown = nullptr; }; template <> struct fmt_class_string { static void format(std::string& out, u64 arg); }; template struct fmt_class_string : fmt_class_string { // Classify all pointers as const void* }; template <> struct fmt_class_string { static void format(std::string& out, u64 arg); }; template <> struct fmt_class_string : fmt_class_string { // Classify char* as const char* }; struct fmt_type_info { decltype(&fmt_class_string::format) fmt_string; template static constexpr fmt_type_info make() { return fmt_type_info { &fmt_class_string::format, }; } }; // Argument array type (each element generated via fmt_unveil<>) template using fmt_args_t = const u64(&&)[sizeof...(Args) + 1]; template using fmt_unveil_t = typename fmt_unveil::type; namespace fmt { // Base-57 format helper struct base57 { const uchar* data; usz size; template base57(const T& arg) : data(reinterpret_cast(&arg)) , size(sizeof(T)) { } base57(const uchar* data, usz size) : data(data) , size(size) { } }; template SAFE_BUFFERS FORCE_INLINE const fmt_type_info* get_type_info() { // Constantly initialized null-terminated list of type-specific information static constexpr fmt_type_info result[sizeof...(Args) + 1]{fmt_type_info::make>()...}; return result; } template constexpr const fmt_type_info type_info_v[sizeof...(Args) + 1]{fmt_type_info::make>()...}; // Internal formatting function void raw_append(std::string& out, const char*, const fmt_type_info*, const u64*) noexcept; // Formatting function template SAFE_BUFFERS FORCE_INLINE void append(std::string& out, const CharT(&fmt)[N], const Args&... args) { static constexpr fmt_type_info type_list[sizeof...(Args) + 1]{fmt_type_info::make>()...}; raw_append(out, reinterpret_cast(fmt), type_list, fmt_args_t{fmt_unveil::get(args)...}); } // Formatting function template SAFE_BUFFERS FORCE_INLINE std::string format(const CharT(&fmt)[N], const Args&... args) { std::string result; append(result, fmt, args...); return result; } // Internal exception message formatting template, must be explicitly specialized or instantiated in cpp to minimize code bloat [[noreturn]] void raw_throw_exception(const src_loc&, const char*, const fmt_type_info*, const u64*); // Throw exception with formatting template struct throw_exception { [[noreturn]] SAFE_BUFFERS FORCE_INLINE throw_exception(const CharT(&fmt)[N], const Args&... args, u32 line = __builtin_LINE(), u32 col = __builtin_COLUMN(), const char* file = __builtin_FILE(), const char* func = __builtin_FUNCTION()) { static constexpr fmt_type_info type_list[sizeof...(Args) + 1]{fmt_type_info::make>()...}; raw_throw_exception({line, col, file, func}, reinterpret_cast(fmt), type_list, fmt_args_t{fmt_unveil::get(args)...}); } #ifdef _DEBUG [[noreturn]] ~throw_exception() { #ifdef _MSC_VER __assume(false); #else __builtin_unreachable(); #endif } #else [[noreturn]] ~throw_exception(); #endif }; template throw_exception(const CharT(&)[N], const Args&...) -> throw_exception; }