CORE_Static U8 to_lower_case(U8 a) { if (a >= 'A' && a <= 'Z') a += 32; return a; } CORE_Static U8 to_upper_case(U8 a) { if (a >= 'a' && a <= 'z') a -= 32; return a; } CORE_Static U8 char_to_lower(U8 c) { if (c >= 'A' && c <= 'Z') c += 32; return c; } CORE_Static U8 char_to_upper(U8 c) { if (c >= 'a' && c <= 'z') c -= 32; return c; } CORE_Static B32 is_whitespace(U8 w) { bool result = w == '\n' || w == ' ' || w == '\t' || w == '\v' || w == '\r'; return result; } CORE_Static B32 is_alphabetic(U8 a) { if ((a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z')) { return true; } return false; } CORE_Static B32 is_number(U8 a) { B32 result = a >= '0' && a <= '9'; return result; } CORE_Static B32 is_alphanumeric(U8 a) { B32 result = is_number(a) || is_alphabetic(a); return result; } CORE_Static S64 string_len(char *string) { S64 len = 0; while (*string++ != 0) len++; return len; } CORE_Static String string_from_cstring(char *string) { String result; result.str = (U8 *)string; result.len = string_len(string); return result; } CORE_Static B32 string_compare(String a, String b, B32 ignore_case = false) { if (a.len != b.len) return false; for (S64 i = 0; i < a.len; i++) { U8 A = a.str[i]; U8 B = b.str[i]; if (ignore_case) { A = to_lower_case(A); B = to_lower_case(B); } if (A != B) return false; } return true; } CORE_Static B32 cstring_compare(char *a, char *b, B32 ignore_case = false) { return string_compare(string_from_cstring(a), string_from_cstring(b), ignore_case); } CORE_Static B32 operator==(String a, String b) { return string_compare(a, b); } CORE_Static String string_copy(Allocator *a, String string) { U8 *copy = allocate_array(a, U8, string.len + 1); memory_copy(copy, string.str, string.len); copy[string.len] = 0; return String{copy, string.len}; } CORE_Static String string_fmtv(Allocator *a, const char *str, va_list args1) { va_list args2; va_copy(args2, args1); S64 len = stbsp_vsnprintf(0, 0, str, args2); va_end(args2); char *result = allocate_array(a, char, len + 1); stbsp_vsnprintf(result, (int)(len + 1), str, args1); String res = {(U8 *)result, len}; return res; } #define STRING_FMT(alloc, str, result) \ va_list args1; \ va_start(args1, str); \ String result = string_fmtv(alloc, str, args1); \ va_end(args1) CORE_Static String string_fmt(Allocator *a, const char *str, ...) { STRING_FMT(a, str, result); return result; } //----------------------------------------------------------------------------- // String builder //----------------------------------------------------------------------------- struct String_Builder_Block { String_Builder_Block *next; S64 cap; S64 len; U8 data[0]; }; struct String_Builder { Allocator *allocator; String_Builder_Block *first_free; String_Builder_Block *first; String_Builder_Block *last; U64 di; void reset() { for (; first;) { auto *block = first; first = first->next; block->next = first_free; first_free = block; } last = 0; assert(!last && !first); } void push_block(size_t size) { String_Builder_Block *block = 0; if (first_free) { block = first_free; first_free = first_free->next; } else { block = (String_Builder_Block *)allocate_size(allocator, sizeof(String_Builder_Block) + size, false); } memory_zero(block, sizeof(String_Builder_Block) + 1); // Also clear first byte of character data block->cap = size; SLL_QUEUE_ADD(first, last, block); } void init(S64 size = 4096) { assert(allocator); push_block(size); } void append_data(void *data, S64 size) { if (first == 0) { init(); } S64 remaining_cap = last->cap - last->len; if (size > remaining_cap) { S64 new_block_size = max(last->cap * 2, size * 2); push_block(new_block_size); } U8 *write_address = last->data + last->len; last->len += size; memory_copy(write_address, data, size); } void addf(const char *str, ...) { if (first == 0) { init(); } va_list args, args2; va_start(args, str); retry : { String_Builder_Block *block = last; S64 block_size = block->cap - block->len; char *write_address = (char *)block->data + block->len; va_copy(args2, args); int written = stbsp_vsnprintf(write_address, (int)block_size, str, args2); va_end(args2); if (written > (block_size - 1)) { S64 new_block_size = max(4096, (written + 1) * 2); push_block(new_block_size); goto retry; } block->len += written; } va_end(args); di++; } }; CORE_Static String_Builder string_builder_make(Allocator *a, S64 first_block_size = 4096) { String_Builder sb = {a}; sb.init(first_block_size); return sb; } // @! Make string_flatten a method static String string_flatten(Allocator *a, String_Builder *b) { // @Note(Krzosa): Compute size to allocate S64 size = 1; For_Linked_List(b->first) { size += it->len; } String result = {}; result.str = (U8 *)allocate_size(a, size, false); // @Note(Krzosa): Copy the content of each block into the string For_Linked_List(b->first) { memory_copy(result.str + result.len, it->data, it->len); result.len += it->len; } result.str[result.len] = 0; return result; } //----------------------------------------------------------------------------- // String ops //----------------------------------------------------------------------------- CORE_Static void string_path_normalize(String s) { for (S64 i = 0; i < s.len; i++) { if (s.str[i] == '\\') s.str[i] = '/'; } } CORE_Static String string_make(char *str, S64 len) { String result; result.str = (U8 *)str; result.len = len; return result; } CORE_Static String string_make(U8 *str, S64 len) { return string_make((char *)str, len); } CORE_Static String string_chop(String string, S64 len) { len = clamp_top(len, string.len); String result = string_make(string.str, string.len - len); return result; } CORE_Static String string_skip(String string, S64 len) { len = clamp_top(len, string.len); S64 remain = string.len - len; String result = string_make(string.str + len, remain); return result; } CORE_Static String string_get_postfix(String string, S64 len) { len = clamp_top(len, string.len); S64 remain_len = string.len - len; String result = string_make(string.str + remain_len, len); return result; } CORE_Static String string_get_prefix(String string, S64 len) { len = clamp_top(len, string.len); String result = string_make(string.str, len); return result; } CORE_Static String string_slice(String string, S64 first_index, S64 one_past_last_index) { assert_message(first_index < one_past_last_index, "string_slice, first_index is bigger then one_past_last_index"); assert_message(string.len > 0, "Slicing string of length 0! Might be an error!"); String result = string; if (string.len > 0) { if (one_past_last_index > first_index) { first_index = clamp_top(first_index, string.len - 1); one_past_last_index = clamp_top(one_past_last_index, string.len); result.str += first_index; result.len = one_past_last_index - first_index; } else { result.len = 0; } } return result; } CORE_Static String string_trim(String string) { if (string.len == 0) return string; S64 whitespace_begin = 0; for (; whitespace_begin < string.len; whitespace_begin++) { if (!is_whitespace(string.str[whitespace_begin])) { break; } } S64 whitespace_end = string.len; for (; whitespace_end != whitespace_begin; whitespace_end--) { if (!is_whitespace(string.str[whitespace_end - 1])) { break; } } if (whitespace_begin == whitespace_end) { string.len = 0; } else { string = string_slice(string, whitespace_begin, whitespace_end); } return string; } CORE_Static String string_trim_end(String string) { S64 whitespace_end = string.len; for (; whitespace_end != 0; whitespace_end--) { if (!is_whitespace(string.str[whitespace_end - 1])) { break; } } String result = string_get_prefix(string, whitespace_end); return result; } CORE_Static String string_to_lower_case(Allocator *arena, String s) { String copy = string_copy(arena, s); for (S64 i = 0; i < copy.len; i++) { copy.str[i] = to_lower_case(copy.str[i]); } return copy; } CORE_Static String string_to_upper_case(Allocator *arena, String s) { String copy = string_copy(arena, s); for (S64 i = 0; i < copy.len; i++) { copy.str[i] = to_upper_case(copy.str[i]); } return copy; } typedef U32 MatchFlag; enum { MatchFlag_None = 0, MatchFlag_FindLast = 1, MatchFlag_IgnoreCase = 2, }; CORE_Static B32 string_find(String string, String find, MatchFlag flags, S64 *index_out) { B32 result = false; if (flags & MatchFlag_FindLast) { for (S64 i = string.len; i != 0; i--) { S64 index = i - 1; String substring = string_slice(string, index, index + find.len); if (string_compare(substring, find, flags & MatchFlag_IgnoreCase)) { if (index_out) *index_out = index; result = true; break; } } } else { for (S64 i = 0; i < string.len; i++) { String substring = string_slice(string, i, i + find.len); if (string_compare(substring, find, flags & MatchFlag_IgnoreCase)) { if (index_out) *index_out = i; result = true; break; } } } return result; } CORE_Static String string_chop_last_slash(String s) { String result = s; string_find(s, "/"_s, MatchFlag_FindLast, &result.len); return result; } CORE_Static String string_chop_last_period(String s) { String result = s; string_find(s, "."_s, MatchFlag_FindLast, &result.len); return result; } CORE_Static String string_skip_to_last_slash(String s) { S64 pos; String result = s; if (string_find(s, "/"_s, MatchFlag_FindLast, &pos)) { result = string_skip(result, pos + 1); } return result; } CORE_Static String string_skip_to_last_period(String s) { S64 pos; String result = s; if (string_find(s, "."_s, MatchFlag_FindLast, &pos)) { result = string_skip(result, pos + 1); } return result; } struct String_Replace { String find; String replace; }; CORE_Static String string_replace(Arena *scratch, Allocator *allocator, String string, Array pairs) { Scoped_Arena _scope(scratch); String_Builder builder = {scratch}; for (S64 i = 0; i < string.len; i++) { For(pairs) { String current = string_skip(string, i); current = string_get_prefix(current, it.find.len); if (current == it.find) { builder.append_data(it.replace.str, it.replace.len); i += it.find.len; continue; } } builder.append_data(string.str + i, 1); } return string_flatten(allocator, &builder); }