MULE 0.0.1
Multi-Use Library Essentials
Loading...
Searching...
No Matches
str.c
1#include "str/str.h"
2
3#include <assert.h>
4#include <errno.h>
5#include <math.h>
6#include <stdarg.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11/* Grow so that `needed` content bytes fit. Amortised doubling. */
12static bool str_grow_(Str *s, size_t needed) {
13 if (needed <= s->capacity && s->data != NULL) return true;
14
15 size_t cap = s->capacity ? s->capacity : 15;
16 while (cap < needed) {
17 if (ckd_mul(&cap, cap, 2)) {
18 cap = needed;
19 break;
20 }
21 }
22
23 size_t bytes;
24 if (ckd_add(&bytes, cap, 1)) return false;
25
26 size_t old = s->data != NULL ? s->capacity + 1 : 0;
27 char *p = mem_realloc(s->alloc, s->data, old, bytes);
28 if (p == NULL) return false;
29 if (s->data == NULL) p[0] = '\0';
30
31 s->data = p;
32 s->capacity = cap;
33 return true;
34}
35
36bool str_with_capacity(Str *s, size_t capacity) {
37 *s = (Str){ .alloc = s->alloc };
38 return str_grow_(s, capacity);
39}
40
41bool str_from(Str *s, StrView v) {
42 *s = (Str){ .alloc = s->alloc };
43 return str_append(s, v);
44}
45
46bool str_from_cstr(Str *s, const char *cstr) {
47 return str_from(s, sv_from_cstr(cstr));
48}
49
50void str_free(Str *s) {
51 if (s->data != NULL)
52 s->data = mem_realloc(s->alloc, s->data, s->capacity + 1, 0);
53 *s = (Str){ .alloc = s->alloc };
54}
55
56bool str_reserve(Str *s, size_t additional) {
57 size_t needed;
58 if (ckd_add(&needed, s->len, additional)) return false;
59 return str_grow_(s, needed);
60}
61
63 if (s->data == NULL || s->capacity == s->len) return true;
64 char *p = mem_realloc(s->alloc, s->data, s->capacity + 1, s->len + 1);
65 if (p == NULL) return false;
66 s->data = p;
67 s->capacity = s->len;
68 return true;
69}
70
71void str_clear(Str *s) {
72 s->len = 0;
73 if (s->data != NULL) s->data[0] = '\0';
74}
75
76bool str_push(Str *s, char c) {
77 /* Locals, because a char store may alias `*s` and force reloads. */
78 char *data = s->data;
79 size_t len = s->len;
80 if (data != NULL && len < s->capacity) {
81 data[len] = c;
82 data[len + 1] = '\0';
83 s->len = len + 1;
84 return true;
85 }
86 return str_append(s, (StrView){ .data = &c, .len = 1 });
87}
88
89bool str_append(Str *s, StrView v) {
90 if (v.len == 0) return true;
91
92 /* Fast path: it fits, so the buffer cannot move under `v`. Locals, as in
93 * str_push, so the stores into `data` cannot force reloads of `*s`. */
94 char *data = s->data;
95 size_t len = s->len;
96 if (data != NULL && v.len <= s->capacity - len) {
97 memmove(data + len, v.data, v.len);
98 data[len + v.len] = '\0';
99 s->len = len + v.len;
100 return true;
101 }
102
103 /* `v` may point into our own buffer (e.g. str_append(&s, str_view(&s))).
104 * Remember its offset, since growing may move the allocation. */
105 const bool aliased = s->data != NULL &&
106 v.data >= s->data &&
107 v.data <= s->data + s->len;
108 const size_t off = aliased ? (size_t)(v.data - s->data) : 0;
109
110 if (!str_reserve(s, v.len)) return false;
111
112 const char *src = aliased ? s->data + off : v.data;
113 memmove(s->data + s->len, src, v.len);
114 s->len += v.len;
115 s->data[s->len] = '\0';
116 return true;
117}
118
119bool str_append_cstr(Str *s, const char *cstr) {
120 return str_append(s, sv_from_cstr(cstr));
121}
122
123bool str_appendf(Str *s, const char *fmt, ...) {
124 va_list ap;
125 va_start(ap, fmt);
126 bool ok = str_vappendf(s, fmt, ap);
127 va_end(ap);
128 return ok;
129}
130
131bool str_vappendf(Str *s, const char *fmt, va_list ap) {
132 va_list ap2;
133 va_copy(ap2, ap);
134
135 /* Format straight into the spare capacity; most calls fit and stop here. */
136 size_t room = s->data != NULL ? s->capacity - s->len + 1 : 0;
137 char *end = room != 0 ? s->data + s->len : NULL;
138 int n = vsnprintf(end, room, fmt, ap);
139 bool ok = n >= 0 && (size_t)n < room;
140 if (ok) {
141 s->len += (size_t)n;
142 } else {
143 /* The attempt overwrote the terminator; restore it before failing. */
144 if (end != NULL) *end = '\0';
145 if (n >= 0 && str_reserve(s, (size_t)n)) {
146 vsnprintf(s->data + s->len, (size_t)n + 1, fmt, ap2);
147 s->len += (size_t)n;
148 ok = true;
149 }
150 }
151
152 va_end(ap2);
153 return ok;
154}
155
156bool str_append_file(Str *s, FILE *f) {
157 /* Read into a scratch Str so a failure part-way leaves `s` untouched. */
158 Str tmp = { .alloc = s->alloc };
159 bool ok = true;
160 for (;;) {
161 if (!str_reserve(&tmp, 4096)) {
162 ok = false;
163 break;
164 }
165 size_t room = tmp.capacity - tmp.len;
166 size_t n = fread(tmp.data + tmp.len, 1, room, f);
167 tmp.len += n;
168 if (n < room) {
169 ok = !ferror(f);
170 break;
171 }
172 }
173
174 if (ok && s->data == NULL) {
175 /* `s` owns nothing, so take the buffer instead of copying it. */
176 tmp.data[tmp.len] = '\0';
177 *s = tmp;
178 return true;
179 }
180 ok = ok && str_append(s, str_view(&tmp));
181 str_free(&tmp);
182 return ok;
183}
184
185bool str_read_line(Str *s, FILE *f) {
186 /* Read after the current contents, so failure can restore them. */
187 size_t old = s->len;
188 bool got = false, ok = true;
189 flockfile(f);
190 for (int c; (c = getc_unlocked(f)) != EOF;) {
191 got = true;
192 if (c == '\n') break;
193 if (!str_push(s, (char)c)) {
194 ok = false;
195 break;
196 }
197 }
198 funlockfile(f);
199
200 if (!ok || !got || ferror(f)) {
201 s->len = old;
202 if (s->data != NULL) s->data[old] = '\0';
203 return false;
204 }
205 if (s->data == NULL) return true; /* an empty first line */
206 memmove(s->data, s->data + old, s->len - old);
207 s->len -= old;
208 s->data[s->len] = '\0';
209 return true;
210}
211
213 return (StrView){ .data = str_cstr(s), .len = s->len };
214}
215
216const char *str_cstr(const Str *s) {
217 return s->data != NULL ? s->data : "";
218}
219
221 return a.len == b.len && (a.len == 0 || memcmp(a.data, b.data, a.len) == 0);
222}
223
224StrView sv_from_cstr(const char *cstr) {
225 if (cstr == NULL) return (StrView){ .data = "", .len = 0 };
226 return (StrView){ .data = cstr, .len = strlen(cstr) };
227}
228
229/* --- Views --------------------------------------------------------------- */
230
231size_t sv_find_byte(StrView v, char c) {
232 if (v.len == 0) return SV_NPOS;
233 const char *p = memchr(v.data, c, v.len);
234 return p != NULL ? (size_t)(p - v.data) : SV_NPOS;
235}
236
237size_t sv_find(StrView v, StrView needle) {
238 if (needle.len == 0) return 0;
239 if (needle.len > v.len) return SV_NPOS;
240
241 /* memchr to each candidate first byte, then compare the rest. */
242 size_t last = v.len - needle.len;
243 for (size_t i = 0; i <= last; i++) {
244 const char *p = memchr(v.data + i, needle.data[0], last - i + 1);
245 if (p == NULL) return SV_NPOS;
246 i = (size_t)(p - v.data);
247 if (memcmp(p + 1, needle.data + 1, needle.len - 1) == 0) return i;
248 }
249 return SV_NPOS;
250}
251
253 size_t n = a.len < b.len ? a.len : b.len;
254 int c = n != 0 ? memcmp(a.data, b.data, n) : 0;
255 if (c != 0) return c;
256 return (a.len > b.len) - (a.len < b.len);
257}
258
260 return prefix.len <= v.len && str_eq(sv_slice(v, 0, prefix.len), prefix);
261}
262
263bool sv_ends_with(StrView v, StrView suffix) {
264 return suffix.len <= v.len &&
265 str_eq(sv_slice(v, v.len - suffix.len, v.len), suffix);
266}
267
268StrView sv_slice(StrView v, size_t from, size_t to) {
269 assert(from <= to && to <= v.len);
270 /* An empty view may hold a null pointer, and null + 0 is undefined. */
271 return (StrView){ .data = v.len != 0 ? v.data + from : v.data,
272 .len = to - from };
273}
274
275static bool sv_space_(char c) {
276 return c == ' ' || (c >= '\t' && c <= '\r');
277}
278
280 size_t from = 0, to = v.len;
281 while (from < to && sv_space_(v.data[from])) from++;
282 while (to > from && sv_space_(v.data[to - 1])) to--;
283 return sv_slice(v, from, to);
284}
285
286bool sv_split_next(StrView *rest, StrView sep, StrView *piece) {
287 assert(sep.len != 0);
288 if (rest->len == 0) return false;
289
290 size_t at = sv_find(*rest, sep);
291 if (at == SV_NPOS) {
292 *piece = *rest;
293 *rest = sv_slice(*rest, rest->len, rest->len);
294 } else {
295 *piece = sv_slice(*rest, 0, at);
296 *rest = sv_slice(*rest, at + sep.len, rest->len);
297 }
298 return true;
299}
300
301static bool sv_in_set_(const uint64_t set[4], char c) {
302 unsigned char u = (unsigned char)c;
303 return (set[u >> 6] >> (u & 63)) & 1;
304}
305
306bool sv_token_next(StrView *rest, StrView delims, StrView *token) {
307 /* One bit per byte value, like strspn builds, so each test is O(1). */
308 uint64_t set[4] = { 0 };
309 for (size_t i = 0; i < delims.len; i++) {
310 unsigned char c = (unsigned char)delims.data[i];
311 set[c >> 6] |= UINT64_C(1) << (c & 63);
312 }
313
314 size_t from = 0, n = rest->len;
315 while (from < n && sv_in_set_(set, rest->data[from])) from++;
316 size_t to = from;
317 while (to < n && !sv_in_set_(set, rest->data[to])) to++;
318
319 if (from == to) {
320 *rest = sv_slice(*rest, n, n);
321 return false;
322 }
323 *token = sv_slice(*rest, from, to);
324 *rest = sv_slice(*rest, to, n);
325 return true;
326}
327
328/* --- Parsing ------------------------------------------------------------- */
329
330bool sv_parse_i64(StrView v, int64_t *out) {
331 size_t i = 0;
332 bool neg = false;
333 if (v.len > 0 && (v.data[0] == '-' || v.data[0] == '+')) {
334 neg = v.data[0] == '-';
335 i = 1;
336 }
337 if (i == v.len) return false;
338
339 uint64_t mag = 0;
340 for (; i < v.len; i++) {
341 unsigned d = (unsigned)(unsigned char)v.data[i] - '0';
342 if (d > 9 || ckd_mul(&mag, mag, 10) || ckd_add(&mag, mag, d))
343 return false;
344 }
345 if (mag > (uint64_t)INT64_MAX + neg) return false;
346 /* -(mag - 1) - 1 reaches INT64_MIN without overflowing on the way. */
347 *out = neg ? -(int64_t)(mag - 1) - 1 : (int64_t)mag;
348 return true;
349}
350
351bool sv_parse_f64(StrView v, double *out) {
352 char buf[256];
353 if (v.len == 0 || v.len >= sizeof buf || sv_space_(v.data[0])) return false;
354 memcpy(buf, v.data, v.len);
355 buf[v.len] = '\0';
356
357 char *end;
358 errno = 0;
359 double d = strtod(buf, &end);
360 if (end != buf + v.len || (errno == ERANGE && isinf(d))) return false;
361 *out = d;
362 return true;
363}
void * mem_realloc(const Allocator *a, void *old, size_t old_size, size_t new_size)
Resize old from old_size to new_size bytes through a.
Definition mem.c:14
size_t len
Number of bytes in the view.
Definition str.h:74
size_t capacity
Content bytes that fit before the next realloc.
Definition str.h:67
const char * data
First byte.
Definition str.h:73
const Allocator * alloc
Source of data.
Definition str.h:68
size_t len
Bytes of content, excluding the terminator.
Definition str.h:66
char * data
Buffer, or null when nothing is allocated.
Definition str.h:65
const char * str_cstr(const Str *s)
NUL-terminated contents of s, for C APIs such as fopen and puts.
Definition str.c:216
size_t sv_find(StrView v, StrView needle)
Index of the first needle in v.
Definition str.c:237
bool str_shrink_to_fit(Str *s)
Shrink the allocation to exactly len + 1 bytes.
Definition str.c:62
StrView sv_from_cstr(const char *cstr)
View of cstr, measured with strlen.
Definition str.c:224
bool sv_parse_f64(StrView v, double *out)
Parse all of v as a double, in any form strtod accepts in the C locale.
Definition str.c:351
void str_clear(Str *s)
Set the length to zero.
Definition str.c:71
StrView str_view(const Str *s)
Borrow the whole of s.
Definition str.c:212
bool sv_token_next(StrView *rest, StrView delims, StrView *token)
strtok without the mutation: skip delims, then cut the next run of other bytes.
Definition str.c:306
bool str_from(Str *s, StrView v)
Initialise s as a copy of v.
Definition str.c:41
#define SV_NPOS
Returned by the find functions when there is no match.
Definition str.h:217
void str_free(Str *s)
Free the buffer and reset s to the zero state.
Definition str.c:50
bool str_append_file(Str *s, FILE *f)
Append everything left to read from f.
Definition str.c:156
bool str_appendf(Str *s, const char *fmt,...)
Append the printf-style expansion of fmt.
Definition str.c:123
bool str_push(Str *s, char c)
Append the single byte c.
Definition str.c:76
StrView sv_trim(StrView v)
v without leading and trailing ASCII whitespace: space, \t, \n, \v, \f and \r.
Definition str.c:279
bool sv_ends_with(StrView v, StrView suffix)
True when v ends with suffix.
Definition str.c:263
bool str_read_line(Str *s, FILE *f)
Replace the contents of s with the next line of f, without its \n.
Definition str.c:185
bool str_reserve(Str *s, size_t additional)
Ensure additional more bytes fit without another allocation.
Definition str.c:56
bool str_append(Str *s, StrView v)
Append v.
Definition str.c:89
bool sv_starts_with(StrView v, StrView prefix)
True when v begins with prefix.
Definition str.c:259
bool str_eq(StrView a, StrView b)
True when a and b hold the same bytes.
Definition str.c:220
bool sv_split_next(StrView *rest, StrView sep, StrView *piece)
Cut the piece before sep off rest.
Definition str.c:286
bool sv_parse_i64(StrView v, int64_t *out)
Parse all of v as a base-10 integer with an optional sign.
Definition str.c:330
bool str_from_cstr(Str *s, const char *cstr)
Initialise s as a copy of cstr.
Definition str.c:46
bool str_with_capacity(Str *s, size_t capacity)
Initialise s as empty with room for capacity bytes of content.
Definition str.c:36
StrView sv_slice(StrView v, size_t from, size_t to)
Bytes from up to but not including to.
Definition str.c:268
int sv_cmp(StrView a, StrView b)
Byte order like memcmp.
Definition str.c:252
size_t sv_find_byte(StrView v, char c)
Index of the first c in v.
Definition str.c:231
bool str_vappendf(Str *s, const char *fmt, va_list ap)
str_appendf() for a va_list, like vprintf.
Definition str.c:131
bool str_append_cstr(Str *s, const char *cstr)
Append cstr.
Definition str.c:119
Owned, growable byte string.
Definition str.h:64
Non-owning view of bytes; handy for passing slices and literals around.
Definition str.h:72