37#ifndef DYNAMIC_STRING_H
38#define DYNAMIC_STRING_H
49#define DS_MALLOC malloc
53#define DS_REALLOC realloc
62#define DS_ASSERT assert
71#ifndef DS_ATOMIC_REFCOUNT
72#define DS_ATOMIC_REFCOUNT 0
83#if DS_ATOMIC_REFCOUNT && __STDC_VERSION__ < 201112L
84 #error "DS_ATOMIC_REFCOUNT requires C11 or later for atomic support (compile with -std=c11 or later)"
89 #include <stdatomic.h>
90 #define DS_ATOMIC_SIZE_T _Atomic size_t
91 #define DS_ATOMIC_FETCH_ADD(ptr, val) atomic_fetch_add(ptr, val)
92 #define DS_ATOMIC_FETCH_SUB(ptr, val) atomic_fetch_sub(ptr, val)
93 #define DS_ATOMIC_LOAD(ptr) atomic_load(ptr)
94 #define DS_ATOMIC_STORE(ptr, val) atomic_store(ptr, val)
96 #define DS_ATOMIC_SIZE_T size_t
97 #define DS_ATOMIC_FETCH_ADD(ptr, val) (*(ptr) += (val), *(ptr) - (val))
98 #define DS_ATOMIC_FETCH_SUB(ptr, val) (*(ptr) -= (val), *(ptr) + (val))
99 #define DS_ATOMIC_LOAD(ptr) (*(ptr))
100 #define DS_ATOMIC_STORE(ptr, val) (*(ptr) = (val))
560#define ds_empty() ds_new("")
561#define ds_from_literal(lit) ds_new(lit)
600#ifdef DS_IMPLEMENTATION
605typedef struct ds_internal {
619static ds_internal* ds_meta(
ds_string str) {
return (ds_internal*)(str -
sizeof(ds_internal)); }
626static ds_string ds_alloc(
size_t length) {
628 void* block =
DS_MALLOC(
sizeof(ds_internal) + length + 1);
629 DS_ASSERT(block &&
"Memory allocation failed");
632 ds_internal* meta = block;
634 meta->length = length;
637 ds_string str = (
char*)block +
sizeof(ds_internal);
650 void* block = str -
sizeof(ds_internal);
660 DS_ASSERT(str &&
"ds_length: str cannot be NULL");
661 return ds_meta(str)->length;
665 DS_ASSERT(str &&
"ds_refcount: str cannot be NULL");
670 DS_ASSERT(str &&
"ds_is_shared: str cannot be NULL");
675 DS_ASSERT(str &&
"ds_is_empty: str cannot be NULL");
676 return ds_meta(str)->length == 0;
680 DS_ASSERT(text &&
"ds_new: text cannot be NULL");
682 size_t len = strlen(text);
687 DS_ASSERT(text &&
"ds_create_length: text cannot be NULL");
689 size_t text_len = strlen(text);
690 size_t actual_len = text_len < length ? text_len : length;
694 if (actual_len > 0) {
695 memcpy(str, text, actual_len);
702 DS_ASSERT(str &&
"ds_retain: str cannot be NULL");
709 ds_internal* meta = ds_meta(*str);
711 if (old_count == 1) {
719 DS_ASSERT(str &&
"ds_append: str cannot be NULL");
720 DS_ASSERT(text &&
"ds_append: text cannot be NULL");
722 size_t text_len = strlen(text);
727 size_t new_length = ds_meta(str)->length + text_len;
731 memcpy(result, str, ds_meta(str)->length);
733 memcpy(result + ds_meta(str)->length, text, text_len);
738static size_t ds_encode_utf8(uint32_t codepoint,
char* buffer) {
739 if (codepoint <= 0x7F) {
740 buffer[0] = (char)codepoint;
743 if (codepoint <= 0x7FF) {
744 buffer[0] = (char)(0xC0 | codepoint >> 6);
745 buffer[1] = (char)(0x80 | codepoint & 0x3F);
748 if (codepoint <= 0xFFFF) {
749 buffer[0] = (char)(0xE0 | codepoint >> 12);
750 buffer[1] = (char)(0x80 | codepoint >> 6 & 0x3F);
751 buffer[2] = (char)(0x80 | codepoint & 0x3F);
754 if (codepoint <= 0x10FFFF) {
755 buffer[0] = (char)(0xF0 | codepoint >> 18);
756 buffer[1] = (char)(0x80 | codepoint >> 12 & 0x3F);
757 buffer[2] = (char)(0x80 | codepoint >> 6 & 0x3F);
758 buffer[3] = (char)(0x80 | codepoint & 0x3F);
763 buffer[0] = (char)0xEF;
764 buffer[1] = (char)0xBF;
765 buffer[2] = (char)0xBD;
770 DS_ASSERT(str &&
"ds_append_char: str cannot be NULL");
773 size_t bytes_needed = ds_encode_utf8(codepoint, utf8_buffer);
776 memcpy(temp_str, utf8_buffer, bytes_needed);
777 temp_str[bytes_needed] =
'\0';
783 DS_ASSERT(str &&
"ds_prepend: str cannot be NULL");
784 DS_ASSERT(text &&
"ds_prepend: text cannot be NULL");
786 size_t text_len = strlen(text);
791 size_t new_length = ds_meta(str)->length + text_len;
795 memcpy(result, text, text_len);
797 memcpy(result + text_len, str, ds_meta(str)->length);
803 DS_ASSERT(str &&
"ds_insert: str cannot be NULL");
804 DS_ASSERT(text &&
"ds_insert: text cannot be NULL");
807 size_t str_len = ds_meta(str)->length;
808 if (index > str_len) {
812 size_t text_len = strlen(text);
817 size_t new_length = str_len + text_len;
821 memcpy(result, str, index);
823 memcpy(result + index, text, text_len);
825 memcpy(result + index + text_len, str + index, str_len - index);
831 DS_ASSERT(str &&
"ds_substring: str cannot be NULL");
833 if (start >= ds_meta(str)->length) {
837 size_t str_len = ds_meta(str)->length;
838 if (start + len > str_len) {
839 len = str_len - start;
846 DS_ASSERT(a &&
"ds_concat: a cannot be NULL");
847 DS_ASSERT(b &&
"ds_concat: b cannot be NULL");
849 size_t new_length = ds_meta(a)->length + ds_meta(b)->length;
852 memcpy(result, a, ds_meta(a)->length);
853 memcpy(result + ds_meta(a)->length, b, ds_meta(b)->length);
859 DS_ASSERT(strings &&
"ds_join: strings cannot be NULL");
866 DS_ASSERT(strings[0] &&
"ds_join: strings[0] cannot be NULL");
872 for (
size_t i = 0; i < count; i++) {
873 DS_ASSERT(strings[i] &&
"ds_join: strings[i] cannot be NULL");
876 if (i < count - 1 && separator) {
887 DS_ASSERT(a &&
"ds_compare: a cannot be NULL");
888 DS_ASSERT(b &&
"ds_compare: b cannot be NULL");
898 DS_ASSERT(a &&
"ds_compare_ignore_case: a cannot be NULL");
899 DS_ASSERT(b &&
"ds_compare_ignore_case: b cannot be NULL");
905 const char* a_str = a;
906 const char* b_str = b;
909 while (*a_str && *b_str) {
910 char ca = (char)tolower((
unsigned char)*a_str);
911 char cb = (char)tolower((
unsigned char)*b_str);
919 return (
unsigned char)tolower((
unsigned char)*a_str) - (
unsigned char)tolower((
unsigned char)*b_str);
923 DS_ASSERT(str &&
"ds_hash: str cannot be NULL");
926 const size_t FNV_PRIME =
sizeof(size_t) == 8 ? 1099511628211ULL : 16777619U;
927 const size_t FNV_OFFSET_BASIS =
sizeof(size_t) == 8 ? 14695981039346656037ULL : 2166136261U;
929 size_t hash = FNV_OFFSET_BASIS;
932 for (
size_t i = 0; i < len; i++) {
933 hash ^= (
unsigned char)str[i];
941 DS_ASSERT(str &&
"ds_find: str cannot be NULL");
942 DS_ASSERT(needle &&
"ds_find: needle cannot be NULL");
944 const char* found = strstr(str, needle);
945 return found ? (int)(found - str) : -1;
949 DS_ASSERT(str &&
"ds_find_last: str cannot be NULL");
950 DS_ASSERT(needle &&
"ds_find_last: needle cannot be NULL");
952 size_t needle_len = strlen(needle);
957 if (needle_len > str_len)
961 for (
size_t i = str_len - needle_len; i != SIZE_MAX; i--) {
962 if (memcmp(str + i, needle, needle_len) == 0) {
971 DS_ASSERT(str &&
"ds_contains: str cannot be NULL");
972 DS_ASSERT(needle &&
"ds_contains: needle cannot be NULL");
973 return ds_find(str, needle) != -1;
977 DS_ASSERT(str &&
"ds_starts_with: str cannot be NULL");
978 DS_ASSERT(prefix &&
"ds_starts_with: prefix cannot be NULL");
980 size_t prefix_len = strlen(prefix);
981 if (prefix_len > ds_meta(str)->length)
984 return memcmp(str, prefix, prefix_len) == 0;
988 DS_ASSERT(str &&
"ds_ends_with: str cannot be NULL");
989 DS_ASSERT(suffix &&
"ds_ends_with: suffix cannot be NULL");
991 size_t suffix_len = strlen(suffix);
992 size_t str_len = ds_meta(str)->length;
993 if (suffix_len > str_len)
996 return memcmp(str + str_len - suffix_len, suffix, suffix_len) == 0;
1003static int ds_is_whitespace(
char c) {
1004 return c ==
' ' || c ==
'\t' || c ==
'\n' || c ==
'\r' || c ==
'\v' || c ==
'\f';
1008 DS_ASSERT(str &&
"ds_trim_left: str cannot be NULL");
1014 while (start < len && ds_is_whitespace(str[start])) {
1026 DS_ASSERT(str &&
"ds_trim_right: str cannot be NULL");
1032 while (end > 0 && ds_is_whitespace(str[end - 1])) {
1044 DS_ASSERT(str &&
"ds_trim: str cannot be NULL");
1051 while (start < len && ds_is_whitespace(str[start])) {
1057 while (end > start && ds_is_whitespace(str[end - 1])) {
1061 if (start == 0 && end == len) {
1073 DS_ASSERT(str &&
"ds_replace: str cannot be NULL");
1074 DS_ASSERT(old &&
"ds_replace: old cannot be NULL");
1075 DS_ASSERT(
new &&
"ds_replace: new cannot be NULL");
1082 size_t old_len = strlen(old);
1083 size_t new_len = strlen(
new);
1099 if (pos + old_len < str_len) {
1111 DS_ASSERT(str &&
"ds_replace_all: str cannot be NULL");
1112 DS_ASSERT(old &&
"ds_replace_all: old cannot be NULL");
1113 DS_ASSERT(
new &&
"ds_replace_all: new cannot be NULL");
1115 size_t old_len = strlen(old);
1116 if (old_len == 0)
return ds_retain(str);
1122 while (start < str_len) {
1123 const char* found = strstr(str + start, old);
1132 size_t match_pos = found - str;
1135 if (match_pos > start) {
1145 start = match_pos + old_len;
1160 DS_ASSERT(str &&
"ds_to_upper: str cannot be NULL");
1167 for (
size_t i = 0; i < len; i++) {
1169 char upper_c = (char)toupper((
unsigned char)c);
1179 DS_ASSERT(str &&
"ds_to_lower: str cannot be NULL");
1186 for (
size_t i = 0; i < len; i++) {
1188 char lower_c = (char)tolower((
unsigned char)c);
1202 DS_ASSERT(str &&
"ds_repeat: str cannot be NULL");
1203 if (times == 0)
return ds_new(
"");
1207 if (str_len == 0)
return ds_retain(str);
1211 for (
size_t i = 0; i < times; i++) {
1221 DS_ASSERT(str &&
"ds_truncate: str cannot be NULL");
1224 if (str_len <= max_length) {
1228 size_t ellipsis_len = ellipsis ? strlen(ellipsis) : 0;
1229 if (max_length < ellipsis_len) {
1234 if (ellipsis_len == 0) {
1243 size_t truncate_at = max_length - ellipsis_len;
1257 DS_ASSERT(str &&
"ds_reverse: str cannot be NULL");
1267 size_t cp_count = 0;
1271 codepoints[cp_count++] = cp;
1275 for (
size_t i = cp_count; i > 0; i--) {
1287 DS_ASSERT(str &&
"ds_pad_left: str cannot be NULL");
1290 if (len >= width)
return ds_retain(str);
1292 size_t pad_count = width - len;
1296 for (
size_t i = 0; i < pad_count; i++) {
1309 DS_ASSERT(str &&
"ds_pad_right: str cannot be NULL");
1312 if (len >= width)
return ds_retain(str);
1314 size_t pad_count = width - len;
1321 for (
size_t i = 0; i < pad_count; i++) {
1335 DS_ASSERT(str &&
"ds_split: str cannot be NULL");
1336 DS_ASSERT(delimiter &&
"ds_split: delimiter cannot be NULL");
1338 if (count) *count = 0;
1340 size_t delim_len = strlen(delimiter);
1341 if (delim_len == 0) {
1344 if (str_len == 0)
return NULL;
1347 if (!result)
return NULL;
1349 for (
size_t i = 0; i < str_len; i++) {
1353 if (count) *count = str_len;
1358 size_t split_count = 1;
1359 const char* pos = str;
1360 while ((pos = strstr(pos, delimiter)) != NULL) {
1366 if (!result)
return NULL;
1369 size_t result_index = 0;
1373 if (str_len >= delim_len) {
1374 for (
size_t i = 0; i <= str_len - delim_len; i++) {
1375 if (memcmp(str + i, delimiter, delim_len) == 0) {
1377 result[result_index++] =
ds_substring(str, start, i - start);
1385 result[result_index] =
ds_substring(str, start, str_len - start);
1387 if (count) *count = split_count;
1394 for (
size_t i = 0; i < count; i++) {
1408 if (!fmt)
return NULL;
1411 va_start(args, fmt);
1419 if (!fmt)
return NULL;
1423 va_copy(args_copy, args);
1424 int size = vsnprintf(NULL, 0, fmt, args_copy);
1433 vsnprintf(result, size + 1, fmt, args);
1439 DS_ASSERT(str &&
"ds_escape_json: str cannot be NULL");
1447 for (
size_t i = 0; i < len; i++) {
1448 unsigned char c = (
unsigned char)str[i];
1477 DS_ASSERT(str &&
"ds_unescape_json: str cannot be NULL");
1484 for (
size_t i = 0; i < len; i++) {
1485 if (str[i] ==
'\\' && i + 1 < len) {
1486 switch (str[i + 1]) {
1498 char hex[5] = {str[i+2], str[i+3], str[i+4], str[i+5],
'\0'};
1500 unsigned long codepoint = strtoul(hex, &endptr, 16);
1501 if (endptr == hex + 4) {
1532static uint32_t ds_decode_utf8_at(
const char* data,
size_t pos,
size_t end,
size_t* bytes_consumed) {
1534 *bytes_consumed = 0;
1538 unsigned char first = (
unsigned char)data[pos];
1540 if (first <= 0x7F) {
1541 *bytes_consumed = 1;
1545 if ((first & 0xE0) == 0xC0) {
1546 if (pos + 1 >= end) {
1547 *bytes_consumed = 0;
1550 *bytes_consumed = 2;
1551 return (first & 0x1F) << 6 | (
unsigned char)data[pos + 1] & 0x3F;
1554 if ((first & 0xF0) == 0xE0) {
1555 if (pos + 2 >= end) {
1556 *bytes_consumed = 0;
1559 *bytes_consumed = 3;
1560 return (first & 0x0F) << 12 | ((
unsigned char)data[pos + 1] & 0x3F) << 6 | (
unsigned char)data[pos + 2] & 0x3F;
1563 if ((first & 0xF8) == 0xF0) {
1564 if (pos + 3 >= end) {
1565 *bytes_consumed = 0;
1568 *bytes_consumed = 4;
1569 return (first & 0x07) << 18 | ((
unsigned char)data[pos + 1] & 0x3F) << 12 |
1570 ((
unsigned char)data[pos + 2] & 0x3F) << 6 | (
unsigned char)data[pos + 3] & 0x3F;
1573 *bytes_consumed = 1;
1578 DS_ASSERT(str &&
"ds_codepoints: str cannot be NULL");
1583 iter.
end = ds_meta(str)->length;
1589 if (!iter || iter->
pos >= iter->
end) {
1593 size_t bytes_consumed;
1594 uint32_t codepoint = ds_decode_utf8_at(iter->
data, iter->
pos, iter->
end, &bytes_consumed);
1596 if (bytes_consumed == 0) {
1600 iter->
pos += bytes_consumed;
1607 DS_ASSERT(str &&
"ds_codepoint_length: str cannot be NULL");
1620 DS_ASSERT(str &&
"ds_codepoint_at: str cannot be NULL");
1623 size_t current_index = 0;
1627 if (current_index == index) {
1640#ifndef DS_SB_INITIAL_CAPACITY
1641#define DS_SB_INITIAL_CAPACITY 32
1644#ifndef DS_SB_GROWTH_FACTOR
1645#define DS_SB_GROWTH_FACTOR 2
1649static int ds_sb_ensure_capacity(
ds_stringbuilder* sb,
size_t required_capacity) {
1650 if (sb->
capacity >= required_capacity) {
1653 size_t new_capacity = sb->
capacity;
1654 if (new_capacity == 0) {
1655 new_capacity = DS_SB_INITIAL_CAPACITY;
1658 while (new_capacity < required_capacity) {
1659 new_capacity *= DS_SB_GROWTH_FACTOR;
1663 void* old_block = (
char*)sb->
data -
sizeof(ds_internal);
1664 void* new_block =
DS_REALLOC(old_block,
sizeof(ds_internal) + new_capacity);
1665 DS_ASSERT(new_block &&
"Memory re-allocation failed");
1667 sb->
data = (
char*)new_block +
sizeof(ds_internal);
1676 ds_internal* meta = ds_meta(sb->
data);
1682 size_t current_length = meta->length;
1683 ds_string new_str = ds_alloc(current_length);
1686 memcpy(new_str, sb->
data, current_length);
1705 capacity = DS_SB_INITIAL_CAPACITY;
1707 void* block =
DS_MALLOC(
sizeof(ds_internal) + capacity);
1708 DS_ASSERT(block &&
"Memory allocation failed");
1710 ds_internal* meta = (ds_internal*)block;
1714 sb.
data = (
char*)block +
sizeof(ds_internal);
1729 if (!sb || !text || !sb->
data)
1732 size_t text_len = strlen(text);
1736 if (!ds_sb_ensure_unique(sb))
1739 ds_internal* meta = ds_meta(sb->
data);
1740 if (!ds_sb_ensure_capacity(sb, meta->length + text_len + 1))
1743 meta = ds_meta(sb->
data);
1744 memcpy(sb->
data + meta->length, text, text_len);
1745 meta->length += text_len;
1746 sb->
data[meta->length] =
'\0';
1752 if (!sb || !sb->
data)
1755 char utf8_buffer[4];
1756 size_t bytes_needed = ds_encode_utf8(codepoint, utf8_buffer);
1758 if (!ds_sb_ensure_unique(sb))
1761 ds_internal* meta = ds_meta(sb->
data);
1762 if (!ds_sb_ensure_capacity(sb, meta->length + bytes_needed + 1))
1765 meta = ds_meta(sb->
data);
1766 memcpy(sb->
data + meta->length, utf8_buffer, bytes_needed);
1767 meta->length += bytes_needed;
1768 sb->
data[meta->length] =
'\0';
1777 if (!ds_sb_ensure_unique(sb))
1780 ds_internal* sb_meta = ds_meta(sb->
data);
1781 ds_internal* str_meta = ds_meta(str);
1783 if (!ds_sb_ensure_capacity(sb, sb_meta->length + str_meta->length + 1))
1786 sb_meta = ds_meta(sb->
data);
1787 memcpy(sb->
data + sb_meta->length, str, str_meta->length);
1788 sb_meta->length += str_meta->length;
1789 sb->
data[sb_meta->length] =
'\0';
1795 if (!sb || !text || !sb->
data)
1798 ds_internal* meta = ds_meta(sb->
data);
1799 if (index > meta->length)
1802 size_t text_len = strlen(text);
1806 if (!ds_sb_ensure_unique(sb))
1808 if (!ds_sb_ensure_capacity(sb, meta->length + text_len + 1))
1811 meta = ds_meta(sb->
data);
1814 memmove(sb->
data + index + text_len, sb->
data + index, meta->length - index + 1);
1817 memcpy(sb->
data + index, text, text_len);
1818 meta->length += text_len;
1824 if (!sb || !sb->
data)
1827 if (!ds_sb_ensure_unique(sb))
1830 ds_internal* meta = ds_meta(sb->
data);
1836 if (!sb || !sb->
data) {
1840 ds_internal* meta = ds_meta(sb->
data);
1843 size_t exact_size =
sizeof(ds_internal) + meta->length + 1;
1844 void* old_block = (
char*)sb->
data -
sizeof(ds_internal);
1845 void* shrunk_block =
DS_REALLOC(old_block, exact_size);
1849 result = (
char*)shrunk_block +
sizeof(ds_internal);
1850 meta = (ds_internal*)shrunk_block;
1864 if (!sb || !sb->
data)
1866 ds_internal* meta = ds_meta(sb->
data);
1867 return meta->length;
DS_DEF ds_string ds_substring(ds_string str, size_t start, size_t len)
Extract a substring from a string.
DS_DEF const char * ds_builder_cstr(const ds_stringbuilder *sb)
DS_DEF int ds_ends_with(ds_string str, const char *suffix)
Check if string ends with a suffix.
DS_DEF size_t ds_builder_capacity(const ds_stringbuilder *sb)
DS_DEF ds_string ds_insert(ds_string str, size_t index, const char *text)
Insert text at a specific position in a string.
DS_DEF void ds_free_split_result(ds_string *array, size_t count)
Free the result array from ds_split()
DS_DEF void ds_builder_clear(ds_stringbuilder *sb)
DS_DEF ds_stringbuilder ds_builder_create(void)
#define DS_DEF
Definition dynamic_string.h:79
#define DS_MALLOC
Definition dynamic_string.h:49
DS_DEF ds_stringbuilder ds_builder_create_with_capacity(size_t capacity)
DS_DEF size_t ds_length(ds_string str)
Get the length of a string in bytes.
DS_DEF ds_string ds_append_char(ds_string str, uint32_t codepoint)
Append a Unicode codepoint to a string.
DS_DEF int ds_iter_has_next(const ds_codepoint_iter *iter)
Check if iterator has more codepoints.
DS_DEF ds_string ds_repeat(ds_string str, size_t times)
Repeat a string multiple times.
DS_DEF ds_string ds_to_upper(ds_string str)
Convert string to uppercase.
DS_DEF int ds_starts_with(ds_string str, const char *prefix)
Check if string starts with a prefix.
DS_DEF int ds_builder_insert(ds_stringbuilder *sb, size_t index, const char *text)
DS_DEF int ds_is_empty(ds_string str)
Check if a string is empty.
DS_DEF int ds_builder_append_char(ds_stringbuilder *sb, uint32_t codepoint)
DS_DEF int ds_compare(ds_string a, ds_string b)
Compare two strings lexicographically.
DS_DEF ds_string ds_trim_left(ds_string str)
Remove whitespace from the beginning of a string.
DS_DEF int ds_builder_append(ds_stringbuilder *sb, const char *text)
#define DS_ATOMIC_FETCH_SUB(ptr, val)
Definition dynamic_string.h:98
DS_DEF void ds_builder_destroy(ds_stringbuilder *sb)
DS_DEF int ds_compare_ignore_case(ds_string a, ds_string b)
Compare two strings lexicographically (case-insensitive)
DS_DEF int ds_is_shared(ds_string str)
Check if a string has multiple references.
DS_DEF ds_string ds_trim(ds_string str)
Remove whitespace from both ends of a string.
DS_DEF ds_string ds_trim_right(ds_string str)
Remove whitespace from the end of a string.
#define DS_ATOMIC_SIZE_T
Definition dynamic_string.h:96
DS_DEF size_t ds_refcount(ds_string str)
Get the current reference count of a string.
DS_DEF uint32_t ds_codepoint_at(ds_string str, size_t index)
Get Unicode codepoint at specific index.
DS_DEF ds_codepoint_iter ds_codepoints(ds_string str)
Create an iterator for Unicode codepoints in a string.
#define DS_ATOMIC_STORE(ptr, val)
Definition dynamic_string.h:100
DS_DEF ds_string ds_to_lower(ds_string str)
Convert string to lowercase.
#define DS_REALLOC
Definition dynamic_string.h:53
DS_DEF ds_string ds_format_v(const char *fmt, va_list args)
Create formatted string using printf-style format specifiers (va_list version)
DS_DEF size_t ds_codepoint_length(ds_string str)
Count the number of Unicode codepoints in a string.
#define DS_ATOMIC_LOAD(ptr)
Definition dynamic_string.h:99
DS_DEF int ds_builder_append_string(ds_stringbuilder *sb, ds_string str)
DS_DEF ds_string ds_concat(ds_string a, ds_string b)
Concatenate two strings.
DS_DEF ds_string ds_truncate(ds_string str, size_t max_length, const char *ellipsis)
Truncate string to maximum length with optional ellipsis.
DS_DEF int ds_contains(ds_string str, const char *needle)
Check if string contains a substring.
DS_DEF ds_string ds_escape_json(ds_string str)
Escape string for JSON.
#define DS_ASSERT
Definition dynamic_string.h:62
DS_DEF int ds_find(ds_string str, const char *needle)
Find the first occurrence of a substring.
char * ds_string
String handle - points directly to null-terminated string data.
Definition dynamic_string.h:125
DS_DEF size_t ds_hash(ds_string str)
Calculate hash value for string.
DS_DEF ds_string ds_unescape_json(ds_string str)
Unescape JSON string.
DS_DEF ds_string ds_pad_left(ds_string str, size_t width, char pad)
Pad string on the left to reach specified width.
DS_DEF ds_string ds_replace(ds_string str, const char *old, const char *new)
Replace the first occurrence of a substring.
DS_DEF ds_string ds_replace_all(ds_string str, const char *old, const char *new)
Replace all occurrences of a substring.
#define DS_FREE
Definition dynamic_string.h:57
DS_DEF ds_string ds_prepend(ds_string str, const char *text)
Prepend text to the beginning of a string.
DS_DEF ds_string * ds_split(ds_string str, const char *delimiter, size_t *count)
Split string into array by delimiter.
DS_DEF ds_string ds_pad_right(ds_string str, size_t width, char pad)
Pad string on the right to reach specified width.
DS_DEF ds_string ds_join(ds_string *strings, size_t count, const char *separator)
Join multiple strings with a separator.
DS_DEF ds_string ds_format(const char *fmt,...)
Create formatted string using printf-style format specifiers.
DS_DEF ds_string ds_append(ds_string str, const char *text)
Append text to a string.
DS_DEF int ds_find_last(ds_string str, const char *needle)
Find the last occurrence of a substring.
DS_DEF size_t ds_builder_length(const ds_stringbuilder *sb)
#define DS_ATOMIC_FETCH_ADD(ptr, val)
Definition dynamic_string.h:97
DS_DEF ds_string ds_reverse(ds_string str)
Reverse a string (Unicode-aware)
DS_DEF ds_string ds_builder_to_string(ds_stringbuilder *sb)
DS_DEF uint32_t ds_iter_next(ds_codepoint_iter *iter)
Get the next Unicode codepoint from iterator.
DS_DEF ds_string ds_create_length(const char *text, size_t length)
Create a string from a buffer with explicit length.
DS_DEF ds_string ds_retain(ds_string str)
Increment reference count and return shared handle.
DS_DEF ds_string ds_new(const char *text)
Create a new string from a C string.
DS_DEF void ds_release(ds_string *str)
Decrement reference count and free memory if last reference.
Unicode codepoint iterator for UTF-8 strings.
Definition dynamic_string.h:516
const char * data
Definition dynamic_string.h:517
size_t end
Definition dynamic_string.h:519
size_t pos
Definition dynamic_string.h:518
Definition dynamic_string.h:567
size_t capacity
Definition dynamic_string.h:569
ds_string data
Definition dynamic_string.h:568