ds 0.10.0
A C library to safely yet efficiently work with UTF-8–encoded, growable dynamic strings.
string.c
Go to the documentation of this file.
1//! @file ds/string.c
2//! @author Avinash Maddikonda (svasssakavi@gmail.com)
3//! @brief Implementation of the @ref ds_string_t "String" type, related
4//! constants and functions.
5//! @since 0.10.0
6//! @date 2023-08-31
7
8#include <stdbool.h>
9#include <stddef.h>
10#include <string.h>
11
12#include "ds/char.h"
13#include "ds/cstr.h"
14#include "ds/helpers.h"
15#include "ds/result.h"
16#include "ds/string.h"
17#include "ds/usize.h"
18
19/// @internal
20/// @brief Tiny dynamic strings are dumb. Skip to 8 since the element (`char`)
21/// size is `1`, because any heap allocator is likely to round up a request of
22/// less than `8` bytes to at least `8` bytes.
23static ds_usize_t const DS_STRING_MIN_NON_ZERO_CAP = 8;
24
25/// @internal
26/// @brief Return the minimum capacity required to grow @p self amortized.
27/// @param self The @ref ds_string_t "String" to grow amortized.
28/// @param required_cap The minimum required capacity (assumed to be greater
29/// than @ref ds_string_capacity).
30/// @return The amortized capacity (always greater than or equal to the maximum
31/// of @ref ds_string_capacity and @p required_cap).
32static ds_usize_t
33ds_string_get_amortized_capacity (ds_string_t const *const self,
34 ds_usize_t const required_cap)
35{
37
38 ds_usize_t const next_cap = ds_string_capacity (*self) * 2;
39 ds_usize_t const possible_cap = DS_HELPERS_MAX (next_cap, required_cap);
40
41 return DS_HELPERS_MAX (DS_STRING_MIN_NON_ZERO_CAP, possible_cap);
42}
43
44/// @internal
45/// @brief Grow @p self in size to contain exactly or at least
46/// `ds_string_len (self) + additional` bytes, depending on @p exact.
47/// @param[out] self Pointer to the @ref ds_string_t "String" to be grown.
48/// @param additional Number of additional bytes to reserve.
49/// @param exact Whether the allocation should be exact or amortized.
50/// @return @ref DS_RESULT_OK "Ok" on success.
51/// @return @ref DS_RESULT_ERR_PTR_IS_NULL "Err(PtrIsNull)" if @p self is
52/// `NULL`.
53/// @return @ref DS_RESULT_ERR_MEM_ALLOC_FAILED "Err(MemAllocFailed)" if a
54/// memory allocation fails.
55static ds_result_t
56ds_string_grow (ds_string_t *const self, ds_usize_t const additional,
57 bool const exact)
58{
60
61 ds_usize_t const len = ds_string_len (*self);
62 ds_usize_t const cap = ds_string_capacity (*self);
63
64 ds_usize_t const required_cap = len + additional;
65 if (required_cap < len)
66 {
68 }
69
70 if (required_cap > cap)
71 {
72 ds_usize_t const new_cap
73 = exact ? required_cap
74 : ds_string_get_amortized_capacity (self, required_cap);
75
77 ds_cstr_reallocate (&self->buf, cap, &self->buf, new_cap));
78 self->cap = new_cap;
79 }
80
81 return DS_RESULT_OK;
82}
83
86{
88}
89
91ds_string_with_capacity (ds_string_t *const self, ds_usize_t const capacity)
92{
94
95 *self = ds_string_new ();
96 return ds_string_reserve (self, capacity);
97}
98
101 ds_cstr_mut_t *const buf_ptr,
102 ds_usize_t *const len_ptr, ds_usize_t *const cap_ptr)
103{
108
109 *buf_ptr = ds_string_as_mut_ptr (*self);
110 *len_ptr = ds_string_len (*self);
111 *cap_ptr = ds_string_capacity (*self);
112
113 *self = ds_string_new ();
114 return DS_RESULT_OK;
115}
116
119 ds_usize_t const capacity)
120{
121 return (ds_string_t){ buf, length, capacity };
122}
123
125ds_string_push_str (ds_string_t *const self, ds_cstr_t const string,
126 ds_usize_t const string_len)
127{
129
130 ds_usize_t const len = ds_string_len (*self);
131 return ds_string_insert_str (self, len, string, string_len);
132}
133
136{
137 return self.cap;
138}
139
141ds_string_reserve (ds_string_t *const self, ds_usize_t const additional)
142{
143 return ds_string_grow (self, additional, false);
144}
145
147ds_string_reserve_exact (ds_string_t *const self, ds_usize_t const additional)
148{
149 return ds_string_grow (self, additional, true);
150}
151
154{
156 return ds_string_shrink_to (self, ds_string_len (*self));
157}
158
160ds_string_shrink_to (ds_string_t *const self, ds_usize_t const min_capacity)
161{
163
164 ds_usize_t const len = ds_string_len (*self);
165 ds_usize_t const cap = ds_string_capacity (*self);
166 ds_usize_t const new_cap = DS_HELPERS_CLAMP (min_capacity, len, cap);
167
169 ds_cstr_reallocate (&self->buf, cap, &self->buf, new_cap));
170 self->cap = new_cap;
171
172 return DS_RESULT_OK;
173}
174
176ds_string_push (ds_string_t *const self, ds_char_t const chr)
177{
179 return ds_string_insert (self, ds_string_len (*self), chr);
180}
181
184{
186
187 ds_usize_t const len = ds_string_len (*self);
188 self->len = DS_HELPERS_MIN (new_len, len);
189
190 return DS_RESULT_OK;
191}
192
194ds_string_pop (ds_string_t *const self, ds_char_t *const chr_ptr)
195{
197 return ds_string_remove (self, ds_string_len (*self) - 1, chr_ptr);
198}
199
202 ds_char_t *const chr_ptr)
203{
205
206 ds_usize_t const len = ds_string_len (*self);
207 if (idx >= len)
208 {
210 }
211
212 ds_cstr_t const buf = ds_string_as_ptr (*self);
213 ds_usize_t const new_len = len - 1;
214 ds_cstr_t const idx_ptr = buf + idx * DS_CHAR_BYTES;
215
216 if (ds_helpers_is_instance (chr_ptr))
217 {
218 *chr_ptr = *idx_ptr;
219 }
220 memmove ((void *)idx_ptr, idx_ptr + DS_CHAR_BYTES, new_len - idx);
221 self->len = new_len;
222
223 return DS_RESULT_OK;
224}
225
228{
230
232 ds_cstr_t const buf = ds_string_as_ptr (*self);
233
234 while (idx < ds_string_len (*self))
235 {
236 if (match (buf[idx]))
237 {
238 ++idx;
239 }
240 else
241 {
243 DS_RESULT_PROPAGATE_ERR (ds_string_remove (self, idx, &chr));
244 }
245 }
246
247 return DS_RESULT_OK;
248}
249
252 ds_char_t const chr)
253{
254 return ds_string_insert_str (self, idx, &chr, DS_CHAR_BYTES);
255}
256
259 ds_cstr_t const string, ds_usize_t const string_len)
260{
262
263 ds_usize_t const len = ds_string_len (*self);
264 if (idx > len)
265 {
267 }
268
269 DS_RESULT_PROPAGATE_ERR (ds_string_reserve (self, string_len));
270 ds_cstr_t const buf = ds_string_as_ptr (*self);
271 ds_cstr_t const idx_ptr = buf + idx * DS_CHAR_BYTES;
272
273 memmove ((void *)(idx_ptr + string_len), idx_ptr, len - idx);
274 memmove ((void *)idx_ptr, string, string_len);
275
276 self->len += string_len;
277 return DS_RESULT_OK;
278}
279
282{
283 return self.len;
284}
285
286bool
288{
289 return ds_string_len (self) == DS_USIZE_MIN;
290}
291
294 ds_string_t *const target)
295{
298
299 *target = ds_string_new ();
300 ds_usize_t const len = ds_string_len (*self);
301
302 if (idx > len)
303 {
305 }
306
307 ds_cstr_t const buf = ds_string_as_ptr (*self);
308 ds_cstr_t const idx_ptr = buf + idx * DS_CHAR_BYTES;
309
310 DS_RESULT_PROPAGATE_ERR (ds_string_push_str (target, idx_ptr, len - idx));
311 self->len = idx;
312
313 return DS_RESULT_OK;
314}
315
318{
319 return ds_string_truncate (self, DS_USIZE_MIN);
320}
321
323ds_string_clone (ds_string_t const self, ds_string_t *const target)
324{
325 return ds_string_clone_from (target, self);
326}
327
330{
331 return ds_string_from (self, ds_string_as_ptr (source),
332 ds_string_len (source));
333}
334
337{
338 return ds_string_new ();
339}
340
342ds_string_from (ds_string_t *const self, ds_cstr_t const string,
343 ds_usize_t const string_len)
344{
346
347 *self = ds_string_new ();
348 return ds_string_push_str (self, string, string_len);
349}
350
351bool
352ds_string_eq (ds_string_t const self, ds_string_t const other)
353{
354 ds_usize_t const len = ds_string_len (self);
355 if (len != ds_string_len (other))
356 {
357 return false;
358 }
359
360 ds_cstr_t const buf = ds_string_as_ptr (self);
361 ds_cstr_t const other_buf = ds_string_as_ptr (other);
362
363 for (ds_usize_t i = DS_USIZE_MIN; i < len; i++)
364 {
365 if (buf[i] != other_buf[i])
366 {
367 return false;
368 }
369 }
370 return true;
371}
372
373bool
374ds_string_ne (ds_string_t const self, ds_string_t const other)
375{
376 return !ds_string_eq (self, other);
377}
378
381{
382 return (ds_cstr_t)ds_string_as_mut_ptr (self);
383}
384
387{
388 return self.buf;
389}
390
393{
395
398
399 *self = ds_string_new ();
400 return DS_RESULT_OK;
401}
Declaration of the char type, related constants and functions.
#define DS_CHAR_BYTES
The size of char in bytes.
Definition: char.h:560
char ds_char_t
A character type.
Definition: char.h:30
bool(* ds_predicate_char_t)(ds_char_t)
A function pointer type for char predicate functions that return true or false depending on the passe...
Definition: char.h:64
#define DS_CHAR_NUL
The null character.
Definition: char.h:593
Declaration of the C-string types, related constants and functions.
ds_result_t ds_cstr_reallocate(ds_cstr_mut_t *src_cstr_ptr, ds_usize_t src_size, ds_cstr_mut_t *dst_cstr_ptr, ds_usize_t dst_size)
Attempts to reallocate (extend, shrink) the C-string.
Definition: cstr.c:41
ds_char_t const * ds_cstr_t
An immutable C-string type.
Definition: cstr.h:28
ds_char_t * ds_cstr_mut_t
A mutable C-string type.
Definition: cstr.h:37
ds_result_t ds_cstr_deallocate(ds_cstr_mut_t *self)
Deallocates the C-string referenced by self.
Definition: cstr.c:84
Declaration of helper constants and functions.
#define DS_HELPERS_MIN(self, other)
Compares and returns the minimum of two values.
Definition: helpers.h:61
#define DS_HELPERS_MAX(self, other)
Compares and returns the maximum of two values.
Definition: helpers.h:77
bool ds_helpers_is_instance(void const *ptr)
Returns true if ptr is a valid non-NULL pointer.
Definition: helpers.c:19
#define DS_HELPERS_CLAMP(self, min, max)
Restrict a value to a certain interval.
Definition: helpers.h:97
Declaration of the Result enumeration type, related constants and functions.
ds_result_t
Result is a type that represents either success (Ok) or failure (a DS_RESULT_ERR_* variant).
Definition: result.h:26
@ DS_RESULT_OK
Represents success.
Definition: result.h:28
@ DS_RESULT_ERR_OUT_OF_RANGE
Represents failure due to encountering an invalid value outside the expected range of valid values.
Definition: result.h:39
#define DS_RESULT_PROPAGATE_IF_NULL(ptr)
Propagates (instantly, possibly early, returns) DS_RESULT_ERR_PTR_IS_NULL if and only if ptr is NULL.
Definition: result.h:162
#define DS_RESULT_PROPAGATE_ERR(result)
Propagates (instantly, possibly early, returns) evaluated result to the calling function only if it i...
Definition: result.h:140
ds_result_t ds_string_reserve(ds_string_t *const self, ds_usize_t const additional)
Reserves capacity for at least additional bytes more than self's current length. The allocator may re...
Definition: string.c:141
bool ds_string_is_empty(ds_string_t const self)
Returns true if self has a length of zero, and false otherwise.
Definition: string.c:287
ds_result_t ds_string_with_capacity(ds_string_t *const self, ds_usize_t const capacity)
Creates a new empty String with at least the specified capacity.
Definition: string.c:91
ds_result_t ds_string_insert(ds_string_t *const self, ds_usize_t const idx, ds_char_t const chr)
Inserts chr into self at a byte position.
Definition: string.c:251
ds_result_t ds_string_clone_from(ds_string_t *const self, ds_string_t const source)
Performs copy-assignment from source.
Definition: string.c:329
ds_result_t ds_string_into_raw_parts(ds_string_t *const self, ds_cstr_mut_t *const buf_ptr, ds_usize_t *const len_ptr, ds_usize_t *const cap_ptr)
Decomposes self into its raw components.
Definition: string.c:100
ds_result_t ds_string_retain(ds_string_t *const self, ds_predicate_char_t const match)
Retains only the characters specified by the predicate.
Definition: string.c:227
ds_string_t ds_string_default(void)
Creates an empty String.
Definition: string.c:336
ds_cstr_mut_t ds_string_as_mut_ptr(ds_string_t const self)
Converts self to a mutable raw pointer.
Definition: string.c:386
ds_result_t ds_string_clear(ds_string_t *const self)
Truncates self, removing all contents.
Definition: string.c:317
bool ds_string_ne(ds_string_t const self, ds_string_t const other)
This method tests for self and other values to not be equal.
Definition: string.c:374
ds_result_t ds_string_insert_str(ds_string_t *const self, ds_usize_t const idx, ds_cstr_t const string, ds_usize_t const string_len)
Inserts a string slice into self at a byte position.
Definition: string.c:258
ds_result_t ds_string_push(ds_string_t *const self, ds_char_t const chr)
Appends chr to the end of self.
Definition: string.c:176
ds_result_t ds_string_pop(ds_string_t *const self, ds_char_t *const chr_ptr)
Removes the last character from the string buffer and retrieves it.
Definition: string.c:194
ds_result_t ds_string_clone(ds_string_t const self, ds_string_t *const target)
Retrieves a copy of self.
Definition: string.c:323
ds_result_t ds_string_reserve_exact(ds_string_t *const self, ds_usize_t const additional)
Reserves the minimum capacity for at least additional bytes more than self's current length....
Definition: string.c:147
ds_result_t ds_string_drop(ds_string_t *const self)
Disposes of a value.
Definition: string.c:392
ds_result_t ds_string_truncate(ds_string_t *self, ds_usize_t new_len)
Shortens self to new_len.
Definition: string.c:183
ds_string_t ds_string_new(void)
Creates a new empty String.
Definition: string.c:85
ds_usize_t ds_string_len(ds_string_t const self)
Returns the length of self, in bytes, not graphemes. In other words, it might not be what a human con...
Definition: string.c:281
ds_usize_t ds_string_capacity(ds_string_t const self)
Returns self's capacity, in bytes.
Definition: string.c:135
ds_result_t ds_string_push_str(ds_string_t *const self, ds_cstr_t const string, ds_usize_t const string_len)
Appends string onto the end of self.
Definition: string.c:125
bool ds_string_eq(ds_string_t const self, ds_string_t const other)
This method tests for self and other values to be equal.
Definition: string.c:352
ds_cstr_t ds_string_as_ptr(ds_string_t const self)
Converts self to a raw pointer.
Definition: string.c:380
ds_result_t ds_string_split_off(ds_string_t *const self, ds_usize_t const idx, ds_string_t *const target)
Splits self into two at the given byte index.
Definition: string.c:293
ds_result_t ds_string_shrink_to(ds_string_t *const self, ds_usize_t const min_capacity)
Shrinks the capacity of self with a lower bound.
Definition: string.c:160
ds_result_t ds_string_remove(ds_string_t *const self, ds_usize_t const idx, ds_char_t *const chr_ptr)
Removes a char from self at a byte position and retrieves it.
Definition: string.c:201
ds_result_t ds_string_shrink_to_fit(ds_string_t *const self)
Shrinks the capacity of self to match its length.
Definition: string.c:153
ds_string_t ds_string_from_raw_parts(ds_cstr_mut_t const buf, ds_usize_t const length, ds_usize_t const capacity)
Creates a new String from a length, capacity, and buf pointer.
Definition: string.c:118
ds_result_t ds_string_from(ds_string_t *const self, ds_cstr_t const string, ds_usize_t const string_len)
Converts a C-string into a String.
Definition: string.c:342
Declaration of the String type, related constants and functions.
A growable string. Will be UTF-8–encoded in the future.
Definition: string.h:163
ds_usize_t cap
The size of the buffer in bytes.
Definition: string.h:181
ds_cstr_mut_t buf
Points to an internal buffer String uses to store its data.
Definition: string.h:169
ds_usize_t len
The number of bytes currently stored in the buffer.
Definition: string.h:175
Declaration of the usize type, related constants and functions.
#define DS_USIZE_MIN
The smallest value that can be represented by usize.
Definition: usize.h:49
size_t ds_usize_t
The pointer-sized unsigned integer type.
Definition: usize.h:26