dynamic_string.h v0.2.2
A modern, efficient, single-file string library for C
Loading...
Searching...
No Matches
dynamic_string.h
Go to the documentation of this file.
1
37#ifndef DYNAMIC_STRING_H
38#define DYNAMIC_STRING_H
39
40#include <stddef.h>
41#include <stdint.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45#include <ctype.h>
46
47// Configuration macros (user can override before including)
48#ifndef DS_MALLOC
49#define DS_MALLOC malloc
50#endif
51
52#ifndef DS_REALLOC
53#define DS_REALLOC realloc
54#endif
55
56#ifndef DS_FREE
57#define DS_FREE free
58#endif
59
60#ifndef DS_ASSERT
61#include <assert.h>
62#define DS_ASSERT assert
63#endif
64
71#ifndef DS_ATOMIC_REFCOUNT
72#define DS_ATOMIC_REFCOUNT 0
73#endif
74
75// API macros
76#ifdef DS_STATIC
77#define DS_DEF static
78#else
79#define DS_DEF extern
80#endif
81
82/* Check C11 support for atomic operations */
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)"
85#endif
86
87/* atomic operations */
88#if DS_ATOMIC_REFCOUNT
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)
95#else
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))
101#endif
102
103#ifdef __cplusplus
104extern "C" {
105#endif
106
107// ============================================================================
108// INTERFACE
109// ============================================================================
110
125typedef char* ds_string;
126
148DS_DEF ds_string ds_new(const char* text);
149
156DS_DEF ds_string ds_create_length(const char* text, size_t length);
157
164
170
173// String operations (return new strings - immutable)
174
181DS_DEF ds_string ds_append(ds_string str, const char* text);
182
189DS_DEF ds_string ds_append_char(ds_string str, uint32_t codepoint);
190
197DS_DEF ds_string ds_prepend(ds_string str, const char* text);
198
206DS_DEF ds_string ds_insert(ds_string str, size_t index, const char* text);
207
215DS_DEF ds_string ds_substring(ds_string str, size_t start, size_t len);
216
217// String concatenation
218
226
234DS_DEF ds_string ds_join(ds_string* strings, size_t count, const char* separator);
235
236// Utility functions (read-only)
243
251
259
267
274DS_DEF int ds_find(ds_string str, const char* needle);
275
282DS_DEF int ds_find_last(ds_string str, const char* needle);
283
290DS_DEF int ds_contains(ds_string str, const char* needle);
291
298DS_DEF int ds_starts_with(ds_string str, const char* prefix);
299
306DS_DEF int ds_ends_with(ds_string str, const char* suffix);
307
308// String transformation functions
328
335
342
343// String replacement and manipulation
351DS_DEF ds_string ds_replace(ds_string str, const char* old, const char* new);
352
360DS_DEF ds_string ds_replace_all(ds_string str, const char* old, const char* new);
361
362// Case transformation
369
376
377// Utility transformations
385
393DS_DEF ds_string ds_truncate(ds_string str, size_t max_length, const char* ellipsis);
394
401
409DS_DEF ds_string ds_pad_left(ds_string str, size_t width, char pad);
410
418DS_DEF ds_string ds_pad_right(ds_string str, size_t width, char pad);
419
420// String splitting
450DS_DEF ds_string* ds_split(ds_string str, const char* delimiter, size_t* count);
451
457DS_DEF void ds_free_split_result(ds_string* array, size_t count);
458
459// String formatting
466DS_DEF ds_string ds_format(const char* fmt, ...);
467
474DS_DEF ds_string ds_format_v(const char* fmt, va_list args);
475
482
489
490// Reference count inspection
497
504
511
512// Unicode codepoint iteration (Rust-style)
516typedef struct {
517 const char* data;
518 size_t pos; // Current byte position
519 size_t end; // End byte position
521
528
535
542
543// Unicode utility functions
550
557DS_DEF uint32_t ds_codepoint_at(ds_string str, size_t index);
558
559// Convenience macros for common operations
560#define ds_empty() ds_new("")
561#define ds_from_literal(lit) ds_new(lit)
562
563// ============================================================================
564// STRINGBUILDER - Mutable builder for efficient string construction
565// ============================================================================
566
567typedef struct {
568 ds_string data; // Points to string data (same layout as ds_string)
569 size_t capacity; // Capacity for growth (length is in metadata)
571
572// StringBuilder creation and management
576
577// Mutable operations (modify the builder in-place)
578DS_DEF int ds_builder_append(ds_stringbuilder* sb, const char* text);
579DS_DEF int ds_builder_append_char(ds_stringbuilder* sb, uint32_t codepoint);
581DS_DEF int ds_builder_insert(ds_stringbuilder* sb, size_t index, const char* text);
583
584// Conversion to immutable string (the magic happens here!)
586
587// StringBuilder inspection
591
592#ifdef __cplusplus
593}
594#endif
595
596// ============================================================================
597// IMPLEMENTATION
598// ============================================================================
599
600#ifdef DS_IMPLEMENTATION
601
605typedef struct ds_internal {
606 DS_ATOMIC_SIZE_T refcount;
607 size_t length;
608} ds_internal;
609
610// ============================================================================
611// INTERNAL HELPER FUNCTIONS
612// ============================================================================
613
619static ds_internal* ds_meta(ds_string str) { return (ds_internal*)(str - sizeof(ds_internal)); }
620
626static ds_string ds_alloc(size_t length) {
627 // Allocate: metadata + string data + null terminator
628 void* block = DS_MALLOC(sizeof(ds_internal) + length + 1);
629 DS_ASSERT(block && "Memory allocation failed");
630
631 // Initialize metadata
632 ds_internal* meta = block;
633 DS_ATOMIC_STORE(&meta->refcount, 1);
634 meta->length = length;
635
636 // Return pointer to string data portion
637 ds_string str = (char*)block + sizeof(ds_internal);
638 str[length] = '\0'; // Always null-terminate
639
640 return str;
641}
642
647static void ds_dealloc(ds_string str) {
648 if (str) {
649 // Get original malloc pointer and free it
650 void* block = str - sizeof(ds_internal);
651 DS_FREE(block);
652 }
653}
654
655// ============================================================================
656// CORE STRING FUNCTIONS
657// ============================================================================
658
659DS_DEF size_t ds_length(ds_string str) {
660 DS_ASSERT(str && "ds_length: str cannot be NULL");
661 return ds_meta(str)->length;
662}
663
664DS_DEF size_t ds_refcount(ds_string str) {
665 DS_ASSERT(str && "ds_refcount: str cannot be NULL");
666 return DS_ATOMIC_LOAD(&ds_meta(str)->refcount);
667}
668
670 DS_ASSERT(str && "ds_is_shared: str cannot be NULL");
671 return DS_ATOMIC_LOAD(&ds_meta(str)->refcount) > 1;
672}
673
675 DS_ASSERT(str && "ds_is_empty: str cannot be NULL");
676 return ds_meta(str)->length == 0;
677}
678
679DS_DEF ds_string ds_new(const char* text) {
680 DS_ASSERT(text && "ds_new: text cannot be NULL");
681
682 size_t len = strlen(text);
683 return ds_create_length(text, len);
684}
685
686DS_DEF ds_string ds_create_length(const char* text, size_t length) {
687 DS_ASSERT(text && "ds_create_length: text cannot be NULL");
688
689 size_t text_len = strlen(text);
690 size_t actual_len = text_len < length ? text_len : length;
691
692 ds_string str = ds_alloc(actual_len);
693
694 if (actual_len > 0) {
695 memcpy(str, text, actual_len);
696 }
697
698 return str;
699}
700
702 DS_ASSERT(str && "ds_retain: str cannot be NULL");
703 DS_ATOMIC_FETCH_ADD(&ds_meta(str)->refcount, 1);
704 return str;
705}
706
707DS_DEF void ds_release(ds_string* str) {
708 if (str && *str) {
709 ds_internal* meta = ds_meta(*str);
710 size_t old_count = DS_ATOMIC_FETCH_SUB(&meta->refcount, 1);
711 if (old_count == 1) { // We were the last reference
712 ds_dealloc(*str);
713 }
714 *str = NULL;
715 }
716}
717
718DS_DEF ds_string ds_append(ds_string str, const char* text) {
719 DS_ASSERT(str && "ds_append: str cannot be NULL");
720 DS_ASSERT(text && "ds_append: text cannot be NULL");
721
722 size_t text_len = strlen(text);
723 if (text_len == 0) {
724 return ds_retain(str);
725 }
726
727 size_t new_length = ds_meta(str)->length + text_len;
728 ds_string result = ds_alloc(new_length);
729
730 // Copy original string
731 memcpy(result, str, ds_meta(str)->length);
732 // Append new text
733 memcpy(result + ds_meta(str)->length, text, text_len);
734
735 return result;
736}
737
738static size_t ds_encode_utf8(uint32_t codepoint, char* buffer) {
739 if (codepoint <= 0x7F) {
740 buffer[0] = (char)codepoint;
741 return 1;
742 }
743 if (codepoint <= 0x7FF) {
744 buffer[0] = (char)(0xC0 | codepoint >> 6);
745 buffer[1] = (char)(0x80 | codepoint & 0x3F);
746 return 2;
747 }
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);
752 return 3;
753 }
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);
759 return 4;
760 }
761
762 // Invalid codepoint - use replacement character (U+FFFD)
763 buffer[0] = (char)0xEF;
764 buffer[1] = (char)0xBF;
765 buffer[2] = (char)0xBD;
766 return 3;
767}
768
769DS_DEF ds_string ds_append_char(ds_string str, uint32_t codepoint) {
770 DS_ASSERT(str && "ds_append_char: str cannot be NULL");
771
772 char utf8_buffer[4];
773 size_t bytes_needed = ds_encode_utf8(codepoint, utf8_buffer);
774
775 char temp_str[5];
776 memcpy(temp_str, utf8_buffer, bytes_needed);
777 temp_str[bytes_needed] = '\0';
778
779 return ds_append(str, temp_str);
780}
781
782DS_DEF ds_string ds_prepend(ds_string str, const char* text) {
783 DS_ASSERT(str && "ds_prepend: str cannot be NULL");
784 DS_ASSERT(text && "ds_prepend: text cannot be NULL");
785
786 size_t text_len = strlen(text);
787 if (text_len == 0) {
788 return ds_retain(str);
789 }
790
791 size_t new_length = ds_meta(str)->length + text_len;
792 ds_string result = ds_alloc(new_length);
793
794 // Copy new text first
795 memcpy(result, text, text_len);
796 // Copy original string after
797 memcpy(result + text_len, str, ds_meta(str)->length);
798
799 return result;
800}
801
802DS_DEF ds_string ds_insert(ds_string str, size_t index, const char* text) {
803 DS_ASSERT(str && "ds_insert: str cannot be NULL");
804 DS_ASSERT(text && "ds_insert: text cannot be NULL");
805
806 // Clamp index to end of string if beyond bounds
807 size_t str_len = ds_meta(str)->length;
808 if (index > str_len) {
809 index = str_len;
810 }
811
812 size_t text_len = strlen(text);
813 if (text_len == 0) {
814 return ds_retain(str);
815 }
816
817 size_t new_length = str_len + text_len;
818 ds_string result = ds_alloc(new_length);
819
820 // Copy part before insertion point
821 memcpy(result, str, index);
822 // Copy inserted text
823 memcpy(result + index, text, text_len);
824 // Copy part after insertion point
825 memcpy(result + index + text_len, str + index, str_len - index);
826
827 return result;
828}
829
830DS_DEF ds_string ds_substring(ds_string str, size_t start, size_t len) {
831 DS_ASSERT(str && "ds_substring: str cannot be NULL");
832
833 if (start >= ds_meta(str)->length) {
834 return ds_new("");
835 }
836
837 size_t str_len = ds_meta(str)->length;
838 if (start + len > str_len) {
839 len = str_len - start;
840 }
841
842 return ds_create_length(str + start, len);
843}
844
846 DS_ASSERT(a && "ds_concat: a cannot be NULL");
847 DS_ASSERT(b && "ds_concat: b cannot be NULL");
848
849 size_t new_length = ds_meta(a)->length + ds_meta(b)->length;
850 ds_string result = ds_alloc(new_length);
851
852 memcpy(result, a, ds_meta(a)->length);
853 memcpy(result + ds_meta(a)->length, b, ds_meta(b)->length);
854
855 return result;
856}
857
858DS_DEF ds_string ds_join(ds_string* strings, size_t count, const char* separator) {
859 DS_ASSERT(strings && "ds_join: strings cannot be NULL");
860
861 if (count == 0) {
862 return ds_new("");
863 }
864
865 if (count == 1) {
866 DS_ASSERT(strings[0] && "ds_join: strings[0] cannot be NULL");
867 return ds_retain(strings[0]);
868 }
869
871
872 for (size_t i = 0; i < count; i++) {
873 DS_ASSERT(strings[i] && "ds_join: strings[i] cannot be NULL");
874 ds_builder_append_string(&sb, strings[i]);
875
876 if (i < count - 1 && separator) {
877 ds_builder_append(&sb, separator);
878 }
879 }
880
881 ds_string result = ds_builder_to_string(&sb);
883 return result;
884}
885
887 DS_ASSERT(a && "ds_compare: a cannot be NULL");
888 DS_ASSERT(b && "ds_compare: b cannot be NULL");
889
890 // Fast path: same object
891 if (a == b)
892 return 0;
893
894 return strcmp(a, b);
895}
896
898 DS_ASSERT(a && "ds_compare_ignore_case: a cannot be NULL");
899 DS_ASSERT(b && "ds_compare_ignore_case: b cannot be NULL");
900
901 // Fast path: same object
902 if (a == b)
903 return 0;
904
905 const char* a_str = a;
906 const char* b_str = b;
907
908 // Manual case-insensitive comparison (portable)
909 while (*a_str && *b_str) {
910 char ca = (char)tolower((unsigned char)*a_str);
911 char cb = (char)tolower((unsigned char)*b_str);
912 if (ca != cb) {
913 return ca - cb;
914 }
915 a_str++;
916 b_str++;
917 }
918
919 return (unsigned char)tolower((unsigned char)*a_str) - (unsigned char)tolower((unsigned char)*b_str);
920}
921
922DS_DEF size_t ds_hash(ds_string str) {
923 DS_ASSERT(str && "ds_hash: str cannot be NULL");
924
925 // FNV-1a hash algorithm
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;
928
929 size_t hash = FNV_OFFSET_BASIS;
930 size_t len = ds_length(str);
931
932 for (size_t i = 0; i < len; i++) {
933 hash ^= (unsigned char)str[i];
934 hash *= FNV_PRIME;
935 }
936
937 return hash;
938}
939
940DS_DEF int ds_find(ds_string str, const char* needle) {
941 DS_ASSERT(str && "ds_find: str cannot be NULL");
942 DS_ASSERT(needle && "ds_find: needle cannot be NULL");
943
944 const char* found = strstr(str, needle);
945 return found ? (int)(found - str) : -1;
946}
947
948DS_DEF int ds_find_last(ds_string str, const char* needle) {
949 DS_ASSERT(str && "ds_find_last: str cannot be NULL");
950 DS_ASSERT(needle && "ds_find_last: needle cannot be NULL");
951
952 size_t needle_len = strlen(needle);
953 if (needle_len == 0)
954 return 0; // Empty string found at beginning
955
956 size_t str_len = ds_length(str);
957 if (needle_len > str_len)
958 return -1;
959
960 // Search backwards from the end
961 for (size_t i = str_len - needle_len; i != SIZE_MAX; i--) {
962 if (memcmp(str + i, needle, needle_len) == 0) {
963 return (int)i;
964 }
965 }
966
967 return -1;
968}
969
970DS_DEF int ds_contains(ds_string str, const char* needle) {
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;
974}
975
976DS_DEF int ds_starts_with(ds_string str, const char* prefix) {
977 DS_ASSERT(str && "ds_starts_with: str cannot be NULL");
978 DS_ASSERT(prefix && "ds_starts_with: prefix cannot be NULL");
979
980 size_t prefix_len = strlen(prefix);
981 if (prefix_len > ds_meta(str)->length)
982 return 0;
983
984 return memcmp(str, prefix, prefix_len) == 0;
985}
986
987DS_DEF int ds_ends_with(ds_string str, const char* suffix) {
988 DS_ASSERT(str && "ds_ends_with: str cannot be NULL");
989 DS_ASSERT(suffix && "ds_ends_with: suffix cannot be NULL");
990
991 size_t suffix_len = strlen(suffix);
992 size_t str_len = ds_meta(str)->length;
993 if (suffix_len > str_len)
994 return 0;
995
996 return memcmp(str + str_len - suffix_len, suffix, suffix_len) == 0;
997}
998
999// ============================================================================
1000// STRING TRANSFORMATION FUNCTIONS
1001// ============================================================================
1002
1003static int ds_is_whitespace(char c) {
1004 return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f';
1005}
1006
1008 DS_ASSERT(str && "ds_trim_left: str cannot be NULL");
1009
1010 size_t len = ds_length(str);
1011 if (len == 0) return ds_retain(str);
1012
1013 size_t start = 0;
1014 while (start < len && ds_is_whitespace(str[start])) {
1015 start++;
1016 }
1017
1018 if (start == 0) {
1019 return ds_retain(str); // No trimming needed
1020 }
1021
1022 return ds_substring(str, start, len - start);
1023}
1024
1026 DS_ASSERT(str && "ds_trim_right: str cannot be NULL");
1027
1028 size_t len = ds_length(str);
1029 if (len == 0) return ds_retain(str);
1030
1031 size_t end = len;
1032 while (end > 0 && ds_is_whitespace(str[end - 1])) {
1033 end--;
1034 }
1035
1036 if (end == len) {
1037 return ds_retain(str); // No trimming needed
1038 }
1039
1040 return ds_substring(str, 0, end);
1041}
1042
1044 DS_ASSERT(str && "ds_trim: str cannot be NULL");
1045
1046 size_t len = ds_length(str);
1047 if (len == 0) return ds_retain(str);
1048
1049 // Find start of non-whitespace
1050 size_t start = 0;
1051 while (start < len && ds_is_whitespace(str[start])) {
1052 start++;
1053 }
1054
1055 // Find end of non-whitespace
1056 size_t end = len;
1057 while (end > start && ds_is_whitespace(str[end - 1])) {
1058 end--;
1059 }
1060
1061 if (start == 0 && end == len) {
1062 return ds_retain(str); // No trimming needed
1063 }
1064
1065 return ds_substring(str, start, end - start);
1066}
1067
1068// ============================================================================
1069// STRING REPLACEMENT FUNCTIONS
1070// ============================================================================
1071
1072DS_DEF ds_string ds_replace(ds_string str, const char* old, const char* new) {
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");
1076
1077 int pos = ds_find(str, old);
1078 if (pos == -1) {
1079 return ds_retain(str); // Nothing to replace
1080 }
1081
1082 size_t old_len = strlen(old);
1083 size_t new_len = strlen(new);
1084 size_t str_len = ds_length(str);
1085
1087
1088 // Add part before match
1089 if (pos > 0) {
1090 ds_string before = ds_substring(str, 0, pos);
1091 ds_builder_append_string(&sb, before);
1092 ds_release(&before);
1093 }
1094
1095 // Add replacement
1096 ds_builder_append(&sb, new);
1097
1098 // Add part after match
1099 if (pos + old_len < str_len) {
1100 ds_string after = ds_substring(str, pos + old_len, str_len - (pos + old_len));
1101 ds_builder_append_string(&sb, after);
1102 ds_release(&after);
1103 }
1104
1105 ds_string result = ds_builder_to_string(&sb);
1106 ds_builder_destroy(&sb);
1107 return result;
1108}
1109
1110DS_DEF ds_string ds_replace_all(ds_string str, const char* old, const char* new) {
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");
1114
1115 size_t old_len = strlen(old);
1116 if (old_len == 0) return ds_retain(str);
1117
1119 size_t start = 0;
1120 size_t str_len = ds_length(str);
1121
1122 while (start < str_len) {
1123 const char* found = strstr(str + start, old);
1124 if (!found) {
1125 // No more matches, add remainder
1126 ds_string remainder = ds_substring(str, start, str_len - start);
1127 ds_builder_append_string(&sb, remainder);
1128 ds_release(&remainder);
1129 break;
1130 }
1131
1132 size_t match_pos = found - str;
1133
1134 // Add part before match
1135 if (match_pos > start) {
1136 ds_string before = ds_substring(str, start, match_pos - start);
1137 ds_builder_append_string(&sb, before);
1138 ds_release(&before);
1139 }
1140
1141 // Add replacement
1142 ds_builder_append(&sb, new);
1143
1144 // Move past the match
1145 start = match_pos + old_len;
1146 }
1147
1148 ds_string result = ds_builder_to_string(&sb);
1149 ds_builder_destroy(&sb);
1150 return result;
1151}
1152
1153// ============================================================================
1154// CASE TRANSFORMATION FUNCTIONS
1155// ============================================================================
1156
1157#include <ctype.h>
1158
1160 DS_ASSERT(str && "ds_to_upper: str cannot be NULL");
1161
1162 size_t len = ds_length(str);
1163 if (len == 0) return ds_retain(str);
1164
1166
1167 for (size_t i = 0; i < len; i++) {
1168 char c = str[i];
1169 char upper_c = (char)toupper((unsigned char)c);
1170 ds_builder_append_char(&sb, upper_c);
1171 }
1172
1173 ds_string result = ds_builder_to_string(&sb);
1174 ds_builder_destroy(&sb);
1175 return result;
1176}
1177
1179 DS_ASSERT(str && "ds_to_lower: str cannot be NULL");
1180
1181 size_t len = ds_length(str);
1182 if (len == 0) return ds_retain(str);
1183
1185
1186 for (size_t i = 0; i < len; i++) {
1187 char c = str[i];
1188 char lower_c = (char)tolower((unsigned char)c);
1189 ds_builder_append_char(&sb, lower_c);
1190 }
1191
1192 ds_string result = ds_builder_to_string(&sb);
1193 ds_builder_destroy(&sb);
1194 return result;
1195}
1196
1197// ============================================================================
1198// UTILITY TRANSFORMATION FUNCTIONS
1199// ============================================================================
1200
1201DS_DEF ds_string ds_repeat(ds_string str, size_t times) {
1202 DS_ASSERT(str && "ds_repeat: str cannot be NULL");
1203 if (times == 0) return ds_new("");
1204 if (times == 1) return ds_retain(str);
1205
1206 size_t str_len = ds_length(str);
1207 if (str_len == 0) return ds_retain(str);
1208
1210
1211 for (size_t i = 0; i < times; i++) {
1212 ds_builder_append_string(&sb, str);
1213 }
1214
1215 ds_string result = ds_builder_to_string(&sb);
1216 ds_builder_destroy(&sb);
1217 return result;
1218}
1219
1220DS_DEF ds_string ds_truncate(ds_string str, size_t max_length, const char* ellipsis) {
1221 DS_ASSERT(str && "ds_truncate: str cannot be NULL");
1222
1223 size_t str_len = ds_length(str);
1224 if (str_len <= max_length) {
1225 return ds_retain(str); // No truncation needed
1226 }
1227
1228 size_t ellipsis_len = ellipsis ? strlen(ellipsis) : 0;
1229 if (max_length < ellipsis_len) {
1230 // Max length is too small for ellipsis, just truncate without ellipsis
1231 return ds_substring(str, 0, max_length);
1232 }
1233
1234 if (ellipsis_len == 0) {
1235 // No ellipsis, just truncate
1236 return ds_substring(str, 0, max_length);
1237 }
1238
1239 // Use builder for efficient construction
1240 ds_stringbuilder sb = ds_builder_create_with_capacity(max_length + ellipsis_len);
1241
1242 // Add truncated part
1243 size_t truncate_at = max_length - ellipsis_len;
1244 ds_string truncated_part = ds_substring(str, 0, truncate_at);
1245 ds_builder_append_string(&sb, truncated_part);
1246 ds_release(&truncated_part);
1247
1248 // Add ellipsis
1249 ds_builder_append(&sb, ellipsis);
1250
1251 ds_string result = ds_builder_to_string(&sb);
1252 ds_builder_destroy(&sb);
1253 return result;
1254}
1255
1257 DS_ASSERT(str && "ds_reverse: str cannot be NULL");
1258
1259 size_t len = ds_length(str);
1260 if (len <= 1) return ds_retain(str);
1261
1263
1264 // Reverse by codepoints for proper Unicode handling
1265 ds_codepoint_iter iter = ds_codepoints(str);
1266 uint32_t* codepoints = DS_MALLOC(ds_codepoint_length(str) * sizeof(uint32_t));
1267 size_t cp_count = 0;
1268
1269 uint32_t cp;
1270 while ((cp = ds_iter_next(&iter)) != 0) {
1271 codepoints[cp_count++] = cp;
1272 }
1273
1274 // Add codepoints in reverse order
1275 for (size_t i = cp_count; i > 0; i--) {
1276 ds_builder_append_char(&sb, codepoints[i - 1]);
1277 }
1278
1279 DS_FREE(codepoints);
1280
1281 ds_string result = ds_builder_to_string(&sb);
1282 ds_builder_destroy(&sb);
1283 return result;
1284}
1285
1286DS_DEF ds_string ds_pad_left(ds_string str, size_t width, char pad) {
1287 DS_ASSERT(str && "ds_pad_left: str cannot be NULL");
1288
1289 size_t len = ds_length(str);
1290 if (len >= width) return ds_retain(str);
1291
1292 size_t pad_count = width - len;
1294
1295 // Add padding
1296 for (size_t i = 0; i < pad_count; i++) {
1297 ds_builder_append_char(&sb, pad);
1298 }
1299
1300 // Add original string
1301 ds_builder_append_string(&sb, str);
1302
1303 ds_string result = ds_builder_to_string(&sb);
1304 ds_builder_destroy(&sb);
1305 return result;
1306}
1307
1308DS_DEF ds_string ds_pad_right(ds_string str, size_t width, char pad) {
1309 DS_ASSERT(str && "ds_pad_right: str cannot be NULL");
1310
1311 size_t len = ds_length(str);
1312 if (len >= width) return ds_retain(str);
1313
1314 size_t pad_count = width - len;
1316
1317 // Add original string
1318 ds_builder_append_string(&sb, str);
1319
1320 // Add padding
1321 for (size_t i = 0; i < pad_count; i++) {
1322 ds_builder_append_char(&sb, pad);
1323 }
1324
1325 ds_string result = ds_builder_to_string(&sb);
1326 ds_builder_destroy(&sb);
1327 return result;
1328}
1329
1330// ============================================================================
1331// STRING SPLITTING FUNCTIONS
1332// ============================================================================
1333
1334DS_DEF ds_string* ds_split(ds_string str, const char* delimiter, size_t* count) {
1335 DS_ASSERT(str && "ds_split: str cannot be NULL");
1336 DS_ASSERT(delimiter && "ds_split: delimiter cannot be NULL");
1337
1338 if (count) *count = 0;
1339
1340 size_t delim_len = strlen(delimiter);
1341 if (delim_len == 0) {
1342 // Split into individual characters
1343 size_t str_len = ds_length(str);
1344 if (str_len == 0) return NULL;
1345
1346 ds_string* result = DS_MALLOC(str_len * sizeof(ds_string));
1347 if (!result) return NULL;
1348
1349 for (size_t i = 0; i < str_len; i++) {
1350 result[i] = ds_substring(str, i, 1);
1351 }
1352
1353 if (count) *count = str_len;
1354 return result;
1355 }
1356
1357 // Count occurrences to allocate array
1358 size_t split_count = 1; // At least one part
1359 const char* pos = str;
1360 while ((pos = strstr(pos, delimiter)) != NULL) {
1361 split_count++;
1362 pos += delim_len;
1363 }
1364
1365 ds_string* result = DS_MALLOC(split_count * sizeof(ds_string));
1366 if (!result) return NULL;
1367
1368 // Split the string
1369 size_t result_index = 0;
1370 size_t start = 0;
1371 size_t str_len = ds_length(str);
1372
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) {
1376 // Found delimiter
1377 result[result_index++] = ds_substring(str, start, i - start);
1378 i += delim_len - 1; // Skip delimiter (loop will increment i)
1379 start = i + 1;
1380 }
1381 }
1382 }
1383
1384 // Add the last part
1385 result[result_index] = ds_substring(str, start, str_len - start);
1386
1387 if (count) *count = split_count;
1388 return result;
1389}
1390
1391DS_DEF void ds_free_split_result(ds_string* array, size_t count) {
1392 if (!array) return;
1393
1394 for (size_t i = 0; i < count; i++) {
1395 ds_release(&array[i]);
1396 }
1397
1398 DS_FREE(array);
1399}
1400
1401// ============================================================================
1402// STRING FORMATTING FUNCTIONS
1403// ============================================================================
1404
1405#include <stdarg.h>
1406
1407DS_DEF ds_string ds_format(const char* fmt, ...) {
1408 if (!fmt) return NULL;
1409
1410 va_list args;
1411 va_start(args, fmt);
1412 ds_string result = ds_format_v(fmt, args);
1413 va_end(args);
1414
1415 return result;
1416}
1417
1418DS_DEF ds_string ds_format_v(const char* fmt, va_list args) {
1419 if (!fmt) return NULL;
1420
1421 // Get required size
1422 va_list args_copy;
1423 va_copy(args_copy, args);
1424 int size = vsnprintf(NULL, 0, fmt, args_copy);
1425 va_end(args_copy);
1426
1427 if (size < 0) {
1428 return NULL;
1429 }
1430
1431 // Allocate and format
1432 ds_string result = ds_alloc(size);
1433 vsnprintf(result, size + 1, fmt, args);
1434
1435 return result;
1436}
1437
1439 DS_ASSERT(str && "ds_escape_json: str cannot be NULL");
1440
1441 size_t len = ds_length(str);
1442 if (len == 0) return ds_retain(str);
1443
1444 // Use builder with reasonable initial capacity (assume some escaping needed)
1446
1447 for (size_t i = 0; i < len; i++) {
1448 unsigned char c = (unsigned char)str[i];
1449
1450 switch (c) {
1451 case '"': ds_builder_append(&sb, "\\\""); break;
1452 case '\\': ds_builder_append(&sb, "\\\\"); break;
1453 case '\b': ds_builder_append(&sb, "\\b"); break;
1454 case '\f': ds_builder_append(&sb, "\\f"); break;
1455 case '\n': ds_builder_append(&sb, "\\n"); break;
1456 case '\r': ds_builder_append(&sb, "\\r"); break;
1457 case '\t': ds_builder_append(&sb, "\\t"); break;
1458 default:
1459 if (c < 0x20) {
1460 // Control characters - escape as \uXXXX
1461 ds_string escaped = ds_format("\\u%04x", c);
1462 ds_builder_append_string(&sb, escaped);
1463 ds_release(&escaped);
1464 } else {
1465 ds_builder_append_char(&sb, c);
1466 }
1467 break;
1468 }
1469 }
1470
1471 ds_string result = ds_builder_to_string(&sb);
1472 ds_builder_destroy(&sb);
1473 return result;
1474}
1475
1477 DS_ASSERT(str && "ds_unescape_json: str cannot be NULL");
1478
1479 size_t len = ds_length(str);
1480 if (len == 0) return ds_retain(str);
1481
1483
1484 for (size_t i = 0; i < len; i++) {
1485 if (str[i] == '\\' && i + 1 < len) {
1486 switch (str[i + 1]) {
1487 case '"': ds_builder_append_char(&sb, '"'); i++; break;
1488 case '\\': ds_builder_append_char(&sb, '\\'); i++; break;
1489 case '/': ds_builder_append_char(&sb, '/'); i++; break;
1490 case 'b': ds_builder_append_char(&sb, '\b'); i++; break;
1491 case 'f': ds_builder_append_char(&sb, '\f'); i++; break;
1492 case 'n': ds_builder_append_char(&sb, '\n'); i++; break;
1493 case 'r': ds_builder_append_char(&sb, '\r'); i++; break;
1494 case 't': ds_builder_append_char(&sb, '\t'); i++; break;
1495 case 'u':
1496 // Unicode escape sequence \uXXXX
1497 if (i + 5 < len) {
1498 char hex[5] = {str[i+2], str[i+3], str[i+4], str[i+5], '\0'};
1499 char* endptr;
1500 unsigned long codepoint = strtoul(hex, &endptr, 16);
1501 if (endptr == hex + 4) { // Valid 4-digit hex
1502 ds_builder_append_char(&sb, (uint32_t)codepoint);
1503 i += 5;
1504 } else {
1505 // Invalid escape, keep as-is
1506 ds_builder_append_char(&sb, str[i]);
1507 }
1508 } else {
1509 // Incomplete escape at end of string
1510 ds_builder_append_char(&sb, str[i]);
1511 }
1512 break;
1513 default:
1514 // Unknown escape, keep both characters
1515 ds_builder_append_char(&sb, str[i]);
1516 break;
1517 }
1518 } else {
1519 ds_builder_append_char(&sb, str[i]);
1520 }
1521 }
1522
1523 ds_string result = ds_builder_to_string(&sb);
1524 ds_builder_destroy(&sb);
1525 return result;
1526}
1527
1528// ============================================================================
1529// UNICODE CODEPOINT ITERATION
1530// ============================================================================
1531
1532static uint32_t ds_decode_utf8_at(const char* data, size_t pos, size_t end, size_t* bytes_consumed) {
1533 if (pos >= end) {
1534 *bytes_consumed = 0;
1535 return 0;
1536 }
1537
1538 unsigned char first = (unsigned char)data[pos];
1539
1540 if (first <= 0x7F) {
1541 *bytes_consumed = 1;
1542 return first;
1543 }
1544
1545 if ((first & 0xE0) == 0xC0) {
1546 if (pos + 1 >= end) {
1547 *bytes_consumed = 0;
1548 return 0;
1549 }
1550 *bytes_consumed = 2;
1551 return (first & 0x1F) << 6 | (unsigned char)data[pos + 1] & 0x3F;
1552 }
1553
1554 if ((first & 0xF0) == 0xE0) {
1555 if (pos + 2 >= end) {
1556 *bytes_consumed = 0;
1557 return 0;
1558 }
1559 *bytes_consumed = 3;
1560 return (first & 0x0F) << 12 | ((unsigned char)data[pos + 1] & 0x3F) << 6 | (unsigned char)data[pos + 2] & 0x3F;
1561 }
1562
1563 if ((first & 0xF8) == 0xF0) {
1564 if (pos + 3 >= end) {
1565 *bytes_consumed = 0;
1566 return 0;
1567 }
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;
1571 }
1572
1573 *bytes_consumed = 1;
1574 return 0xFFFD; // Unicode replacement character
1575}
1576
1578 DS_ASSERT(str && "ds_codepoints: str cannot be NULL");
1579
1580 ds_codepoint_iter iter;
1581 iter.data = str;
1582 iter.pos = 0;
1583 iter.end = ds_meta(str)->length;
1584
1585 return iter;
1586}
1587
1588DS_DEF uint32_t ds_iter_next(ds_codepoint_iter* iter) {
1589 if (!iter || iter->pos >= iter->end) {
1590 return 0;
1591 }
1592
1593 size_t bytes_consumed;
1594 uint32_t codepoint = ds_decode_utf8_at(iter->data, iter->pos, iter->end, &bytes_consumed);
1595
1596 if (bytes_consumed == 0) {
1597 return 0;
1598 }
1599
1600 iter->pos += bytes_consumed;
1601 return codepoint;
1602}
1603
1604DS_DEF int ds_iter_has_next(const ds_codepoint_iter* iter) { return iter && iter->pos < iter->end; }
1605
1607 DS_ASSERT(str && "ds_codepoint_length: str cannot be NULL");
1608
1609 ds_codepoint_iter iter = ds_codepoints(str);
1610 size_t count = 0;
1611
1612 while (ds_iter_next(&iter) != 0) {
1613 count++;
1614 }
1615
1616 return count;
1617}
1618
1619DS_DEF uint32_t ds_codepoint_at(ds_string str, size_t index) {
1620 DS_ASSERT(str && "ds_codepoint_at: str cannot be NULL");
1621
1622 ds_codepoint_iter iter = ds_codepoints(str);
1623 size_t current_index = 0;
1624 uint32_t codepoint;
1625
1626 while ((codepoint = ds_iter_next(&iter)) != 0) {
1627 if (current_index == index) {
1628 return codepoint;
1629 }
1630 current_index++;
1631 }
1632
1633 return 0;
1634}
1635
1636// ============================================================================
1637// STRINGBUILDER
1638// ============================================================================
1639
1640#ifndef DS_SB_INITIAL_CAPACITY
1641#define DS_SB_INITIAL_CAPACITY 32
1642#endif
1643
1644#ifndef DS_SB_GROWTH_FACTOR
1645#define DS_SB_GROWTH_FACTOR 2
1646#endif
1647
1648// StringBuilder helper functions
1649static int ds_sb_ensure_capacity(ds_stringbuilder* sb, size_t required_capacity) {
1650 if (sb->capacity >= required_capacity) {
1651 return 1; // Already have enough capacity
1652 }
1653 size_t new_capacity = sb->capacity;
1654 if (new_capacity == 0) {
1655 new_capacity = DS_SB_INITIAL_CAPACITY;
1656 }
1657
1658 while (new_capacity < required_capacity) {
1659 new_capacity *= DS_SB_GROWTH_FACTOR;
1660 }
1661
1662 // Get original block pointer and resize
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");
1666
1667 sb->data = (char*)new_block + sizeof(ds_internal);
1668 sb->capacity = new_capacity;
1669 return 1;
1670}
1671
1672static int ds_sb_ensure_unique(ds_stringbuilder* sb) {
1673 if (!sb->data)
1674 return 0;
1675
1676 ds_internal* meta = ds_meta(sb->data);
1677 if (DS_ATOMIC_LOAD(&meta->refcount) <= 1) {
1678 return 1; // Already unique
1679 }
1680
1681 // Need to create our own copy - just allocate exactly what we need
1682 size_t current_length = meta->length;
1683 ds_string new_str = ds_alloc(current_length);
1684
1685 // Copy current content
1686 memcpy(new_str, sb->data, current_length);
1687
1688 // Release old reference properly
1689 ds_string old_str = sb->data;
1690 ds_release(&old_str);
1691
1692 // Update StringBuilder - capacity is just what ds_alloc gave us
1693 sb->data = new_str;
1694 sb->capacity = current_length + 1; // ds_alloc gives us length + null terminator
1695
1696 return 1;
1697}
1698
1699DS_DEF ds_stringbuilder ds_builder_create(void) { return ds_builder_create_with_capacity(DS_SB_INITIAL_CAPACITY); }
1700
1703
1704 if (capacity == 0)
1705 capacity = DS_SB_INITIAL_CAPACITY;
1706
1707 void* block = DS_MALLOC(sizeof(ds_internal) + capacity);
1708 DS_ASSERT(block && "Memory allocation failed");
1709
1710 ds_internal* meta = (ds_internal*)block;
1711 DS_ATOMIC_STORE(&meta->refcount, 1);
1712 meta->length = 0;
1713
1714 sb.data = (char*)block + sizeof(ds_internal);
1715 sb.data[0] = '\0';
1716 sb.capacity = capacity;
1717
1718 return sb;
1719}
1720
1722 if (sb) {
1723 ds_release(&sb->data); // Handles refcount decrement and freeing
1724 sb->capacity = 0;
1725 }
1726}
1727
1728DS_DEF int ds_builder_append(ds_stringbuilder* sb, const char* text) {
1729 if (!sb || !text || !sb->data)
1730 return 0;
1731
1732 size_t text_len = strlen(text);
1733 if (text_len == 0)
1734 return 1;
1735
1736 if (!ds_sb_ensure_unique(sb))
1737 return 0;
1738
1739 ds_internal* meta = ds_meta(sb->data);
1740 if (!ds_sb_ensure_capacity(sb, meta->length + text_len + 1))
1741 return 0;
1742
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';
1747
1748 return 1;
1749}
1750
1751DS_DEF int ds_builder_append_char(ds_stringbuilder* sb, uint32_t codepoint) {
1752 if (!sb || !sb->data)
1753 return 0;
1754
1755 char utf8_buffer[4];
1756 size_t bytes_needed = ds_encode_utf8(codepoint, utf8_buffer);
1757
1758 if (!ds_sb_ensure_unique(sb))
1759 return 0;
1760
1761 ds_internal* meta = ds_meta(sb->data);
1762 if (!ds_sb_ensure_capacity(sb, meta->length + bytes_needed + 1))
1763 return 0;
1764
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';
1769
1770 return 1;
1771}
1772
1774 if (!sb || !str)
1775 return 0;
1776
1777 if (!ds_sb_ensure_unique(sb))
1778 return 0;
1779
1780 ds_internal* sb_meta = ds_meta(sb->data);
1781 ds_internal* str_meta = ds_meta(str);
1782
1783 if (!ds_sb_ensure_capacity(sb, sb_meta->length + str_meta->length + 1))
1784 return 0;
1785
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';
1790
1791 return 1;
1792}
1793
1794DS_DEF int ds_builder_insert(ds_stringbuilder* sb, size_t index, const char* text) {
1795 if (!sb || !text || !sb->data)
1796 return 0;
1797
1798 ds_internal* meta = ds_meta(sb->data);
1799 if (index > meta->length)
1800 return 0;
1801
1802 size_t text_len = strlen(text);
1803 if (text_len == 0)
1804 return 1;
1805
1806 if (!ds_sb_ensure_unique(sb))
1807 return 0;
1808 if (!ds_sb_ensure_capacity(sb, meta->length + text_len + 1))
1809 return 0;
1810
1811 meta = ds_meta(sb->data);
1812
1813 // Move content after insertion point
1814 memmove(sb->data + index + text_len, sb->data + index, meta->length - index + 1);
1815
1816 // Insert the text
1817 memcpy(sb->data + index, text, text_len);
1818 meta->length += text_len;
1819
1820 return 1;
1821}
1822
1824 if (!sb || !sb->data)
1825 return;
1826
1827 if (!ds_sb_ensure_unique(sb))
1828 return;
1829
1830 ds_internal* meta = ds_meta(sb->data);
1831 meta->length = 0;
1832 sb->data[0] = '\0';
1833}
1834
1836 if (!sb || !sb->data) {
1837 return NULL;
1838 }
1839
1840 ds_internal* meta = ds_meta(sb->data);
1841
1842 // Shrink to exact size
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);
1846
1847 ds_string result;
1848 if (shrunk_block) {
1849 result = (char*)shrunk_block + sizeof(ds_internal);
1850 meta = (ds_internal*)shrunk_block;
1851 } else {
1852 result = sb->data; // Use original if realloc failed
1853 }
1854
1855 // IMPORTANT: Mark StringBuilder as consumed to prevent reuse
1856 // This eliminates the complex copy-on-write bugs
1857 sb->data = NULL;
1858 sb->capacity = 0;
1859
1860 return result;
1861}
1862
1863DS_DEF size_t ds_builder_length(const ds_stringbuilder* sb) {
1864 if (!sb || !sb->data)
1865 return 0;
1866 ds_internal* meta = ds_meta(sb->data);
1867 return meta->length;
1868}
1869
1870DS_DEF size_t ds_builder_capacity(const ds_stringbuilder* sb) { return sb ? sb->capacity : 0; }
1871
1872DS_DEF const char* ds_builder_cstr(const ds_stringbuilder* sb) { return sb && sb->data ? sb->data : ""; }
1873
1874#endif // DS_IMPLEMENTATION
1875
1876#endif // DYNAMIC_STRING_H
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