1 // Copyright © Tavian Barnes <tavianator@tavianator.com>
2 // SPDX-License-Identifier: 0BSD
15 * The memory representation of a dynamic string. Users get a pointer to data.
20 alignas(dchar) char data[];
23 /** Get the string header from the string data pointer. */
24 static struct dstring *dstrheader(const dchar *dstr) {
25 return (struct dstring *)(dstr - offsetof(struct dstring, data));
28 /** Get the correct size for a dstring with the given capacity. */
29 static size_t dstrsize(size_t capacity) {
30 return sizeof_flex(struct dstring, data, capacity + 1);
33 /** Allocate a dstring with the given contents. */
34 static dchar *dstralloc_impl(size_t capacity, size_t length, const char *data) {
35 // Avoid reallocations for small strings
40 struct dstring *header = malloc(dstrsize(capacity));
45 header->capacity = capacity;
46 header->length = length;
48 memcpy(header->data, data, length);
49 header->data[length] = '\0';
53 dchar *dstralloc(size_t capacity) {
54 return dstralloc_impl(capacity, 0, "");
57 dchar *dstrdup(const char *str) {
58 return dstrxdup(str, strlen(str));
61 dchar *dstrndup(const char *str, size_t n) {
62 return dstrxdup(str, strnlen(str, n));
65 dchar *dstrddup(const dchar *dstr) {
66 return dstrxdup(dstr, dstrlen(dstr));
69 dchar *dstrxdup(const char *str, size_t len) {
70 return dstralloc_impl(len, len, str);
73 size_t dstrlen(const dchar *dstr) {
74 return dstrheader(dstr)->length;
77 int dstreserve(dchar **dstr, size_t capacity) {
79 *dstr = dstralloc(capacity);
80 return *dstr ? 0 : -1;
83 struct dstring *header = dstrheader(*dstr);
85 if (capacity > header->capacity) {
86 capacity = bit_ceil(capacity + 1) - 1;
88 header = realloc(header, dstrsize(capacity));
92 header->capacity = capacity;
100 int dstresize(dchar **dstr, size_t length) {
101 if (dstreserve(dstr, length) != 0) {
105 struct dstring *header = dstrheader(*dstr);
106 header->length = length;
107 header->data[length] = '\0';
111 int dstrcat(dchar **dest, const char *src) {
112 return dstrxcat(dest, src, strlen(src));
115 int dstrncat(dchar **dest, const char *src, size_t n) {
116 return dstrxcat(dest, src, strnlen(src, n));
119 int dstrdcat(dchar **dest, const dchar *src) {
120 return dstrxcat(dest, src, dstrlen(src));
123 int dstrxcat(dchar **dest, const char *src, size_t len) {
124 size_t oldlen = dstrlen(*dest);
125 size_t newlen = oldlen + len;
127 if (dstresize(dest, newlen) != 0) {
131 memcpy(*dest + oldlen, src, len);
135 int dstrapp(dchar **str, char c) {
136 return dstrxcat(str, &c, 1);
139 int dstrcpy(dchar **dest, const char *src) {
140 return dstrxcpy(dest, src, strlen(src));
143 int dstrncpy(dchar **dest, const char *src, size_t n) {
144 return dstrxcpy(dest, src, strnlen(src, n));
147 int dstrdcpy(dchar **dest, const dchar *src) {
148 return dstrxcpy(dest, src, dstrlen(src));
151 int dstrxcpy(dchar **dest, const char *src, size_t len) {
152 if (dstresize(dest, len) != 0) {
156 memcpy(*dest, src, len);
160 char *dstrprintf(const char *format, ...) {
163 va_start(args, format);
164 dchar *str = dstrvprintf(format, args);
170 char *dstrvprintf(const char *format, va_list args) {
171 // Guess a capacity to try to avoid reallocating
172 dchar *str = dstralloc(2 * strlen(format));
177 if (dstrvcatf(&str, format, args) != 0) {
185 int dstrcatf(dchar **str, const char *format, ...) {
188 va_start(args, format);
189 int ret = dstrvcatf(str, format, args);
195 int dstrvcatf(dchar **str, const char *format, va_list args) {
196 // Guess a capacity to try to avoid calling vsnprintf() twice
197 size_t len = dstrlen(*str);
198 dstreserve(str, len + 2 * strlen(format));
199 size_t cap = dstrheader(*str)->capacity;
204 char *tail = *str + len;
205 int ret = vsnprintf(tail, cap - len + 1, format, args);
210 size_t tail_len = ret;
211 if (tail_len > cap - len) {
212 cap = len + tail_len;
213 if (dstreserve(str, cap) != 0) {
218 ret = vsnprintf(tail, tail_len + 1, format, copy);
219 if (ret < 0 || (size_t)ret != tail_len) {
220 bfs_bug("Length of formatted string changed");
227 struct dstring *header = dstrheader(*str);
228 header->length += tail_len;
236 int dstrescat(dchar **dest, const char *str, enum wesc_flags flags) {
237 return dstrnescat(dest, str, SIZE_MAX, flags);
240 int dstrnescat(dchar **dest, const char *str, size_t n, enum wesc_flags flags) {
241 size_t len = *dest ? dstrlen(*dest) : 0;
243 // Worst case growth is `ccc...` => $'\xCC\xCC\xCC...'
245 size_t cap = len + 4 * n + 3;
246 if (dstreserve(dest, cap) != 0) {
250 char *cur = *dest + len;
251 char *end = *dest + cap + 1;
252 cur = wordnesc(cur, end, str, n, flags);
253 bfs_assert(cur != end, "wordesc() result truncated");
255 return dstresize(dest, cur - *dest);
258 void dstrfree(dchar *dstr) {
260 free(dstrheader(dstr));