#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&...); #ifdef _WIN32 // Get a string for a windows error (DWORD). Optionally a module HANDLE can be passed. std::string win_error_to_string(unsigned long error, void* module_handle = nullptr); #endif } 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); } }; namespace fmt { template concept CharT = (std::is_same_v || std::is_same_v); } 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 FORCE_INLINE SAFE_BUFFERS(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 FORCE_INLINE SAFE_BUFFERS(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 FORCE_INLINE SAFE_BUFFERS(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* }; template <> struct fmt_class_string : fmt_class_string { }; template <> struct fmt_class_string : fmt_class_string { }; template <> struct fmt_class_string { static void format(std::string& out, u64 arg); }; template <> struct fmt_class_string : fmt_class_string { }; namespace fmt { // Both uchar and std::byte are allowed template concept ByteArray = requires (T& t) { const_cast, std::byte, uchar>&>(std::data(t)[0]); }; } template struct fmt_class_string { static FORCE_INLINE SAFE_BUFFERS(const T&) get_object(u64 arg) { return *reinterpret_cast(static_cast(arg)); } static void format(std::string& out, u64 arg) { const auto& obj = get_object(arg); void format_byte_array(std::string&, const uchar*, usz); format_byte_array(out, reinterpret_cast(std::data(obj)), std::size(obj)); } }; 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 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 FORCE_INLINE SAFE_BUFFERS(void) append(std::string& out, const CharT(&fmt)[N], const Args&... args) { raw_append(out, reinterpret_cast(fmt), type_info_v, fmt_args_t{fmt_unveil::get(args)...}); } // Formatting function template FORCE_INLINE SAFE_BUFFERS(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]] FORCE_INLINE SAFE_BUFFERS() 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()) { raw_throw_exception({line, col, file, func}, reinterpret_cast(fmt), type_info_v, fmt_args_t{fmt_unveil::get(args)...}); } #ifndef _MSC_VER [[noreturn]] ~throw_exception(); #endif }; template throw_exception(const CharT(&)[N], const Args&...) -> throw_exception; // Helper template: pack format variables template struct tie { // Universal reference std::add_rvalue_reference_t arg; tie next; // Store only references, unveil op is postponed tie(Arg&& arg, Args&&... args) noexcept : arg(std::forward(arg)) , next(std::forward(args)...) { } using type = std::remove_cvref_t; // Storage for fmt_unveil (deferred initialization) decltype(fmt_unveil::get(std::declval())) value; void init(u64 to[]) { value = fmt_unveil::get(arg); to[0] = value; next.init(to + 1); } }; template <> struct tie { void init(u64 to[]) const { // Isn't really null terminated, this value has no meaning to[0] = 0; } }; template tie(Args&&... args) -> tie; // Ensure with formatting template decltype(auto) ensure(T&& arg, const CharT(&fmt)[N], tie args, u32 line = __builtin_LINE(), u32 col = __builtin_COLUMN(), const char* file = __builtin_FILE(), const char* func = __builtin_FUNCTION()) noexcept { if (std::forward(arg)) [[likely]] { return std::forward(arg); } // Prepare u64 array u64 data[sizeof...(Args) + 1]; args.init(data); raw_throw_exception({line, col, file, func}, reinterpret_cast(fmt), type_info_v...>, +data); } }