#include "str8.h" #include "aliases.h" #include "mem_allocator.h" #include #include #include #include #include #define STR8_BUF_ALLOC_SIZE(CAPACITY) (sizeof(Str8) + sizeof(c8) * CAPACITY) internal Str8List node_to_list(Str8Node *node); Str8 *wapp_str8_alloc_buf(const Allocator *allocator, u64 capacity) { Str8 *str = NULL; if (!allocator) { goto RETURN_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_cstr(const Allocator *allocator, const char *str) { Str8 *output = NULL; if (!allocator || !str) { goto RETURN_ALLOC_CSTR; } u64 length = strlen(str); 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) { Str8 *output = NULL; if (!allocator || !str) { goto RETURN_ALLOC_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) { Str8 *output = NULL; if (!allocator || !str) { goto RETURN_ALLOC_SUBSTR; } 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) { if (!allocator || !str || !(*str)) { return; } 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) { if (!allocator || !dst || !src) { return NULL; } Str8 *output = NULL; u64 remaining = dst->capacity - dst->size; if (src->size <= remaining) { output = dst; goto COPY_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); COPY_STRING_STR8_CONCAT: wapp_str8_concat_capped(output, src); RETURN_STR8_CONCAT: return output; } void wapp_str8_concat_capped(Str8 *dst, Str8RO *src) { if (!dst || !src) { return; } 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) { if (!dst || !src) { return; } 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) { if (!dst || !src) { return; } 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) { if (!dst || !src) { return; } 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, ...) { if (!dst || !format) { return; } 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 = true; 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 = true; 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) { if (!allocator || !str || !delimiter) { return 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->string = 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) { node->string = 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) { node->string = 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) { if (!allocator || !str || !delimiter) { return 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->string = 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->string = 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) { node->string = 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) { if (!allocator || !list || !delimiter) { return NULL; } u64 capacity = list->total_size + (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 = true; while (running) { node = wapp_str8_list_get(list, node_index); wapp_str8_concat_capped(output, node->string); if (node_index + 1 < list->node_count) { wapp_str8_concat_capped(output, delimiter); } ++node_index; running = node_index < list->node_count; } return output; } Str8Node *wapp_str8_list_get(const Str8List *list, u64 index) { if (index >= list->node_count) { return NULL; } Str8Node *output = NULL; Str8Node *current = list->first; for (u64 i = 1; i <= index; ++i) { current = current->next; } output = current; return output; } void wapp_str8_list_push_front(Str8List *list, Str8Node *node) { if (!list || !node || !(node->string)) { return; } Str8List node_list = node_to_list(node); if (list->node_count == 0) { *list = node_list; return; } list->total_size += node_list.total_size; list->node_count += node_list.node_count; Str8Node *first = list->first; if (first) { first->prev = node_list.last; } list->first = node_list.first; node_list.last->next = first; } void wapp_str8_list_push_back(Str8List *list, Str8Node *node) { if (!list || !node || !(node->string)) { return; } Str8List node_list = node_to_list(node); if (list->node_count == 0) { *list = node_list; return; } list->total_size += node_list.total_size; list->node_count += node_list.node_count; Str8Node *last = list->last; if (last) { last->next = node_list.first; } list->last = node_list.last; node_list.first->prev = last; } void wapp_str8_list_insert(Str8List *list, Str8Node *node, u64 index) { if (!list || !node || !(node->string)) { return; } if (index == 0) { wapp_str8_list_push_front(list, node); return; } else if (index == list->node_count) { wapp_str8_list_push_back(list, node); return; } Str8Node *dst_node = wapp_str8_list_get(list, index); if (!dst_node) { return; } Str8List node_list = node_to_list(node); list->total_size += node_list.total_size; list->node_count += node_list.node_count; Str8Node *prev = dst_node->prev; dst_node->prev = node_list.last; prev->next = node_list.first; node_list.first->prev = prev; node_list.last->next = dst_node; } Str8Node *wapp_str8_list_pop_front(Str8List *list) { Str8Node *output = NULL; if (!list || list->node_count == 0) { goto RETURN_STR8_LIST_POP_FRONT; } output = list->first; if (list->node_count == 1) { *list = (Str8List){0}; goto RETURN_STR8_LIST_POP_FRONT; } --(list->node_count); list->total_size -= output->string->size; list->first = output->next; output->prev = output->next = NULL; RETURN_STR8_LIST_POP_FRONT: return output; } Str8Node *wapp_str8_list_pop_back(Str8List *list) { Str8Node *output = NULL; if (!list || list->node_count == 0) { goto RETURN_STR8_LIST_POP_BACK; } output = list->last; if (list->node_count == 1) { *list = (Str8List){0}; goto RETURN_STR8_LIST_POP_BACK; } --(list->node_count); list->total_size -= output->string->size; list->last = output->prev; output->prev = output->next = NULL; RETURN_STR8_LIST_POP_BACK: return output; } Str8Node *wapp_str8_list_remove(Str8List *list, u64 index) { Str8Node *output = NULL; if (!list) { goto RETURN_STR8_LIST_REMOVE; } if (index == 0) { output = wapp_str8_list_pop_front(list); goto RETURN_STR8_LIST_REMOVE; } else if (index == list->node_count) { output = wapp_str8_list_pop_back(list); goto RETURN_STR8_LIST_REMOVE; } output = wapp_str8_list_get(list, index); if (!output) { goto RETURN_STR8_LIST_REMOVE; } output->prev->next = output->next; output->next->prev = output->prev; --(list->node_count); list->total_size -= output->string->size; output->prev = output->next = NULL; RETURN_STR8_LIST_REMOVE: return output; } void wapp_str8_list_empty(Str8List *list) { if (!list) { return; } u64 count = list->node_count; for (u64 i = 0; i < count; ++i) { wapp_str8_list_pop_back(list); } } internal Str8List node_to_list(Str8Node *node) { Str8List output = {.first = node, .last = node, .total_size = node->string->size, .node_count = 1}; while (output.first->prev != NULL) { output.total_size += output.first->prev->string->size; output.first = output.first->prev; ++(output.node_count); } while (output.last->next != NULL) { output.total_size += output.last->next->string->size; output.last = output.last->next; ++(output.node_count); } return output; }