#ifdef MD_INTELLISENSE_DIRECTIVES #pragma once #include "base/strings.h" #endif // Copyright (c) 2024 Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) //////////////////////////////// //~ rjf: Third Party Includes // #if !BUILD_SUPPLEMENTARY_UNIT // # define STB_SPRINTF_IMPLEMENTATION // # define STB_SPRINTF_STATIC // # include "third_party/stb/stb_sprintf.h" // #endif //////////////////////////////// //~ NOTE(allen): String <-> Integer Tables read_only global U8 integer_symbols[16] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F', }; // NOTE(allen): Includes reverses for uppercase and lowercase hex. read_only global U8 integer_symbol_reverse[128] = { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, }; read_only global U8 base64[64] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '_', '$', }; read_only global U8 base64_reverse[128] = { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xFF,0xFF,0xFF,0xFF,0xFF,0x00, 0xFF,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32, 0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0x3E, 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18, 0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0xFF,0xFF,0xFF,0xFF,0xFF, }; //////////////////////////////// //~ rjf: Character Classification & Conversion Functions internal B32 char_is_space(U8 c){ return(c == ' ' || c == '\n' || c == '\t' || c == '\r' || c == '\f' || c == '\v'); } internal B32 char_is_upper(U8 c){ return('A' <= c && c <= 'Z'); } internal B32 char_is_lower(U8 c){ return('a' <= c && c <= 'z'); } internal B32 char_is_alpha(U8 c){ return(char_is_upper(c) || char_is_lower(c)); } internal B32 char_is_slash(U8 c){ return(c == '/' || c == '\\'); } internal B32 char_is_digit(U8 c, U32 base){ B32 result = 0; if (0 < base && base <= 16){ U8 val = integer_symbol_reverse[c]; if (val < base){ result = 1; } } return(result); } internal U8 char_to_lower(U8 c){ if (char_is_upper(c)){ c += ('a' - 'A'); } return(c); } internal U8 char_to_upper(U8 c){ if (char_is_lower(c)){ c += ('A' - 'a'); } return(c); } internal U8 char_to_correct_slash(U8 c){ if(char_is_slash(c)){ c = '/'; } return(c); } //////////////////////////////// //~ rjf: C-String Measurement internal U64 cstring8_length(U8 *c){ U8 *p = c; for (;*p != 0; p += 1); return(p - c); } internal U64 cstring16_length(U16 *c){ U16 *p = c; for (;*p != 0; p += 1); return(p - c); } internal U64 cstring32_length(U32 *c){ U32 *p = c; for (;*p != 0; p += 1); return(p - c); } //////////////////////////////// //~ rjf: String Constructors internal String8 str8(U8 *str, U64 size){ String8 result = {str, size}; return(result); } internal String8 str8_range(U8 *first, U8 *one_past_last){ String8 result = {first, (U64)(one_past_last - first)}; return(result); } internal String8 str8_zero(void){ String8 result = {0}; return(result); } internal String16 str16(U16 *str, U64 size){ String16 result = {str, size}; return(result); } internal String16 str16_range(U16 *first, U16 *one_past_last){ String16 result = {first, (U64)(one_past_last - first)}; return(result); } internal String16 str16_zero(void){ String16 result = {0}; return(result); } internal String32 str32(U32 *str, U64 size){ String32 result = {str, size}; return(result); } internal String32 str32_range(U32 *first, U32 *one_past_last){ String32 result = {first, (U64)(one_past_last - first)}; return(result); } internal String32 str32_zero(void){ String32 result = {0}; return(result); } internal String8 str8_cstring(char *c){ String8 result = {(U8*)c, cstring8_length((U8*)c)}; return(result); } internal String16 str16_cstring(U16 *c){ String16 result = {(U16*)c, cstring16_length((U16*)c)}; return(result); } internal String32 str32_cstring(U32 *c){ String32 result = {(U32*)c, cstring32_length((U32*)c)}; return(result); } internal String8 str8_cstring_capped(void *cstr, void *cap) { char *ptr = (char*)cstr; char *opl = (char*)cap; for (;ptr < opl && *ptr != 0; ptr += 1); U64 size = (U64)(ptr - (char *)cstr); String8 result = {(U8*)cstr, size}; return(result); } //////////////////////////////// //~ rjf: String Stylization internal String8 upper_from_str8(Arena *arena, String8 string) { string = push_str8_copy(arena, string); for(U64 idx = 0; idx < string.size; idx += 1) { string.str[idx] = char_to_upper(string.str[idx]); } return string; } internal String8 lower_from_str8(Arena *arena, String8 string) { string = push_str8_copy(arena, string); for(U64 idx = 0; idx < string.size; idx += 1) { string.str[idx] = char_to_lower(string.str[idx]); } return string; } internal String8 backslashed_from_str8(Arena *arena, String8 string) { string = push_str8_copy(arena, string); for(U64 idx = 0; idx < string.size; idx += 1) { string.str[idx] = char_is_slash(string.str[idx]) ? '\\' : string.str[idx]; } return string; } //////////////////////////////// //~ rjf: String Matching internal B32 str8_match(String8 a, String8 b, StringMatchFlags flags){ B32 result = 0; if (a.size == b.size || (flags & StringMatchFlag_RightSideSloppy)){ B32 case_insensitive = (flags & StringMatchFlag_CaseInsensitive); B32 slash_insensitive = (flags & StringMatchFlag_SlashInsensitive); U64 size = Min(a.size, b.size); result = 1; for (U64 i = 0; i < size; i += 1){ U8 at = a.str[i]; U8 bt = b.str[i]; if (case_insensitive){ at = char_to_upper(at); bt = char_to_upper(bt); } if (slash_insensitive){ at = char_to_correct_slash(at); bt = char_to_correct_slash(bt); } if (at != bt){ result = 0; break; } } } return(result); } internal U64 str8_find_needle(String8 string, U64 start_pos, String8 needle, StringMatchFlags flags){ U8 *p = string.str + start_pos; U64 stop_offset = Max(string.size + 1, needle.size) - needle.size; U8 *stop_p = string.str + stop_offset; if (needle.size > 0){ U8 *string_opl = string.str + string.size; String8 needle_tail = str8_skip(needle, 1); StringMatchFlags adjusted_flags = flags | StringMatchFlag_RightSideSloppy; U8 needle_first_char_adjusted = needle.str[0]; if(adjusted_flags & StringMatchFlag_CaseInsensitive){ needle_first_char_adjusted = char_to_upper(needle_first_char_adjusted); } for (;p < stop_p; p += 1){ U8 haystack_char_adjusted = *p; if(adjusted_flags & StringMatchFlag_CaseInsensitive){ haystack_char_adjusted = char_to_upper(haystack_char_adjusted); } if (haystack_char_adjusted == needle_first_char_adjusted){ if (str8_match(str8_range(p + 1, string_opl), needle_tail, adjusted_flags)){ break; } } } } U64 result = string.size; if (p < stop_p){ result = (U64)(p - string.str); } return(result); } internal B32 str8_ends_with(String8 string, String8 end, StringMatchFlags flags){ String8 postfix = str8_postfix(string, end.size); B32 is_match = str8_match(end, postfix, flags); return is_match; } //////////////////////////////// //~ rjf: String Slicing internal String8 str8_substr(String8 str, Rng1U64 range){ range.min = ClampTop(range.min, str.size); range.max = ClampTop(range.max, str.size); str.str += range.min; str.size = dim_1u64(range); return(str); } internal String8 str8_prefix(String8 str, U64 size){ str.size = ClampTop(size, str.size); return(str); } internal String8 str8_skip(String8 str, U64 amt){ amt = ClampTop(amt, str.size); str.str += amt; str.size -= amt; return(str); } internal String8 str8_postfix(String8 str, U64 size){ size = ClampTop(size, str.size); str.str = (str.str + str.size) - size; str.size = size; return(str); } internal String8 str8_chop(String8 str, U64 amt){ amt = ClampTop(amt, str.size); str.size -= amt; return(str); } internal String8 str8_skip_chop_whitespace(String8 string){ U8 *first = string.str; U8 *opl = first + string.size; for (;first < opl; first += 1){ if (!char_is_space(*first)){ break; } } for (;opl > first;){ opl -= 1; if (!char_is_space(*opl)){ opl += 1; break; } } String8 result = str8_range(first, opl); return(result); } //////////////////////////////// //~ rjf: String Formatting & Copying internal String8 push_str8_cat(Arena *arena, String8 s1, String8 s2){ String8 str; str.size = s1.size + s2.size; str.str = push_array_no_zero(arena, U8, str.size + 1); MemoryCopy(str.str, s1.str, s1.size); MemoryCopy(str.str + s1.size, s2.str, s2.size); str.str[str.size] = 0; return(str); } internal String8 push_str8_copy(Arena *arena, String8 s){ String8 str; str.size = s.size; str.str = push_array_no_zero(arena, U8, str.size + 1); MemoryCopy(str.str, s.str, s.size); str.str[str.size] = 0; return(str); } internal String8 push_str8fv(Arena *arena, char *fmt, va_list args){ va_list args2; va_copy(args2, args); U32 needed_bytes = raddbg_vsnprintf(0, 0, fmt, args) + 1; String8 result = {0}; result.str = push_array_no_zero(arena, U8, needed_bytes); result.size = raddbg_vsnprintf((char*)result.str, needed_bytes, fmt, args2); result.str[result.size] = 0; va_end(args2); return(result); } internal String8 push_str8f(Arena *arena, char *fmt, ...){ va_list args; va_start(args, fmt); String8 result = push_str8fv(arena, fmt, args); va_end(args); return(result); } //////////////////////////////// //~ rjf: String <=> Integer Conversions //- rjf: string -> integer internal S64 sign_from_str8(String8 string, String8 *string_tail){ // count negative signs U64 neg_count = 0; U64 i = 0; for (; i < string.size; i += 1){ if (string.str[i] == '-'){ neg_count += 1; } else if (string.str[i] != '+'){ break; } } // output part of string after signs *string_tail = str8_skip(string, i); // output integer sign S64 sign = (neg_count & 1)?-1:+1; return(sign); } internal B32 str8_is_integer(String8 string, U32 radix){ B32 result = 0; if (string.size > 0){ if (1 < radix && radix <= 16){ result = 1; for (U64 i = 0; i < string.size; i += 1){ U8 c = string.str[i]; if (!(c < 0x80) || integer_symbol_reverse[c] >= radix){ result = 0; break; } } } } return(result); } internal U64 u64_from_str8(String8 string, U32 radix){ U64 x = 0; if (1 < radix && radix <= 16){ for (U64 i = 0; i < string.size; i += 1){ x *= radix; x += integer_symbol_reverse[string.str[i]&0x7F]; } } return(x); } internal S64 s64_from_str8(String8 string, U32 radix){ S64 sign = sign_from_str8(string, &string); S64 x = (S64)u64_from_str8(string, radix) * sign; return(x); } internal B32 try_u64_from_str8_c_rules(String8 string, U64 *x){ B32 is_integer = 0; if (str8_is_integer(string, 10)){ is_integer = 1; *x = u64_from_str8(string, 10); } else{ String8 hex_string = str8_skip(string, 2); if (str8_match(str8_prefix(string, 2), str8_lit("0x"), 0) && str8_is_integer(hex_string, 0x10)){ is_integer = 1; *x = u64_from_str8(hex_string, 0x10); } else if (str8_match(str8_prefix(string, 2), str8_lit("0b"), 0) && str8_is_integer(hex_string, 2)){ is_integer = 1; *x = u64_from_str8(hex_string, 2); } else{ String8 oct_string = str8_skip(string, 1); if (str8_match(str8_prefix(string, 1), str8_lit("0"), 0) && str8_is_integer(hex_string, 010)){ is_integer = 1; *x = u64_from_str8(oct_string, 010); } } } return(is_integer); } internal B32 try_s64_from_str8_c_rules(String8 string, S64 *x){ String8 string_tail = {0}; S64 sign = sign_from_str8(string, &string_tail); U64 x_u64 = 0; B32 is_integer = try_u64_from_str8_c_rules(string_tail, &x_u64); *x = x_u64*sign; return(is_integer); } //- rjf: integer -> string internal String8 str8_from_memory_size(Arena *arena, U64 z){ String8 result = {0}; if (z < KB(1)){ result = push_str8f(arena, "%llu b", z); } else if (z < MB(1)){ result = push_str8f(arena, "%llu.%02llu Kb", z/KB(1), ((100*z)/KB(1))%100); } else if (z < GB(1)){ result = push_str8f(arena, "%llu.%02llu Mb", z/MB(1), ((100*z)/MB(1))%100); } else{ result = push_str8f(arena, "%llu.%02llu Gb", z/GB(1), ((100*z)/GB(1))%100); } return(result); } internal String8 str8_from_u64(Arena *arena, U64 u64, U32 radix, U8 min_digits, U8 digit_group_separator) { String8 result = {0}; { // rjf: prefix String8 prefix = {0}; switch(radix) { case 16:{prefix = str8_lit("0x");}break; case 8: {prefix = str8_lit("0o");}break; case 2: {prefix = str8_lit("0b");}break; } // rjf: determine # of chars between separators U8 digit_group_size = 3; switch(radix) { default:break; case 2: case 8: case 16: {digit_group_size = 4;}break; } // rjf: prep U64 needed_leading_0s = 0; { U64 needed_digits = 1; { U64 u64_reduce = u64; for(;;) { u64_reduce /= radix; if(u64_reduce == 0) { break; } needed_digits += 1; } } needed_leading_0s = (min_digits > needed_digits) ? min_digits - needed_digits : 0; U64 needed_separators = 0; if(digit_group_separator != 0) { needed_separators = (needed_digits+needed_leading_0s)/digit_group_size; if(needed_separators > 0 && (needed_digits+needed_leading_0s)%digit_group_size == 0) { needed_separators -= 1; } } result.size = prefix.size + needed_leading_0s + needed_separators + needed_digits; result.str = push_array_no_zero(arena, U8, result.size + 1); result.str[result.size] = 0; } // rjf: fill contents { U64 u64_reduce = u64; U64 digits_until_separator = digit_group_size; for(U64 idx = 0; idx < result.size; idx += 1) { if(digits_until_separator == 0 && digit_group_separator != 0) { result.str[result.size - idx - 1] = digit_group_separator; digits_until_separator = digit_group_size+1; } else { result.str[result.size - idx - 1] = char_to_lower(integer_symbols[u64_reduce%radix]); u64_reduce /= radix; } digits_until_separator -= 1; if(u64_reduce == 0) { break; } } for(U64 leading_0_idx = 0; leading_0_idx < needed_leading_0s; leading_0_idx += 1) { result.str[prefix.size + leading_0_idx] = '0'; } } // rjf: fill prefix if(prefix.size != 0) { MemoryCopy(result.str, prefix.str, prefix.size); } } return result; } internal String8 str8_from_s64(Arena *arena, S64 s64, U32 radix, U8 min_digits, U8 digit_group_separator) { String8 result = {0}; // TODO(rjf): preeeeetty sloppy... if(s64 < 0) { Temp scratch = scratch_begin(&arena, 1); String8 numeric_part = str8_from_u64(scratch.arena, (U64)(-s64), radix, min_digits, digit_group_separator); result = push_str8f(arena, "-%S", numeric_part); scratch_end(scratch); } else { result = str8_from_u64(arena, (U64)s64, radix, min_digits, digit_group_separator); } return result; } //////////////////////////////// //~ rjf: String <=> Float Conversions internal F64 f64_from_str8(String8 string) { // TODO(rjf): crappy implementation for now that just uses atof. F64 result = 0; if(string.size > 0) { // rjf: find starting pos of numeric string, as well as sign F64 sign = +1.0; //U64 first_numeric = 0; if(string.str[0] == '-') { //first_numeric = 1; sign = -1.0; } else if(string.str[0] == '+') { //first_numeric = 1; sign = 1.0; } // rjf: gather numerics U64 num_valid_chars = 0; char buffer[64]; for(U64 idx = 0; idx < string.size && num_valid_chars < sizeof(buffer)-1; idx += 1) { if(char_is_digit(string.str[idx], 10) || string.str[idx] == '.') { buffer[num_valid_chars] = string.str[idx]; num_valid_chars += 1; } } // rjf: null-terminate (the reason for all of this!!!!!!) buffer[num_valid_chars] = 0; // rjf: do final conversion result = sign * atof(buffer); } return result; } //////////////////////////////// //~ rjf: String List Construction Functions internal String8Node* str8_list_push_node(String8List *list, String8Node *node){ SLLQueuePush(list->first, list->last, node); list->node_count += 1; list->total_size += node->string.size; return(node); } internal String8Node* str8_list_push_node_set_string(String8List *list, String8Node *node, String8 string){ SLLQueuePush(list->first, list->last, node); list->node_count += 1; list->total_size += string.size; node->string = string; return(node); } internal String8Node* str8_list_push_node_front(String8List *list, String8Node *node){ SLLQueuePushFront(list->first, list->last, node); list->node_count += 1; list->total_size += node->string.size; return(node); } internal String8Node* str8_list_push_node_front_set_string(String8List *list, String8Node *node, String8 string){ SLLQueuePushFront(list->first, list->last, node); list->node_count += 1; list->total_size += string.size; node->string = string; return(node); } internal String8Node* str8_list_push(Arena *arena, String8List *list, String8 string){ String8Node *node = push_array_no_zero(arena, String8Node, 1); str8_list_push_node_set_string(list, node, string); return(node); } internal String8Node* str8_list_push_front(Arena *arena, String8List *list, String8 string){ String8Node *node = push_array_no_zero(arena, String8Node, 1); str8_list_push_node_front_set_string(list, node, string); return(node); } internal void str8_list_concat_in_place(String8List *list, String8List *to_push){ if(to_push->node_count != 0){ if (list->last){ list->node_count += to_push->node_count; list->total_size += to_push->total_size; list->last->next = to_push->first; list->last = to_push->last; } else{ *list = *to_push; } MemoryZeroStruct(to_push); } } internal String8Node* str8_list_push_aligner(Arena *arena, String8List *list, U64 min, U64 align){ String8Node *node = push_array_no_zero(arena, String8Node, 1); U64 new_size = list->total_size + min; U64 increase_size = 0; if (align > 1){ // NOTE(allen): assert is power of 2 Assert(((align - 1) & align) == 0); U64 mask = align - 1; new_size += mask; new_size &= (~mask); increase_size = new_size - list->total_size; } local_persist const U8 zeroes_buffer[64] = {0}; Assert(increase_size <= ArrayCount(zeroes_buffer)); SLLQueuePush(list->first, list->last, node); list->node_count += 1; list->total_size = new_size; node->string.str = (U8*)zeroes_buffer; node->string.size = increase_size; return(node); } internal String8Node* str8_list_pushf(Arena *arena, String8List *list, char *fmt, ...){ va_list args; va_start(args, fmt); String8 string = push_str8fv(arena, fmt, args); String8Node *result = str8_list_push(arena, list, string); va_end(args); return(result); } internal String8Node* str8_list_push_frontf(Arena *arena, String8List *list, char *fmt, ...){ va_list args; va_start(args, fmt); String8 string = push_str8fv(arena, fmt, args); String8Node *result = str8_list_push_front(arena, list, string); va_end(args); return(result); } internal String8List str8_list_copy(Arena *arena, String8List *list){ String8List result = {0}; for (String8Node *node = list->first; node != 0; node = node->next){ String8Node *new_node = push_array_no_zero(arena, String8Node, 1); String8 new_string = push_str8_copy(arena, node->string); str8_list_push_node_set_string(&result, new_node, new_string); } return(result); } internal String8List str8_split(Arena *arena, String8 string, U8 *split_chars, U64 split_char_count, StringSplitFlags flags){ String8List list = {0}; B32 keep_empties = (flags & StringSplitFlag_KeepEmpties); U8 *ptr = string.str; U8 *opl = string.str + string.size; for (;ptr < opl;){ U8 *first = ptr; for (;ptr < opl; ptr += 1){ U8 c = *ptr; B32 is_split = 0; for (U64 i = 0; i < split_char_count; i += 1){ if (split_chars[i] == c){ is_split = 1; break; } } if (is_split){ break; } } String8 string = str8_range(first, ptr); if (keep_empties || string.size > 0){ str8_list_push(arena, &list, string); } ptr += 1; } return(list); } internal String8List str8_split_by_string_chars(Arena *arena, String8 string, String8 split_chars, StringSplitFlags flags){ String8List list = str8_split(arena, string, split_chars.str, split_chars.size, flags); return list; } internal String8List str8_list_split_by_string_chars(Arena *arena, String8List list, String8 split_chars, StringSplitFlags flags){ String8List result = {0}; for (String8Node *node = list.first; node != 0; node = node->next){ String8List split = str8_split_by_string_chars(arena, node->string, split_chars, flags); str8_list_concat_in_place(&result, &split); } return result; } internal String8 str8_list_join(Arena *arena, String8List *list, StringJoin *optional_params){ StringJoin join = {0}; if (optional_params != 0){ MemoryCopyStruct(&join, optional_params); } U64 sep_count = 0; if (list->node_count > 0){ sep_count = list->node_count - 1; } String8 result; result.size = join.pre.size + join.post.size + sep_count*join.sep.size + list->total_size; U8 *ptr = result.str = push_array_no_zero(arena, U8, result.size + 1); MemoryCopy(ptr, join.pre.str, join.pre.size); ptr += join.pre.size; for (String8Node *node = list->first; node != 0; node = node->next){ MemoryCopy(ptr, node->string.str, node->string.size); ptr += node->string.size; if (node->next != 0){ MemoryCopy(ptr, join.sep.str, join.sep.size); ptr += join.sep.size; } } MemoryCopy(ptr, join.post.str, join.post.size); ptr += join.post.size; *ptr = 0; return(result); } internal void str8_list_from_flags(Arena *arena, String8List *list, U32 flags, String8 *flag_string_table, U32 flag_string_count){ for (U32 i = 0; i < flag_string_count; i += 1){ U32 flag = (1 << i); if (flags & flag){ str8_list_push(arena, list, flag_string_table[i]); } } } //////////////////////////////// //~ rjf; String Arrays internal String8Array str8_array_from_list(Arena *arena, String8List *list) { String8Array array; array.count = list->node_count; array.v = push_array_no_zero(arena, String8, array.count); U64 idx = 0; for(String8Node *n = list->first; n != 0; n = n->next, idx += 1) { array.v[idx] = n->string; } return array; } internal String8Array str8_array_reserve(Arena *arena, U64 count) { String8Array arr; arr.count = 0; arr.v = push_array(arena, String8, count); return arr; } //////////////////////////////// //~ rjf: String Path Helpers internal String8 str8_chop_last_slash(String8 string){ if (string.size > 0){ U8 *ptr = string.str + string.size - 1; for (;ptr >= string.str; ptr -= 1){ if (*ptr == '/' || *ptr == '\\'){ break; } } if (ptr >= string.str){ string.size = (U64)(ptr - string.str); } else{ string.size = 0; } } return(string); } internal String8 str8_skip_last_slash(String8 string){ if (string.size > 0){ U8 *ptr = string.str + string.size - 1; for (;ptr >= string.str; ptr -= 1){ if (*ptr == '/' || *ptr == '\\'){ break; } } if (ptr >= string.str){ ptr += 1; string.size = (U64)(string.str + string.size - ptr); string.str = ptr; } } return(string); } internal String8 str8_chop_last_dot(String8 string) { String8 result = string; U64 p = string.size; for (;p > 0;){ p -= 1; if (string.str[p] == '.'){ result = str8_prefix(string, p); break; } } return(result); } internal String8 str8_skip_last_dot(String8 string){ String8 result = string; U64 p = string.size; for (;p > 0;){ p -= 1; if (string.str[p] == '.'){ result = str8_skip(string, p + 1); break; } } return(result); } internal PathStyle path_style_from_str8(String8 string){ PathStyle result = PathStyle_Relative; if (string.size >= 1 && string.str[0] == '/'){ result = PathStyle_UnixAbsolute; } else if (string.size >= 2 && char_is_alpha(string.str[0]) && string.str[1] == ':'){ if (string.size == 2 || char_is_slash(string.str[2])){ result = PathStyle_WindowsAbsolute; } } return(result); } internal String8List str8_split_path(Arena *arena, String8 string){ String8List result = str8_split(arena, string, (U8*)"/\\", 2, 0); return(result); } internal void str8_path_list_resolve_dots_in_place(String8List *path, PathStyle style){ Temp scratch = scratch_begin(0, 0); String8MetaNode *stack = 0; String8MetaNode *free_meta_node = 0; String8Node *first = path->first; MemoryZeroStruct(path); for (String8Node *node = first, *next = 0; node != 0; node = next){ // save next now next = node->next; // cases: if (node == first && style == PathStyle_WindowsAbsolute){ goto save_without_stack; } if (node->string.size == 1 && node->string.str[0] == '.'){ goto do_nothing; } if (node->string.size == 2 && node->string.str[0] == '.' && node->string.str[1] == '.'){ if (stack != 0){ goto eliminate_stack_top; } else{ goto save_without_stack; } } goto save_with_stack; // handlers: save_with_stack: { str8_list_push_node(path, node); String8MetaNode *stack_node = free_meta_node; if (stack_node != 0){ SLLStackPop(free_meta_node); } else{ stack_node = push_array_no_zero(scratch.arena, String8MetaNode, 1); } SLLStackPush(stack, stack_node); stack_node->node = node; continue; } save_without_stack: { str8_list_push_node(path, node); continue; } eliminate_stack_top: { path->node_count -= 1; path->total_size -= stack->node->string.size; SLLStackPop(stack); if (stack == 0){ path->last = path->first; } else{ path->last = stack->node; } continue; } do_nothing: continue; } scratch_end(scratch); } internal String8 str8_path_list_join_by_style(Arena *arena, String8List *path, PathStyle style){ StringJoin params = {0}; switch (style){ case PathStyle_Relative: case PathStyle_WindowsAbsolute: { params.sep = str8_lit("/"); }break; case PathStyle_UnixAbsolute: { params.pre = str8_lit("/"); params.sep = str8_lit("/"); }break; } String8 result = str8_list_join(arena, path, ¶ms); return(result); } internal String8TxtPtPair str8_txt_pt_pair_from_string(String8 string) { String8TxtPtPair pair = {0}; { String8 file_part = {0}; String8 line_part = {0}; String8 col_part = {0}; // rjf: grab file part for(U64 idx = 0; idx <= string.size; idx += 1) { U8 byte = (idx < string.size) ? (string.str[idx]) : 0; U8 next_byte = ((idx+1 < string.size) ? (string.str[idx+1]) : 0); if(byte == ':' && next_byte != '/' && next_byte != '\\') { file_part = str8_prefix(string, idx); line_part = str8_skip(string, idx+1); break; } else if(byte == 0) { file_part = string; break; } } // rjf: grab line/column { U64 colon_pos = str8_find_needle(line_part, 0, str8_lit(":"), 0); if(colon_pos < line_part.size) { col_part = str8_skip(line_part, colon_pos+1); line_part = str8_prefix(line_part, colon_pos); } } // rjf: convert line/column strings to numerics U64 line = 0; U64 column = 0; try_u64_from_str8_c_rules(line_part, &line); try_u64_from_str8_c_rules(col_part, &column); // rjf: fill pair.string = file_part; pair.pt = txt_pt((S64)line, (S64)column); if(pair.pt.line == 0) { pair.pt.line = 1; } if(pair.pt.column == 0) { pair.pt.column = 1; } } return pair; } //////////////////////////////// //~ rjf: UTF-8 & UTF-16 Decoding/Encoding read_only global U8 utf8_class[32] = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,2,2,2,2,3,3,4,5, }; internal UnicodeDecode utf8_decode(U8 *str, U64 max){ UnicodeDecode result = {1, max_U32}; U8 byte = str[0]; U8 byte_class = utf8_class[byte >> 3]; switch (byte_class) { case 1: { result.codepoint = byte; }break; case 2: { if (2 < max) { U8 cont_byte = str[1]; if (utf8_class[cont_byte >> 3] == 0) { result.codepoint = (byte & bitmask5) << 6; result.codepoint |= (cont_byte & bitmask6); result.inc = 2; } } }break; case 3: { if (2 < max) { U8 cont_byte[2] = {str[1], str[2]}; if (utf8_class[cont_byte[0] >> 3] == 0 && utf8_class[cont_byte[1] >> 3] == 0) { result.codepoint = (byte & bitmask4) << 12; result.codepoint |= ((cont_byte[0] & bitmask6) << 6); result.codepoint |= (cont_byte[1] & bitmask6); result.inc = 3; } } }break; case 4: { if (3 < max) { U8 cont_byte[3] = {str[1], str[2], str[3]}; if (utf8_class[cont_byte[0] >> 3] == 0 && utf8_class[cont_byte[1] >> 3] == 0 && utf8_class[cont_byte[2] >> 3] == 0) { result.codepoint = (byte & bitmask3) << 18; result.codepoint |= ((cont_byte[0] & bitmask6) << 12); result.codepoint |= ((cont_byte[1] & bitmask6) << 6); result.codepoint |= (cont_byte[2] & bitmask6); result.inc = 4; } } } } return(result); } internal UnicodeDecode utf16_decode(U16 *str, U64 max){ UnicodeDecode result = {1, max_U32}; result.codepoint = str[0]; result.inc = 1; if (max > 1 && 0xD800 <= str[0] && str[0] < 0xDC00 && 0xDC00 <= str[1] && str[1] < 0xE000){ result.codepoint = ((str[0] - 0xD800) << 10) | ((str[1] - 0xDC00) + 0x10000); result.inc = 2; } return(result); } internal U32 utf8_encode(U8 *str, U32 codepoint){ U32 inc = 0; if (codepoint <= 0x7F){ str[0] = (U8)codepoint; inc = 1; } else if (codepoint <= 0x7FF){ str[0] = (bitmask2 << 6) | ((codepoint >> 6) & bitmask5); str[1] = bit8 | (codepoint & bitmask6); inc = 2; } else if (codepoint <= 0xFFFF){ str[0] = (bitmask3 << 5) | ((codepoint >> 12) & bitmask4); str[1] = bit8 | ((codepoint >> 6) & bitmask6); str[2] = bit8 | ( codepoint & bitmask6); inc = 3; } else if (codepoint <= 0x10FFFF){ str[0] = (bitmask4 << 4) | ((codepoint >> 18) & bitmask3); str[1] = bit8 | ((codepoint >> 12) & bitmask6); str[2] = bit8 | ((codepoint >> 6) & bitmask6); str[3] = bit8 | ( codepoint & bitmask6); inc = 4; } else{ str[0] = '?'; inc = 1; } return(inc); } internal U32 utf16_encode(U16 *str, U32 codepoint){ U32 inc = 1; if (codepoint == max_U32){ str[0] = (U16)'?'; } else if (codepoint < 0x10000){ str[0] = (U16)codepoint; } else{ U32 v = codepoint - 0x10000; str[0] = safe_cast_u16(0xD800 + (v >> 10)); str[1] = safe_cast_u16(0xDC00 + (v & bitmask10)); inc = 2; } return(inc); } internal U32 utf8_from_utf32_single(U8 *buffer, U32 character){ return(utf8_encode(buffer, character)); } //////////////////////////////// //~ rjf: Unicode String Conversions internal String8 str8_from_16(Arena *arena, String16 in){ U64 cap = in.size*3; U8 *str = push_array_no_zero(arena, U8, cap + 1); U16 *ptr = in.str; U16 *opl = ptr + in.size; U64 size = 0; UnicodeDecode consume; for (;ptr < opl; ptr += consume.inc){ consume = utf16_decode(ptr, opl - ptr); size += utf8_encode(str + size, consume.codepoint); } str[size] = 0; arena_pop(arena, (cap - size)); return(str8(str, size)); } internal String16 str16_from_8(Arena *arena, String8 in){ U64 cap = in.size*2; U16 *str = push_array_no_zero(arena, U16, cap + 1); U8 *ptr = in.str; U8 *opl = ptr + in.size; U64 size = 0; UnicodeDecode consume; for (;ptr < opl; ptr += consume.inc){ consume = utf8_decode(ptr, opl - ptr); size += utf16_encode(str + size, consume.codepoint); } str[size] = 0; arena_pop(arena, (cap - size)*2); return(str16(str, size)); } internal String8 str8_from_32(Arena *arena, String32 in){ U64 cap = in.size*4; U8 *str = push_array_no_zero(arena, U8, cap + 1); U32 *ptr = in.str; U32 *opl = ptr + in.size; U64 size = 0; for (;ptr < opl; ptr += 1){ size += utf8_encode(str + size, *ptr); } str[size] = 0; arena_pop(arena, (cap - size)); return(str8(str, size)); } internal String32 str32_from_8(Arena *arena, String8 in){ U64 cap = in.size; U32 *str = push_array_no_zero(arena, U32, cap + 1); U8 *ptr = in.str; U8 *opl = ptr + in.size; U64 size = 0; UnicodeDecode consume; for (;ptr < opl; ptr += consume.inc){ consume = utf8_decode(ptr, opl - ptr); str[size] = consume.codepoint; size += 1; } str[size] = 0; arena_pop(arena, (cap - size)*4); return(str32(str, size)); } //////////////////////////////// //~ rjf: Basic Types & Space Enum -> String Conversions internal String8 string_from_dimension(Dimension dimension){ local_persist String8 strings[] = { str8_lit_comp("X"), str8_lit_comp("Y"), str8_lit_comp("Z"), str8_lit_comp("W"), }; String8 result = str8_lit("error"); if ((U32)dimension < 4){ result = strings[dimension]; } return(result); } internal String8 string_from_side(Side side){ local_persist String8 strings[] = { str8_lit_comp("Min"), str8_lit_comp("Max"), }; String8 result = str8_lit("error"); if ((U32)side < 2){ result = strings[side]; } return(result); } internal String8 string_from_operating_system(OperatingSystem os){ local_persist String8 strings[] = { str8_lit_comp("Null"), str8_lit_comp("Windows"), str8_lit_comp("Linux"), str8_lit_comp("Mac"), }; String8 result = str8_lit("error"); if (os < OperatingSystem_COUNT){ result = strings[os]; } return(result); } internal String8 string_from_architecture(Architecture arch){ local_persist String8 strings[] = { str8_lit_comp("Null"), str8_lit_comp("x64"), str8_lit_comp("x86"), str8_lit_comp("arm64"), str8_lit_comp("arm32"), }; String8 result = str8_lit("error"); if (arch < Architecture_COUNT){ result = strings[arch]; } return(result); } //////////////////////////////// //~ rjf: Time Types -> String internal String8 string_from_week_day(WeekDay week_day){ local_persist String8 strings[] = { str8_lit_comp("Sun"), str8_lit_comp("Mon"), str8_lit_comp("Tue"), str8_lit_comp("Wed"), str8_lit_comp("Thu"), str8_lit_comp("Fri"), str8_lit_comp("Sat"), }; String8 result = str8_lit("Err"); if ((U32)week_day < WeekDay_COUNT){ result = strings[week_day]; } return(result); } internal String8 string_from_month(Month month){ local_persist String8 strings[] = { str8_lit_comp("Jan"), str8_lit_comp("Feb"), str8_lit_comp("Mar"), str8_lit_comp("Apr"), str8_lit_comp("May"), str8_lit_comp("Jun"), str8_lit_comp("Jul"), str8_lit_comp("Aug"), str8_lit_comp("Sep"), str8_lit_comp("Oct"), str8_lit_comp("Nov"), str8_lit_comp("Dec"), }; String8 result = str8_lit("Err"); if ((U32)month < Month_COUNT){ result = strings[month]; } return(result); } internal String8 push_date_time_string(Arena *arena, DateTime *date_time){ char *mon_str = (char*)string_from_month(date_time->month).str; U32 adjusted_hour = date_time->hour%12; if (adjusted_hour == 0){ adjusted_hour = 12; } char *ampm = "am"; if (date_time->hour >= 12){ ampm = "pm"; } String8 result = push_str8f(arena, "%d %s %d, %02d:%02d:%02d %s", date_time->day, mon_str, date_time->year, adjusted_hour, date_time->min, date_time->sec, ampm); return(result); } internal String8 push_file_name_date_time_string(Arena *arena, DateTime *date_time){ char *mon_str = (char*)string_from_month(date_time->month).str; String8 result = push_str8f(arena, "%d-%s-%0d--%02d-%02d-%02d", date_time->year, mon_str, date_time->day, date_time->hour, date_time->min, date_time->sec); return(result); } internal String8 string_from_elapsed_time(Arena *arena, DateTime dt){ Temp scratch = scratch_begin(&arena, 1); String8List list = {0}; if (dt.year){ str8_list_pushf(scratch.arena, &list, "%dy", dt.year); str8_list_pushf(scratch.arena, &list, "%um", dt.mon); str8_list_pushf(scratch.arena, &list, "%ud", dt.day); } else if (dt.mon){ str8_list_pushf(scratch.arena, &list, "%um", dt.mon); str8_list_pushf(scratch.arena, &list, "%ud", dt.day); } else if (dt.day){ str8_list_pushf(scratch.arena, &list, "%ud", dt.day); } str8_list_pushf(scratch.arena, &list, "%u:%u:%u:%u ms", dt.hour, dt.min, dt.sec, dt.msec); StringJoin join = { str8_lit_comp(""), str8_lit_comp(" "), str8_lit_comp("") }; String8 result = str8_list_join(arena, &list, &join); scratch_end(scratch); return(result); } //////////////////////////////// //~ rjf: Basic Text Indentation internal String8 indented_from_string(Arena *arena, String8 string) { Temp scratch = scratch_begin(&arena, 1); read_only local_persist U8 indentation_bytes[] = " "; String8List indented_strings = {0}; S64 depth = 0; S64 next_depth = 0; U64 line_begin_off = 0; for(U64 off = 0; off <= string.size; off += 1) { U8 byte = off width_this_line){ String8 line = str8_substr(string, line_range); if (wrapped_indent_level > 0){ line = push_str8f(arena, "%.*s%S", wrapped_indent_level, spaces, line); } str8_list_push(arena, &list, line); line_range = r1u64(line_range.max+1, candidate_line_range.max); wrapped_indent_level = ClampTop(64, wrap_indent); } else{ line_range = candidate_line_range; } } } if (line_range.min < string.size && line_range.max > line_range.min){ String8 line = str8_substr(string, line_range); if (wrapped_indent_level > 0){ line = push_str8f(arena, "%.*s%S", wrapped_indent_level, spaces, line); } str8_list_push(arena, &list, line); } return list; } //////////////////////////////// //~ rjf: String <-> Color internal String8 hex_string_from_rgba_4f32(Arena *arena, Vec4F32 rgba) { String8 hex_string = push_str8f(arena, "%02x%02x%02x%02x", (U8)(rgba.x*255.f), (U8)(rgba.y*255.f), (U8)(rgba.z*255.f), (U8)(rgba.w*255.f)); return hex_string; } internal Vec4F32 rgba_from_hex_string_4f32(String8 hex_string) { U8 byte_text[8] = {0}; U64 byte_text_idx = 0; for(U64 idx = 0; idx < hex_string.size && byte_text_idx < ArrayCount(byte_text); idx += 1) { if(char_is_digit(hex_string.str[idx], 16)) { byte_text[byte_text_idx] = char_to_lower(hex_string.str[idx]); byte_text_idx += 1; } } U8 byte_vals[4] = {0}; for(U64 idx = 0; idx < 4; idx += 1) { byte_vals[idx] = (U8)u64_from_str8(str8(&byte_text[idx*2], 2), 16); } Vec4F32 rgba = v4f32(byte_vals[0]/255.f, byte_vals[1]/255.f, byte_vals[2]/255.f, byte_vals[3]/255.f); return rgba; } //////////////////////////////// //~ rjf: String Fuzzy Matching internal FuzzyMatchRangeList fuzzy_match_find(Arena *arena, String8 needle, String8 haystack) { FuzzyMatchRangeList result = {0}; Temp scratch = scratch_begin(&arena, 1); String8List needles = str8_split(scratch.arena, needle, (U8*)" ", 1, 0); result.needle_part_count = needles.node_count; for(String8Node *needle_n = needles.first; needle_n != 0; needle_n = needle_n->next) { U64 find_pos = 0; for(;find_pos < haystack.size;) { find_pos = str8_find_needle(haystack, find_pos, needle_n->string, StringMatchFlag_CaseInsensitive); B32 is_in_gathered_ranges = 0; for(FuzzyMatchRangeNode *n = result.first; n != 0; n = n->next) { if(n->range.min <= find_pos && find_pos < n->range.max) { is_in_gathered_ranges = 1; find_pos = n->range.max; break; } } if(!is_in_gathered_ranges) { break; } } if(find_pos < haystack.size) { Rng1U64 range = r1u64(find_pos, find_pos+needle_n->string.size); FuzzyMatchRangeNode *n = push_array(arena, FuzzyMatchRangeNode, 1); n->range = range; SLLQueuePush(result.first, result.last, n); result.count += 1; result.total_dim += dim_1u64(range); } } scratch_end(scratch); return result; } internal FuzzyMatchRangeList fuzzy_match_range_list_copy(Arena *arena, FuzzyMatchRangeList *src) { FuzzyMatchRangeList dst = {0}; for(FuzzyMatchRangeNode *src_n = src->first; src_n != 0; src_n = src_n->next) { FuzzyMatchRangeNode *dst_n = push_array(arena, FuzzyMatchRangeNode, 1); SLLQueuePush(dst.first, dst.last, dst_n); dst_n->range = src_n->range; } dst.count = src->count; dst.needle_part_count = src->needle_part_count; dst.total_dim = src->total_dim; return dst; } //////////////////////////////// //~ NOTE(allen): Serialization Helpers internal void str8_serial_begin(Arena *arena, String8List *srl){ String8Node *node = push_array(arena, String8Node, 1); node->string.str = push_array_no_zero(arena, U8, 0); srl->first = srl->last = node; srl->node_count = 1; srl->total_size = 0; } internal String8 str8_serial_end(Arena *arena, String8List *srl){ U64 size = srl->total_size; U8 *out = push_array_no_zero(arena, U8, size); str8_serial_write_to_dst(srl, out); String8 result = str8(out, size); return result; } internal void str8_serial_write_to_dst(String8List *srl, void *out){ U8 *ptr = (U8*)out; for (String8Node *node = srl->first; node != 0; node = node->next){ U64 size = node->string.size; MemoryCopy(ptr, node->string.str, size); ptr += size; } } internal U64 str8_serial_push_align(Arena *arena, String8List *srl, U64 align){ Assert(IsPow2(align)); U64 pos = srl->total_size; U64 new_pos = AlignPow2(pos, align); U64 size = (new_pos - pos); if(size != 0) { U8 *buf = push_array(arena, U8, size); String8 *str = &srl->last->string; if (str->str + str->size == buf){ srl->last->string.size += size; srl->total_size += size; } else{ str8_list_push(arena, srl, str8(buf, size)); } } return size; } internal void * str8_serial_push_size(Arena *arena, String8List *srl, U64 size) { void *result = 0; if(size != 0) { U8 *buf = push_array_no_zero(arena, U8, size); String8 *str = &srl->last->string; if (str->str + str->size == buf){ srl->last->string.size += size; srl->total_size += size; } else{ str8_list_push(arena, srl, str8(buf, size)); } result = buf; } return result; } internal void * str8_serial_push_data(Arena *arena, String8List *srl, void *data, U64 size){ void *result = str8_serial_push_size(arena, srl, size); if(result != 0) { MemoryCopy(result, data, size); } return result; } internal void str8_serial_push_data_list(Arena *arena, String8List *srl, String8Node *first){ for (String8Node *node = first; node != 0; node = node->next){ str8_serial_push_data(arena, srl, node->string.str, node->string.size); } } internal void str8_serial_push_u64(Arena *arena, String8List *srl, U64 x){ U8 *buf = push_array_no_zero(arena, U8, 8); MemoryCopy(buf, &x, 8); String8 *str = &srl->last->string; if (str->str + str->size == buf){ srl->last->string.size += 8; srl->total_size += 8; } else{ str8_list_push(arena, srl, str8(buf, 8)); } } internal void str8_serial_push_u32(Arena *arena, String8List *srl, U32 x){ U8 *buf = push_array_no_zero(arena, U8, 4); MemoryCopy(buf, &x, 4); String8 *str = &srl->last->string; if (str->str + str->size == buf){ srl->last->string.size += 4; srl->total_size += 4; } else{ str8_list_push(arena, srl, str8(buf, 4)); } } internal void str8_serial_push_u16(Arena *arena, String8List *srl, U16 x){ str8_serial_push_data(arena, srl, &x, sizeof(x)); } internal void str8_serial_push_u8(Arena *arena, String8List *srl, U8 x){ str8_serial_push_data(arena, srl, &x, sizeof(x)); } internal void str8_serial_push_cstr(Arena *arena, String8List *srl, String8 str){ str8_serial_push_data(arena, srl, str.str, str.size); str8_serial_push_u8(arena, srl, 0); } internal void str8_serial_push_string(Arena *arena, String8List *srl, String8 str){ str8_serial_push_data(arena, srl, str.str, str.size); } //////////////////////////////// //~ rjf: Deserialization Helpers internal U64 str8_deserial_read(String8 string, U64 off, void *read_dst, U64 read_size, U64 granularity) { U64 bytes_left = string.size-Min(off, string.size); U64 actually_readable_size = Min(bytes_left, read_size); U64 legally_readable_size = actually_readable_size - actually_readable_size%granularity; if(legally_readable_size > 0) { MemoryCopy(read_dst, string.str+off, legally_readable_size); } return legally_readable_size; } internal U64 str8_deserial_find_first_match(String8 string, U64 off, U16 scan_val) { U64 cursor = off; for (;;) { U16 val = 0; str8_deserial_read_struct(string, cursor, &val); if (val == scan_val) { break; } cursor += sizeof(val); } return cursor; } internal void * str8_deserial_get_raw_ptr(String8 string, U64 off, U64 size) { void *raw_ptr = 0; if (off + size <= string.size) { raw_ptr = string.str + off; } return raw_ptr; } internal U64 str8_deserial_read_cstr(String8 string, U64 off, String8 *cstr_out) { U64 cstr_size = 0; if (off < string.size) { U8 *ptr = string.str + off; U8 *cap = string.str + string.size; *cstr_out = str8_cstring_capped(ptr, cap); cstr_size = (cstr_out->size + 1); } return cstr_size; } internal U64 str8_deserial_read_windows_utf16_string16(String8 string, U64 off, String16 *str_out) { U64 null_off = str8_deserial_find_first_match(string, off, 0); U64 size = null_off - off; U16 *str = (U16 *)str8_deserial_get_raw_ptr(string, off, size); U64 count = size / sizeof(*str); *str_out = str16(str, count); U64 read_size_with_null = size + sizeof(*str); return read_size_with_null; } internal U64 str8_deserial_read_block(String8 string, U64 off, U64 size, String8 *block_out) { Rng1U64 range = rng_1u64(off, off + size); *block_out = str8_substr(string, range); return block_out->size; }