// vim:fileencoding=utf-8:foldmethod=marker #include "str8.h" #include "../../array/array.h" #include "../../mem/allocator/mem_allocator.h" #include "../../../common/aliases/aliases.h" #include "../../../common/assert/assert.h" #include #include #include #include #include #define STR8_BUF_ALLOC_SIZE(CAPACITY) (sizeof(WpStr8) + sizeof(c8) * CAPACITY) WpStr8 *wpStr8AllocBuf(const WpAllocator *allocator, u64 capacity) { wpDebugAssert(allocator != NULL, "`allocator` should not be NULL"); WpStr8 *str = wpMemAllocatorAlloc(allocator, STR8_BUF_ALLOC_SIZE(capacity)); if (!str) { goto RETURN_STR8; } str->buf = (u8 *)str + sizeof(WpStr8); str->size = 0; str->capacity = capacity; RETURN_STR8: return str; } WpStr8 *wpStr8AllocAndFillBuf(const WpAllocator *allocator, u64 capacity) { WpStr8 *out = wpStr8AllocBuf(allocator, capacity); if (out) { memset(out->buf, 0, capacity); out->size = capacity; } return out; } WpStr8 *wpStr8AllocCstr(const WpAllocator *allocator, const char *str) { wpDebugAssert(allocator != NULL && str != NULL, "`allocator` and `str` should not be NULL"); u64 length = strlen(str); WpStr8 *output = wpStr8AllocBuf(allocator, length * 2); if (!output) { goto RETURN_ALLOC_CSTR; } output->size = length; memcpy(output->buf, str, length); RETURN_ALLOC_CSTR: return output; } WpStr8 *wpStr8AllocStr8(const WpAllocator *allocator, WpStr8RO *str) { wpDebugAssert(allocator != NULL && str != NULL, "`allocator` and `str` should not be NULL"); WpStr8 *output = wpStr8AllocBuf(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; } WpStr8 *wpStr8AllocSubstr(const WpAllocator *allocator, WpStr8RO *str, u64 start, u64 end) { wpDebugAssert(allocator != NULL && str != NULL, "`allocator` and `str` should not be NULL"); WpStr8 *output = NULL; if (start >= str->size || start >= end) { goto RETURN_ALLOC_SUBSTR; } if (end > str->size) { end = str->size; } output = wpStr8AllocBuf(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 wpStr8DeallocBuf(const WpAllocator *allocator, WpStr8 **str) { wpDebugAssert(allocator != NULL && str != NULL && (*str) != NULL, "Either `allocator` is NULL or `str` is an invalid double pointer"); wpMemAllocatorFree(allocator, (void **)str, STR8_BUF_ALLOC_SIZE((*str)->capacity)); } c8 wpStr8Get(const WpStr8 *str, u64 index) { if (index >= str->size) { return '\0'; } return str->buf[index]; } void wpStr8Set(WpStr8 *str, u64 index, c8 c) { if (index >= str->size) { return; } str->buf[index] = c; } void wpStr8PushBack(WpStr8 *str, c8 c) { if (!(str->size < str->capacity)) { return; } u64 index = (str->size)++; wpStr8Set(str, index, c); } b8 wpStr8Equal(WpStr8RO *s1, WpStr8RO *s2) { if (s1->size != s2->size) { return false; } return wpStr8EqualToCount(s1, s2, s1->size); } b8 wpStr8EqualToCount(WpStr8RO* s1, WpStr8RO* s2, u64 count) { if (!s1 || !s2) { return false; } return memcmp(s1->buf, s2->buf, count) == 0; } WpStr8 wpStr8Slice(WpStr8RO *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 (WpStr8RO){ .capacity = end - start, .size = end - start, .buf = str->buf + start, }; } WpStr8 *wpStr8AllocConcat(const WpAllocator *allocator, WpStr8 *dst, WpStr8RO *src) { wpDebugAssert(allocator != NULL && dst != NULL && src != NULL, "`allocator`, `dst` and `src` should not be NULL"); WpStr8 *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 = wpStr8AllocBuf(allocator, capacity); if (!output) { goto RETURN_STR8_CONCAT; } wpStr8ConcatCapped(output, dst); SOURCE_STRING_STR8_CONCAT: wpStr8ConcatCapped(output, src); RETURN_STR8_CONCAT: return output; } void wpStr8ConcatCapped(WpStr8 *dst, WpStr8RO *src) { wpDebugAssert(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 wpStr8CopyCstrCapped(WpStr8 *dst, const char *src) { wpDebugAssert(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 wpStr8CopyStr8Capped(WpStr8 *dst, WpStr8RO *src) { wpDebugAssert(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 wpStr8CopyToCstr(char *dst, WpStr8RO *src, u64 dst_capacity) { wpDebugAssert(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 wpStr8Format(WpStr8 *dst, const char *format, ...) { wpDebugAssert(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); } void wpStr8ToLower(WpStr8 *dst, WpStr8RO *src) { wpDebugAssert(src != NULL && dst != NULL, "`dst` and `src` should not be NULL"); wpDebugAssert(dst->capacity >= src->capacity, "`dst` does not have enough capacity"); dst->size = src->size; // NOTE (Abdelrahman): Uses a while loop instead of a for loop to get rid of // MSVC Spectre mitigation warnings u64 index = 0; b8 running = true; while (running) { wpStr8Set(dst, index, (u8)tolower(wpStr8Get(src, index))); ++index; running = index < src->size; } } void wpStr8ToUpper(WpStr8 *dst, WpStr8RO *src) { wpDebugAssert(src != NULL && dst != NULL, "`dst` and `src` should not be NULL"); wpDebugAssert(dst->capacity >= src->capacity, "`dst` does not have enough capacity"); dst->size = src->size; // NOTE (Abdelrahman): Uses a while loop instead of a for loop to get rid of // MSVC Spectre mitigation warnings u64 index = 0; b8 running = true; while (running) { wpStr8Set(dst, index, (u8)toupper(wpStr8Get(src, index))); ++index; running = index < src->size; } } void wpStr8FromBytes(WpStr8 *dst, const WpU8Array src) { wpDebugAssert(src != NULL && dst != NULL, "`dst` and `src` should not be NULL"); u64 size = wpArrayCount(src) * wpArrayItemSize(src); wpDebugAssert(dst->capacity >= size, "`dst` does not have enough capacity"); dst->size = size; memcpy(dst->buf, src, size); } i64 wpStr8Find(WpStr8RO *str, WpStr8RO 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; b8 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 wpStr8Rfind(WpStr8RO *str, WpStr8RO 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; b8 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; } WpStr8List *wpStr8SplitWithMax(const WpAllocator *allocator, WpStr8RO *str, WpStr8RO *delimiter, i64 max_splits) { wpDebugAssert(allocator != NULL && str != NULL && delimiter != NULL, "`allocator`, `str` and `delimiter` should not be NULL"); WpStr8List *output = wpDblListAlloc(WpStr8, allocator); if (delimiter->size > str->size) { WpStr8 *full = wpStr8AllocStr8(allocator, str); if (full) { wpDblListPushBackAlloc(WpStr8, allocator, output, full); } goto RETURN_STR8_SPLIT; } i64 start = 0; i64 end = 0; i64 splits = 0; WpStr8 *rest = wpStr8AllocStr8(allocator, str); WpStr8 *before_str; while ((end = wpStr8Find(rest, *delimiter)) != -1) { if (max_splits > 0 && splits >= max_splits) { break; } before_str = wpStr8AllocSubstr(allocator, str, start, start + end); if (before_str) { wpDblListPushBackAlloc(WpStr8, allocator, output, before_str); } wpMemAllocatorFree(allocator, (void **)&rest, sizeof(WpStr8)); rest = wpStr8AllocSubstr(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 = wpStr8AllocSubstr(allocator, str, start, str->size); if (rest) { wpDblListPushBackAlloc(WpStr8, allocator, output, rest); } RETURN_STR8_SPLIT: return output; } WpStr8List *wpStr8RsplitWithMax(const WpAllocator *allocator, WpStr8RO *str, WpStr8RO *delimiter, i64 max_splits) { wpDebugAssert(allocator != NULL && str != NULL && delimiter != NULL, "`allocator`, `str` and `delimiter` should not be NULL"); WpStr8List *output = wpDblListAlloc(WpStr8, allocator); if (delimiter->size > str->size) { WpStr8 *full = wpStr8AllocStr8(allocator, str); if (full) { wpDblListPushBackAlloc(WpStr8, allocator, output, full); } goto RETURN_STR8_SPLIT; } i64 end = 0; i64 splits = 0; WpStr8 *rest = wpStr8AllocStr8(allocator, str); WpStr8 *after_str; while ((end = wpStr8Rfind(rest, *delimiter)) != -1) { if (max_splits > 0 && splits >= max_splits) { break; } after_str = wpStr8AllocSubstr(allocator, rest, end + delimiter->size, str->size); if (after_str) { wpDblListPushFrontAlloc(WpStr8, allocator, output, after_str); } wpMemAllocatorFree(allocator, (void **)&rest, sizeof(WpStr8)); rest = wpStr8AllocSubstr(allocator, rest, 0, end); ++splits; } rest = wpStr8AllocSubstr(allocator, str, 0, rest->size); if (rest) { wpDblListPushFrontAlloc(WpStr8, allocator, output, rest); } RETURN_STR8_SPLIT: return output; } WpStr8 *wpStr8Join(const WpAllocator *allocator, const WpStr8List *list, WpStr8RO *delimiter) { wpDebugAssert(allocator != NULL && list != NULL && delimiter != NULL, "`allocator`, `list` and `delimiter` should not be NULL"); u64 capacity = wpStr8ListTotalSize(list) + (delimiter->size * (list->node_count - 1)); WpStr8 *output = wpStr8AllocBuf(allocator, capacity * 2); // NOTE (Abdelrahman): Uses a while loop instead of a for loop to get rid of // MSVC Spectre mitigation warnings WpStr8 *node; u64 node_index = 0; b8 running = node_index < list->node_count; while (running) { node = wpDblListGet(WpStr8, list, node_index); if (!node) { break; } wpStr8ConcatCapped(output, node); // NOTE (Abdelrahman): Comparison extracted to variable to silence // MSVC Spectre mitigation warnings b8 not_last = node_index + 1 < list->node_count; if (not_last) { wpStr8ConcatCapped(output, delimiter); } ++node_index; running = node_index < list->node_count; } return output; } u64 wpStr8ListTotalSize(const WpStr8List *list) { if (!list) { return 0; } // NOTE (Abdelrahman): Uses a while loop instead of a for loop to get rid of // MSVC Spectre mitigation warnings WpStr8 *node; u64 node_index = 0; u64 output = 0; b8 running = node_index < list->node_count; while (running) { node = wpDblListGet(WpStr8, list, node_index); if (!node) { break; } output += node->size; ++node_index; running = node_index < list->node_count; } return output; }