// vim:fileencoding=utf-8:foldmethod=marker #include "str8.h" #include "../../../common/aliases/aliases.h" #include "../../../common/assert/assert.h" #include "../../mem_allocator/mem_allocator.h" #include #include #include #include #include #define STR8_BUF_ALLOC_SIZE(CAPACITY) (sizeof(Str8) + sizeof(c8) * CAPACITY) Str8 *wapp_str8_alloc_buf(const Allocator *allocator, u64 capacity) { wapp_debug_assert(allocator != NULL, "`allocator` should not be NULL"); Str8 *str = wapp_mem_allocator_alloc(allocator, STR8_BUF_ALLOC_SIZE(capacity)); if (!str) { goto RETURN_STR8; } str->buf = (u8 *)str + sizeof(Str8); str->size = 0; str->capacity = capacity; RETURN_STR8: return str; } Str8 *wapp_str8_alloc_and_fill_buf(const Allocator *allocator, u64 capacity) { Str8 *out = wapp_str8_alloc_buf(allocator, capacity); if (out) { memset(out->buf, 0, capacity); out->size = capacity; } return out; } Str8 *wapp_str8_alloc_cstr(const Allocator *allocator, const char *str) { wapp_debug_assert(allocator != NULL && str != NULL, "`allocator` and `str` should not be NULL"); u64 length = strlen(str); Str8 *output = wapp_str8_alloc_buf(allocator, length * 2); if (!output) { goto RETURN_ALLOC_CSTR; } output->size = length; memcpy(output->buf, str, length); RETURN_ALLOC_CSTR: return output; } Str8 *wapp_str8_alloc_str8(const Allocator *allocator, Str8RO *str) { wapp_debug_assert(allocator != NULL && str != NULL, "`allocator` and `str` should not be NULL"); Str8 *output = wapp_str8_alloc_buf(allocator, str->capacity); if (!output) { goto RETURN_ALLOC_STR8; } output->size = str->size; memcpy(output->buf, str->buf, str->size); RETURN_ALLOC_STR8: return output; } Str8 *wapp_str8_alloc_substr(const Allocator *allocator, Str8RO *str, u64 start, u64 end) { wapp_debug_assert(allocator != NULL && str != NULL, "`allocator` and `str` should not be NULL"); Str8 *output = NULL; if (start >= str->size || start >= end) { goto RETURN_ALLOC_SUBSTR; } if (end > str->size) { end = str->size; } output = wapp_str8_alloc_buf(allocator, str->capacity); if (!output) { goto RETURN_ALLOC_SUBSTR; } output->size = end - start; memcpy(output->buf, str->buf + start, output->size); RETURN_ALLOC_SUBSTR: return output; } void wapp_str8_dealloc_buf(const Allocator *allocator, Str8 **str) { wapp_debug_assert(allocator != NULL && str != NULL && (*str) != NULL, "Either `allocator` is NULL or `str` is an invalid double pointer"); wapp_mem_allocator_free(allocator, (void **)str, STR8_BUF_ALLOC_SIZE((*str)->capacity)); } c8 wapp_str8_get(const Str8 *str, u64 index) { if (index >= str->size) { return '\0'; } return str->buf[index]; } void wapp_str8_set(Str8 *str, u64 index, c8 c) { if (index >= str->size) { return; } str->buf[index] = c; } void wapp_str8_push_back(Str8 *str, c8 c) { if (!(str->size < str->capacity)) { return; } u64 index = (str->size)++; wapp_str8_set(str, index, c); } bool wapp_str8_equal(Str8RO *s1, Str8RO *s2) { if (s1->size != s2->size) { return false; } return wapp_str8_equal_to_count(s1, s2, s1->size); } bool wapp_str8_equal_to_count(Str8RO* s1, Str8RO* s2, u64 count) { if (!s1 || !s2) { return false; } return memcmp(s1->buf, s2->buf, count) == 0; } Str8 wapp_str8_slice(Str8RO *str, u64 start, u64 end) { if (start >= str->size || start >= end) { start = str->size; end = str->size; } if (end > str->size) { end = str->size; } return (Str8RO){ .capacity = end - start, .size = end - start, .buf = str->buf + start, }; } Str8 *wapp_str8_alloc_concat(const Allocator *allocator, Str8 *dst, Str8RO *src) { wapp_debug_assert(allocator != NULL && dst != NULL && src != NULL, "`allocator`, `dst` and `src` should not be NULL"); Str8 *output = NULL; u64 remaining = dst->capacity - dst->size; if (src->size <= remaining) { output = dst; goto SOURCE_STRING_STR8_CONCAT; } u64 capacity = dst->capacity + src->size; output = wapp_str8_alloc_buf(allocator, capacity); if (!output) { goto RETURN_STR8_CONCAT; } wapp_str8_concat_capped(output, dst); SOURCE_STRING_STR8_CONCAT: wapp_str8_concat_capped(output, src); RETURN_STR8_CONCAT: return output; } void wapp_str8_concat_capped(Str8 *dst, Str8RO *src) { wapp_debug_assert(dst != NULL && src != NULL, "`dst` and `src` should not be NULL"); u64 remaining = dst->capacity - dst->size; u64 to_copy = remaining < src->size ? remaining : src->size; memcpy(dst->buf + dst->size, src->buf, to_copy); dst->size += to_copy; } void wapp_str8_copy_cstr_capped(Str8 *dst, const char *src) { wapp_debug_assert(dst != NULL && src != NULL, "`dst` and `src` should not be NULL"); u64 length = strlen(src); u64 to_copy = length <= dst->capacity ? length : dst->capacity; memset(dst->buf, 0, dst->size); memcpy(dst->buf, src, to_copy); dst->size = to_copy; } void wapp_str8_copy_str8_capped(Str8 *dst, Str8RO *src) { wapp_debug_assert(dst != NULL && src != NULL, "`dst` and `src` should not be NULL"); u64 to_copy = src->size <= dst->capacity ? src->size : dst->capacity; memset(dst->buf, 0, dst->size); memcpy(dst->buf, src->buf, to_copy); dst->size = to_copy; } void wapp_str8_copy_to_cstr(char *dst, Str8RO *src, u64 dst_capacity) { wapp_debug_assert(dst != NULL && src != NULL, "`dst` and `src` should not be NULL"); u64 to_copy = src->size < dst_capacity ? src->size : dst_capacity - 1; memset(dst, 0, dst_capacity); memcpy(dst, src->buf, to_copy); } void wapp_str8_format(Str8 *dst, const char *format, ...) { wapp_debug_assert(dst != NULL && format != NULL, "`dst` and `format` should not be NULL"); va_list args1; va_list args2; va_start(args1, format); va_copy(args2, args1); u64 total_size = vsnprintf(NULL, 0, format, args1); dst->size = total_size <= dst->capacity ? total_size : dst->capacity; vsnprintf((char *)(dst->buf), dst->capacity, format, args2); va_end(args1); va_end(args2); } i64 wapp_str8_find(Str8RO *str, Str8RO substr) { if (!str || substr.size > str->size) { return -1; } // NOTE (Abdelrahman): Uses a while loop instead of a for loop to get rid of // MSVC Spectre mitigation warnings u64 char_index = 0; bool running = char_index < str->size; while (running) { const c8 *sub = str->buf + char_index; if (memcmp(sub, substr.buf, substr.size) == 0) { return char_index; } ++char_index; running = char_index < str->size; } return -1; } i64 wapp_str8_rfind(Str8RO *str, Str8RO substr) { if (!str || substr.size > str->size) { return -1; } // NOTE (Abdelrahman): Uses a while loop instead of a for loop to get rid of // MSVC Spectre mitigation warnings i64 char_index = str->size - substr.size; bool running = char_index >= 0; while (running) { const c8 *sub = str->buf + char_index; if (memcmp(sub, substr.buf, substr.size) == 0) { return char_index; } --char_index; running = char_index >= 0; } return -1; } Str8List *wapp_str8_split_with_max(const Allocator *allocator, Str8RO *str, Str8RO *delimiter, i64 max_splits) { wapp_debug_assert(allocator != NULL && str != NULL && delimiter != NULL, "`allocator`, `str` and `delimiter` should not be NULL"); Str8List *output = wapp_mem_allocator_alloc(allocator, sizeof(Str8List)); if (delimiter->size > str->size) { Str8 *full = wapp_str8_alloc_str8(allocator, str); Str8Node *node = wapp_mem_allocator_alloc(allocator, sizeof(Str8Node)); if (node) { node->item = full; wapp_str8_list_push_back(output, node); } goto RETURN_STR8_SPLIT; } i64 start = 0; i64 end = 0; i64 splits = 0; Str8 *rest = wapp_str8_alloc_str8(allocator, str); Str8 *before_str; while ((end = wapp_str8_find(rest, *delimiter)) != -1) { if (max_splits > 0 && splits >= max_splits) { break; } before_str = wapp_str8_alloc_substr(allocator, str, start, start + end); Str8Node *node = wapp_mem_allocator_alloc(allocator, sizeof(Str8Node)); if (node && before_str) { node->item = before_str; wapp_str8_list_push_back(output, node); } wapp_mem_allocator_free(allocator, (void **)&rest, sizeof(Str8)); rest = wapp_str8_alloc_substr(allocator, str, start + end + delimiter->size, str->size); start += end + delimiter->size; ++splits; } // Ensure the last part of the string after the delimiter is added to the list rest = wapp_str8_alloc_substr(allocator, str, start, str->size); Str8Node *node = wapp_mem_allocator_alloc(allocator, sizeof(Str8Node)); if (node && rest) { node->item = rest; wapp_str8_list_push_back(output, node); } RETURN_STR8_SPLIT: return output; } Str8List *wapp_str8_rsplit_with_max(const Allocator *allocator, Str8RO *str, Str8RO *delimiter, i64 max_splits) { wapp_debug_assert(allocator != NULL && str != NULL && delimiter != NULL, "`allocator`, `str` and `delimiter` should not be NULL"); Str8List *output = wapp_mem_allocator_alloc(allocator, sizeof(Str8List)); if (delimiter->size > str->size) { Str8 *full = wapp_str8_alloc_str8(allocator, str); Str8Node *node = wapp_mem_allocator_alloc(allocator, sizeof(Str8Node)); if (node && full) { node->item = full; wapp_str8_list_push_back(output, node); } goto RETURN_STR8_SPLIT; } i64 end = 0; i64 splits = 0; Str8 *rest = wapp_str8_alloc_str8(allocator, str); Str8 *after_str; while ((end = wapp_str8_rfind(rest, *delimiter)) != -1) { if (max_splits > 0 && splits >= max_splits) { break; } after_str = wapp_str8_alloc_substr(allocator, rest, end + delimiter->size, str->size); Str8Node *node = wapp_mem_allocator_alloc(allocator, sizeof(Str8Node)); if (node) { node->item = after_str; wapp_str8_list_push_front(output, node); } wapp_mem_allocator_free(allocator, (void **)&rest, sizeof(Str8)); rest = wapp_str8_alloc_substr(allocator, rest, 0, end); ++splits; } rest = wapp_str8_alloc_substr(allocator, str, 0, rest->size); Str8Node *node = wapp_mem_allocator_alloc(allocator, sizeof(Str8Node)); if (node && rest) { node->item = rest; wapp_str8_list_push_front(output, node); } RETURN_STR8_SPLIT: return output; } Str8 *wapp_str8_join(const Allocator *allocator, const Str8List *list, Str8RO *delimiter) { wapp_debug_assert(allocator != NULL && list != NULL && delimiter != NULL, "`allocator`, `list` and `delimiter` should not be NULL"); u64 capacity = wapp_str8_list_total_size(list) + (delimiter->size * (list->node_count - 1)); Str8 *output = wapp_str8_alloc_buf(allocator, capacity * 2); // NOTE (Abdelrahman): Uses a while loop instead of a for loop to get rid of // MSVC Spectre mitigation warnings Str8Node *node; u64 node_index = 0; bool running = node_index < list->node_count; while (running) { node = wapp_str8_list_get(list, node_index); if (!node) { break; } wapp_str8_concat_capped(output, node->item); // NOTE (Abdelrahman): Comparison extracted to variable to silence // MSVC Spectre mitigation warnings bool not_last = node_index + 1 < list->node_count; if (not_last) { wapp_str8_concat_capped(output, delimiter); } ++node_index; running = node_index < list->node_count; } return output; } u64 wapp_str8_list_total_size(const Str8List *list) { if (!list) { return 0; } // NOTE (Abdelrahman): Uses a while loop instead of a for loop to get rid of // MSVC Spectre mitigation warnings Str8Node* node; u64 node_index = 0; u64 output = 0; bool running = node_index < list->node_count; while (running) { node = wapp_str8_list_get(list, node_index); if (!node) { break; } output += node->item->size; ++node_index; running = node_index < list->node_count; } return output; }