#define DN_CONVERT_CPP DN_API int DN_CVT_FmtBuffer3DotTruncate(char *buffer, int size, DN_FMT_ATTRIB char const *fmt, ...) { va_list args; va_start(args, fmt); int size_required = DN_VSNPrintF(buffer, size, fmt, args); int result = DN_Max(DN_Min(size_required, size - 1), 0); if (result == size - 1) { buffer[size - 2] = '.'; buffer[size - 3] = '.'; } va_end(args); return result; } DN_API DN_CVTU64Str8 DN_CVT_U64ToStr8(uint64_t val, char separator) { DN_CVTU64Str8 result = {}; if (val == 0) { result.data[result.size++] = '0'; } else { // NOTE: The number is written in reverse because we form the string by // dividing by 10, so we write it in, then reverse it out after all is // done. DN_CVTU64Str8 temp = {}; for (DN_USize digit_count = 0; val > 0; digit_count++) { if (separator && (digit_count != 0) && (digit_count % 3 == 0)) temp.data[temp.size++] = separator; auto digit = DN_CAST(char)(val % 10); temp.data[temp.size++] = '0' + digit; val /= 10; } // NOTE: Reverse the string DN_MSVC_WARNING_PUSH DN_MSVC_WARNING_DISABLE(6293) // Ill-defined for-loop DN_MSVC_WARNING_DISABLE(6385) // Reading invalid data from 'temp.data' unsigned overflow is valid for loop termination for (DN_USize temp_index = temp.size - 1; temp_index < temp.size; temp_index--) { char ch = temp.data[temp_index]; result.data[result.size++] = ch; } DN_MSVC_WARNING_POP } return result; } DN_API DN_CVTU64ByteSize DN_CVT_U64ToByteSize(uint64_t bytes, DN_CVTU64ByteSizeType desired_type) { DN_CVTU64ByteSize result = {}; result.bytes = DN_CAST(DN_F64) bytes; if (!DN_Check(desired_type != DN_CVTU64ByteSizeType_Count)) { result.suffix = DN_CVT_U64ByteSizeTypeString(result.type); return result; } if (desired_type == DN_CVTU64ByteSizeType_Auto) for (; result.type < DN_CVTU64ByteSizeType_Count && result.bytes >= 1024.0; result.type = DN_CAST(DN_CVTU64ByteSizeType)(DN_CAST(DN_USize) result.type + 1)) result.bytes /= 1024.0; else for (; result.type < desired_type; result.type = DN_CAST(DN_CVTU64ByteSizeType)(DN_CAST(DN_USize) result.type + 1)) result.bytes /= 1024.0; result.suffix = DN_CVT_U64ByteSizeTypeString(result.type); return result; } DN_API DN_Str8 DN_CVT_U64ToByteSizeStr8(DN_Arena *arena, uint64_t bytes, DN_CVTU64ByteSizeType desired_type) { DN_CVTU64ByteSize byte_size = DN_CVT_U64ToByteSize(bytes, desired_type); DN_Str8 result = DN_Str8_InitF(arena, "%.2f%.*s", byte_size.bytes, DN_STR_FMT(byte_size.suffix)); return result; } DN_API DN_Str8 DN_CVT_U64ByteSizeTypeString(DN_CVTU64ByteSizeType type) { DN_Str8 result = DN_STR8(""); switch (type) { case DN_CVTU64ByteSizeType_B: result = DN_STR8("B"); break; case DN_CVTU64ByteSizeType_KiB: result = DN_STR8("KiB"); break; case DN_CVTU64ByteSizeType_MiB: result = DN_STR8("MiB"); break; case DN_CVTU64ByteSizeType_GiB: result = DN_STR8("GiB"); break; case DN_CVTU64ByteSizeType_TiB: result = DN_STR8("TiB"); break; case DN_CVTU64ByteSizeType_Count: result = DN_STR8(""); break; case DN_CVTU64ByteSizeType_Auto: result = DN_STR8(""); break; } return result; } DN_API DN_Str8 DN_CVT_U64ToAge(DN_Arena *arena, DN_U64 age_s, DN_CVTU64AgeUnit unit) { DN_Str8 result = {}; if (!arena) return result; char buffer[512]; DN_Arena stack_arena = DN_Arena_InitFromBuffer(buffer, sizeof(buffer), DN_ArenaFlags_NoPoison); DN_Str8Builder builder = DN_Str8Builder_Init(&stack_arena); DN_U64 remainder = age_s; if (unit & DN_CVTU64AgeUnit_Year) { DN_USize value = remainder / DN_YearsToSec(1); remainder -= DN_YearsToSec(value); if (value) DN_Str8Builder_AppendF(&builder, "%s%zuyr", builder.string_size ? " " : "", value); } if (unit & DN_CVTU64AgeUnit_Week) { DN_USize value = remainder / DN_WeeksToSec(1); remainder -= DN_WeeksToSec(value); if (value) DN_Str8Builder_AppendF(&builder, "%s%zuw", builder.string_size ? " " : "", value); } if (unit & DN_CVTU64AgeUnit_Day) { DN_USize value = remainder / DN_DaysToSec(1); remainder -= DN_DaysToSec(value); if (value) DN_Str8Builder_AppendF(&builder, "%s%zud", builder.string_size ? " " : "", value); } if (unit & DN_CVTU64AgeUnit_Hr) { DN_USize value = remainder / DN_HoursToSec(1); remainder -= DN_HoursToSec(value); if (value) DN_Str8Builder_AppendF(&builder, "%s%zuh", builder.string_size ? " " : "", value); } if (unit & DN_CVTU64AgeUnit_Min) { DN_USize value = remainder / DN_MinutesToSec(1); remainder -= DN_MinutesToSec(value); if (value) DN_Str8Builder_AppendF(&builder, "%s%zum", builder.string_size ? " " : "", value); } if (unit & DN_CVTU64AgeUnit_Sec) { DN_USize value = remainder; if (value || builder.string_size == 0) DN_Str8Builder_AppendF(&builder, "%s%zus", builder.string_size ? " " : "", value); } result = DN_Str8Builder_Build(&builder, arena); return result; } DN_API DN_Str8 DN_CVT_F64ToAge(DN_Arena *arena, DN_F64 age_s, DN_CVTU64AgeUnit unit) { DN_Str8 result = {}; if (!arena) return result; char buffer[128]; DN_Arena stack_arena = DN_Arena_InitFromBuffer(buffer, sizeof(buffer), DN_ArenaFlags_NoPoison); DN_Str8Builder builder = DN_Str8Builder_Init(&stack_arena); DN_F64 remainder = age_s; if (unit & DN_CVTU64AgeUnit_Year) { DN_F64 value = remainder / DN_CAST(DN_F64) DN_YearsToSec(1); if (value >= 1.0) { remainder -= DN_YearsToSec(value); DN_Str8Builder_AppendF(&builder, "%s%.1fyr", builder.string_size ? " " : "", value); } } if (unit & DN_CVTU64AgeUnit_Week) { DN_F64 value = remainder / DN_CAST(DN_F64) DN_WeeksToSec(1); if (value >= 1.0) { remainder -= DN_WeeksToSec(value); DN_Str8Builder_AppendF(&builder, "%s%.1fw", builder.string_size ? " " : "", value); } } if (unit & DN_CVTU64AgeUnit_Day) { DN_F64 value = remainder / DN_CAST(DN_F64) DN_DaysToSec(1); if (value >= 1.0) { remainder -= DN_WeeksToSec(value); DN_Str8Builder_AppendF(&builder, "%s%.1fd", builder.string_size ? " " : "", value); } } if (unit & DN_CVTU64AgeUnit_Hr) { DN_F64 value = remainder / DN_CAST(DN_F64) DN_HoursToSec(1); if (value >= 1.0) { remainder -= DN_HoursToSec(value); DN_Str8Builder_AppendF(&builder, "%s%.1fh", builder.string_size ? " " : "", value); } } if (unit & DN_CVTU64AgeUnit_Min) { DN_F64 value = remainder / DN_CAST(DN_F64) DN_MinutesToSec(1); if (value >= 1.0) { remainder -= DN_MinutesToSec(value); DN_Str8Builder_AppendF(&builder, "%s%.1fm", builder.string_size ? " " : "", value); } } if (unit & DN_CVTU64AgeUnit_Sec) { DN_F64 value = remainder; DN_Str8Builder_AppendF(&builder, "%s%.1fs", builder.string_size ? " " : "", value); } result = DN_Str8Builder_Build(&builder, arena); return result; } DN_API uint64_t DN_CVT_HexToU64(DN_Str8 hex) { DN_Str8 real_hex = DN_Str8_TrimPrefix(DN_Str8_TrimPrefix(hex, DN_STR8("0x")), DN_STR8("0X")); DN_USize max_hex_size = sizeof(uint64_t) * 2 /*hex chars per byte*/; DN_Assert(real_hex.size <= max_hex_size); DN_USize size = DN_Min(max_hex_size, real_hex.size); uint64_t result = 0; for (DN_USize index = 0; index < size; index++) { char ch = real_hex.data[index]; DN_CharHexToU8 val = DN_Char_HexToU8(ch); if (!val.success) break; result = (result << 4) | val.value; } return result; } DN_API DN_Str8 DN_CVT_U64ToHex(DN_Arena *arena, uint64_t number, uint32_t flags) { DN_Str8 prefix = {}; if ((flags & DN_CVTHexU64Str8Flags_0xPrefix)) prefix = DN_STR8("0x"); char const *fmt = (flags & DN_CVTHexU64Str8Flags_UppercaseHex) ? "%I64X" : "%I64x"; DN_USize required_size = DN_CStr8_FSize(fmt, number) + prefix.size; DN_Str8 result = DN_Str8_Alloc(arena, required_size, DN_ZeroMem_No); if (DN_Str8_HasData(result)) { DN_Memcpy(result.data, prefix.data, prefix.size); int space = DN_CAST(int) DN_Max((result.size - prefix.size) + 1, 0); /*null-terminator*/ DN_SNPrintF(result.data + prefix.size, space, fmt, number); } return result; } DN_API DN_CVTU64HexStr8 DN_CVT_U64ToHexStr8(uint64_t number, DN_CVTHexU64Str8Flags flags) { DN_Str8 prefix = {}; if (flags & DN_CVTHexU64Str8Flags_0xPrefix) prefix = DN_STR8("0x"); DN_CVTU64HexStr8 result = {}; DN_Memcpy(result.data, prefix.data, prefix.size); result.size += DN_CAST(int8_t) prefix.size; char const *fmt = (flags & DN_CVTHexU64Str8Flags_UppercaseHex) ? "%I64X" : "%I64x"; int size = DN_SNPrintF(result.data + result.size, DN_ArrayCountU(result.data) - result.size, fmt, number); result.size += DN_CAST(uint8_t) size; DN_Assert(result.size < DN_ArrayCountU(result.data)); // NOTE: snprintf returns the required size of the format string // irrespective of if there's space or not, but, always null terminates so // the last byte is wasted. result.size = DN_Min(result.size, DN_ArrayCountU(result.data) - 1); return result; } DN_API bool DN_CVT_BytesToHexPtr(void const *src, DN_USize src_size, char *dest, DN_USize dest_size) { if (!src || !dest) return false; if (!DN_Check(dest_size >= src_size * 2)) return false; char const *HEX = "0123456789abcdef"; unsigned char const *src_u8 = DN_CAST(unsigned char const *) src; for (DN_USize src_index = 0, dest_index = 0; src_index < src_size; src_index++) { char byte = src_u8[src_index]; char hex01 = (byte >> 4) & 0b1111; char hex02 = (byte >> 0) & 0b1111; dest[dest_index++] = HEX[(int)hex01]; dest[dest_index++] = HEX[(int)hex02]; } return true; } DN_API DN_Str8 DN_CVT_BytesToHex(DN_Arena *arena, void const *src, DN_USize size) { DN_Str8 result = {}; if (!src || size <= 0) return result; result = DN_Str8_Alloc(arena, size * 2, DN_ZeroMem_No); result.data[result.size] = 0; bool converted = DN_CVT_BytesToHexPtr(src, size, result.data, result.size); DN_Assert(converted); return result; } DN_API DN_USize DN_CVT_HexToBytesPtrUnchecked(DN_Str8 hex, void *dest, DN_USize dest_size) { DN_USize result = 0; unsigned char *dest_u8 = DN_CAST(unsigned char *) dest; for (DN_USize hex_index = 0; hex_index < hex.size; hex_index += 2, result += 1) { char hex01 = hex.data[hex_index]; char hex02 = (hex_index + 1 < hex.size) ? hex.data[hex_index + 1] : 0; char bit4_01 = DN_Char_HexToU8(hex01).value; char bit4_02 = DN_Char_HexToU8(hex02).value; char byte = (bit4_01 << 4) | (bit4_02 << 0); dest_u8[result] = byte; } DN_Assert(result <= dest_size); return result; } DN_API DN_USize DN_CVT_HexToBytesPtr(DN_Str8 hex, void *dest, DN_USize dest_size) { hex = DN_Str8_TrimPrefix(hex, DN_STR8("0x")); hex = DN_Str8_TrimPrefix(hex, DN_STR8("0X")); DN_USize result = 0; if (!DN_Str8_HasData(hex)) return result; // NOTE: Trimmed hex can be "0xf" -> "f" or "0xAB" -> "AB" // Either way, the size can be odd or even, hence we round up to the nearest // multiple of two to ensure that we calculate the min buffer size orrectly. DN_USize hex_size_rounded_up = hex.size + (hex.size % 2); DN_USize min_buffer_size = hex_size_rounded_up / 2; if (hex.size <= 0 || !DN_Check(dest_size >= min_buffer_size)) return result; result = DN_CVT_HexToBytesPtrUnchecked(hex, dest, dest_size); return result; } DN_API DN_Str8 DN_CVT_HexToBytesUnchecked(DN_Arena *arena, DN_Str8 hex) { DN_USize hex_size_rounded_up = hex.size + (hex.size % 2); DN_Str8 result = DN_Str8_Alloc(arena, (hex_size_rounded_up / 2), DN_ZeroMem_No); if (result.data) { DN_USize bytes_written = DN_CVT_HexToBytesPtr(hex, result.data, result.size); DN_Assert(bytes_written == result.size); } return result; } DN_API DN_Str8 DN_CVT_HexToBytes(DN_Arena *arena, DN_Str8 hex) { hex = DN_Str8_TrimPrefix(hex, DN_STR8("0x")); hex = DN_Str8_TrimPrefix(hex, DN_STR8("0X")); DN_Str8 result = {}; if (!DN_Str8_HasData(hex)) return result; if (!DN_Check(DN_Str8_IsAll(hex, DN_Str8IsAll_Hex))) return result; result = DN_CVT_HexToBytesUnchecked(arena, hex); return result; }