// #NOTE: All string building, printing and copying operations SHOULD null-terminate the // strings for backwards compatibility reasons. #FIX if something doesn't follow this rule! bool is_valid (string s) { return (s.data != nullptr && s.count > 0); } bool is_c_string (string s) { return (s.data && s.data[s.count] == '\0'); } u8* to_c_string (string s) { u8* result = (u8*)internal_alloc(s.count + 1); memcpy(result, s.data, s.count); result[s.count] = '\0'; return result; } string copy_string (string s) { // Assert(s.count > 0); if (s.count <= 0) return ""; string str = {}; str.count = s.count; str.data = (u8*)internal_alloc(s.count + 1); memcpy(str.data, s.data, s.count); str.data[str.count] = '\0'; // null-terminate for backwards compatibility? return str; } string copy_string (char* c_string) { string str = {}; s64 string_length = strlen(c_string); if (string_length == 0) return ""; str.data = NewArray(string_length + 1); memcpy(str.data, c_string, string_length); str.count = string_length; str.data[str.count] = '\0'; // null-terminate for backwards compatibility? return str; } string to_string (ArrayView str) { return {str.count, str.data}; } ArrayView to_view (string s) { return {s.count, s.data}; } void string_free (string& s) { internal_free(s.data); s.data = nullptr; s.count = 0; } force_inline string string_view (string s, s64 start_index, s64 view_count) { Assert(view_count >= 0); Assert(start_index >= 0); if (view_count < 0 || start_index < 0 || start_index >= s.count) return ""; s64 new_count = view_count; if (start_index + view_count > s.count) { new_count = s.count - start_index; } return { new_count, s.data + start_index }; } bool strings_match (string first_string, string second_string) { return (first_string == second_string); } // #Unicode string wide_to_utf8 (u16* source, s32 length) { if (length == 0) return { }; s32 query_result = WideCharToMultiByte(CP_UTF8, 0, (LPCWCH)source, length, nullptr, 0, nullptr, nullptr); if (query_result <= 0) return { }; // Make room for a null terminator: if (length != -1) { query_result += 1; } u8* memory = NewArray(query_result); string utf8_string; utf8_string.count = query_result - 1; // null terminator is not counted utf8_string.data = memory; s32 result = WideCharToMultiByte(CP_UTF8, 0, (LPCWCH)source, length, (LPSTR)memory, query_result, nullptr, nullptr); if (result <= 0) { internal_free(memory); return { }; } return utf8_string; } wstring utf8_to_wide (string source) { if (!source) return {}; s32 query_num_chars = MultiByteToWideChar(CP_UTF8, 0, (LPCCH)source.data, (s32)source.count, // @Robustness: Silent failure if too long. @Cleanup. nullptr, 0); if (query_num_chars <= 0) return {}; wstring name_u16s = wstring(query_num_chars); s32 result_num_chars = MultiByteToWideChar(CP_UTF8, 0, (LPCCH)source.data, (s32)source.count, // @Robustness: Silent failure if too long. @Cleanup. (LPWSTR)name_u16s.data, query_num_chars); if (!result_num_chars) { internal_free(name_u16s.data); return {}; } Assert(result_num_chars <= query_num_chars); name_u16s.data[result_num_chars] = 0; // null terminate return name_u16s; } string format_string_temp (char* format, ...) { // #sprint push_allocator(temp()); constexpr s64 BUFFER_SIZE = 4096; string str = {}; str.data = NewArray(BUFFER_SIZE); va_list args; va_start(args, format); // Note that this *is* null-terminated for compatibility. str.count = (s64)vsnprintf((char*)str.data, (size_t)BUFFER_SIZE, format, args); va_end(args); return str; } string format_string (char* format, ...) { // #sprint constexpr s64 BUFFER_SIZE = 4096; string str = {}; str.data = NewArray(BUFFER_SIZE); va_list args; va_start(args, format); // Note that this *is* null-terminated for compatibility. str.count = (s64)vsnprintf((char*)str.data, (size_t)BUFFER_SIZE, format, args); va_end(args); return str; } // #MemoryLeak #NoContext - memory will leak from this operation. string format_string_no_context (char* format, ...) { constexpr s64 BUFFER_SIZE = 4096; string str = {}; str.data = (u8*)GPAllocator_New(BUFFER_SIZE); va_list args; va_start(args, format); // Note that this *is* null-terminated for compatibility. str.count = (s64)vsnprintf((char*)str.data, (size_t)BUFFER_SIZE, format, args); va_end(args); return str; } force_inline String_Builder* new_string_builder (Arena_Reserve new_reserve) { return arena_array_new(1, new_reserve); } force_inline void append (String_Builder* sb, string s) { array_add(*sb, ArrayView(s.count, s.data)); } void append (String_Builder* sb, ArrayView strings) { s64 combined_length = 0; for (s64 i = 0; i < strings.count; i += 1) { combined_length += strings[i].count; } s64 final_length = sb->count + combined_length; if (sb->allocated < final_length) { array_reserve(*sb, final_length); } for (s64 i = 0; i < strings.count; i += 1) { string s = strings[i]; array_add(*sb, ArrayView(s.count, s.data)); } } force_inline void append_no_add (String_Builder* sb, string s) { array_add(*sb, ArrayView(s.count, s.data)); sb->count -= s.count; } // Unfortunately this follows the printf format, which is annoying. // I'd rather have something like fmt:: void print_to_builder_internal (String_Builder* sb, string format, va_list args) { s64 expected_final_count = max_array_size(*sb);// amount to reserve if (sb->allocated < expected_final_count) { array_reserve(*sb, expected_final_count); } s64 buffer_size = sb->allocated - sb->count; // available space u8* current_point = &sb->data[sb->count]; s64 print_count = (s64)vsnprintf((char*)current_point, (size_t)buffer_size, (char*)format.data, args); sb->count += print_count; } void print_to_builder (String_Builder* sb, string format, ...) { s64 expected_final_count = max_array_size(*sb); if (sb->allocated < expected_final_count) { array_reserve(*sb, expected_final_count); } s64 buffer_size = sb->allocated - sb->count; // available space u8* current_point = &sb->data[sb->count]; va_list args; va_start(args, format); s64 print_count = (s64)vsnprintf((char*)current_point, (size_t)buffer_size, (char*)format.data, args); va_end(args); sb->count += print_count; } string string_view (String_Builder* sb) { // should probably ensure final byte is null terminated... append_no_add(sb, "\0"); // doesn't increment sb.count return to_string(to_view(*sb)); } // for when we want to keep the string builder around and recycle the memory. internal force_inline void reset_string_builder (String_Builder* sb, bool keep_memory) { array_poison_range(*sb, 0, sb->count); if (keep_memory) { reset_keeping_memory(*sb); } else { array_reset(*sb); } } force_inline string builder_to_string (String_Builder* sb) { string final_string = copy_string(to_string(to_view(*sb))); free_string_builder(sb); return final_string; } internal force_inline void free_string_builder (String_Builder* sb) { arena_array_free(*sb); } char to_lower_ascii(char c) { if (c >= 'A' && c <= 'Z') c = c + ('a' - 'A'); // or c += 32; return c; } char to_upper_ascii(char c) { if (c >= 'a' && c <= 'z') c = c - ('a' - 'A'); // or c -= 32; return c; } // string to_lower_in_place (string s) { } // Input must be ascii or utf8! string to_lower_copy (string s_orig) { string s = copy_string(s_orig); for (s64 i = 0; i < s.count; i += 1) { s.data[i] = to_lower_ascii(s.data[i]); } return s; } #define format_cstring(fmt, ...) \ (char*)format_string(fmt, ##__VA_ARGS__).data bool is_any (u8 c, string chars) { for_each(i, chars) { if (chars.data[i] == c) return true; } return false; } string trim_right (string s, string chars, bool replace_with_zeros) { s64 count = s.count; for_each_reverse(i, s) { if (is_any(s.data[i], chars)) { if (replace_with_zeros) { s.data[i] = 0; } count -= 1; } else { break; } } return string_view(s, 0, count); }