-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathoso89.h
273 lines (212 loc) · 8.12 KB
/
oso89.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
#pragma once
/* Heap-allocated string handling.
Inspired by antirez's sds and gingerBill's gb_string.h.
EXAMPLE
---------
oso *mystring = NULL;
osoput(&mystring, "Hello World");
puts((char *)mystring);
osoput(&mystring, "How about some pancakes?");
puts((char *)mystring);
osocatprintf(&mstring, " Sure! I'd like %d.", 5);
puts((char *)mystring);
osofree(mystring);
> Hello World!
> How about some pancakes?
> How about some pancakes? Sure! I'd like 5.
RULES
-------
1. You can use null as an empty `oso *` string.
2. But `char const *` or `oso **` arguments to oso functions can't be null.
3. You can always cast `oso *` to `char *`, but it might be null.
4. Don't call oso functions with arguments that have overlapping memory.
oso *mystring = NULL;
osolen(mystring); // OK, gives 0
osocat(&mystring, "waffles"); // OK
osolen(mystring); // OK, gives 7
osocat(&mystring, NULL); // Bad, crashes
osocat(NULL, "foo"); // Bad, crashes
osocatoso(&mystring, *mystring); // Bad, no idea what happens
NAMES
-------
osoput______ -> Replace the contents.
osocat______ -> Append to the contents.
______len -> Do it with an explicit length argument, so the
C-string doesn't have to be null-terminated.
______oso -> Do it with a second oso string.
______printf -> Do it by using printf.
ALLOC FAILURE
---------------
If an allocation fails (including failing to reallocate) the `oso *` will be
set to null. If you decide to handle memory allocation failures, you'll need
to check for that.
This means that if you run out of memory and call an oso function, you might
lose your string. But the upside is that you can more easily check for and
handle out-of-memory situations if they do happen. Because of how tedious it
traditionally is, lots of libc/UNIX C software doessn't bother trying to handle
out-of-memory situations at all.
*/
#include <stdarg.h>
#include <stddef.h>
#if (defined(__GNUC__) || defined(__clang__)) && defined(__has_attribute)
#if __has_attribute(format)
#define OSO_PRINTF(a, b) __attribute__((format(printf, a, b)))
#endif
#if __has_attribute(nonnull)
#define OSO_NONNULL(args) __attribute__((nonnull args))
#endif
#endif
#ifndef OSO_PRINTF
#define OSO_PRINTF(a, b)
#endif
#ifndef OSO_NONNULL
#define OSO_NONNULL(args)
#endif
/* clang-format off */
typedef struct oso oso;
void
osoput(oso **p, char const *cstr)
/* Copies the null-terminated string on the right side into the left, replacing
its contents.
If `p` points to a null pointer, or if there isn't enough capacity in it to
hold its new contents, it will be reallocated. The pointed-to pointer might
be different after the call.
oso *color = NULL;
osoput(&color, "red");
puts((char *)color); "red" */
OSO_NONNULL((1, 2));
void
osoputlen(oso **p, char const *cstr, size_t len)
/* Like `osoput()`, but you specify the length (number of non-null chars) for
the right side instead of it scanning for a null terminator.
having a null terminator. */
OSO_NONNULL((1, 2));
void
osoputoso(oso **p, oso const *other)
/* Like `osoput()`, but the right side is an oso. */
OSO_NONNULL((1));
void
osoputprintf(oso **p, char const *fmt, ...)
/* Like `osoput()`, but do it with a printf. */
OSO_NONNULL((1, 2)) OSO_PRINTF(2, 3);
void
osoputvprintf(oso **p, char const *fmt, va_list ap)
/* Like `osoput()`, but do it with a vprintf. */
OSO_NONNULL((1, 2)) OSO_PRINTF(2, 0);
void
osocat(oso **p, char const *cstr)
/* Appends the contents of the right side onto the left. The pointed-to pointer
will be reallocated if necessary.
oso *fungus = NULL;
osocat(&fungus, "mush");
osocat(&fungus, "room");
puts((char *)fungus); "mushroom" */
OSO_NONNULL((1, 2));
void
osocatlen(oso **p, char const *cstr, size_t len)
/* Like `osocat()`, but you specify the length (number of non-null chars) for
the right side instead of it scanning for a null terminator. */
OSO_NONNULL((1, 2));
void
osocatoso(oso **p, oso const *other)
/* Like `osocat()`, but the right side side is an oso. */
OSO_NONNULL((1));
void
osocatprintf(oso **p, char const *fmt, ...)
/* Like `osocat()`, but do it with a pritnf. */
OSO_NONNULL((1, 2)) OSO_PRINTF(2, 3);
void
osocatvprintf(oso **p, char const *fmt, va_list ap)
/* Like `osocat()`, but do it with a vprintf. */
OSO_NONNULL((1, 2)) OSO_PRINTF(2, 0);
void
osoensurecap(oso **p, size_t cap)
/* Ensure that the oso has at least `cap` memory allocated for its capacity.
The capacity is the number of characters it can hold in its allocated
memory, not counting the null terminator. A `cap` of 5 means there is room
for 5 characters, plus the null terminator.
This function doesn't care about the position of the null terminator in the
existing contents or the length number -- only the heap memory allocation.
oso *drink = NULL;
char const *cstring = "horchata";
size_t len = strlen(cstring);
osoensurecap(&drink, len);
memcpy((char *)drink, string, len);
((char *)drink)[len] = '\0';
osopokelen(drink, len); */
OSO_NONNULL((1));
void
osomakeroomfor(oso **p, size_t len)
/* Ensure that the oso has enough memory allocated for an additional `len`
characters, not counting the null terminator.
If you want to prepare to `memcpy()` 5 characters onto the end of an `oso`
that's currently 10 characters in length, call `osomakeroomfor(myoso, 5);`.
This only adjusts the heap memory allocation, not the null terminator or
length number.
Both `osoensurecap()` and `osomakeroomfor()` can be used to avoid repeated
heap reallactions by allocating it all at once before doing other
operations, like `osocat()`.
You can also thsese functions if want to to modify the string buffer
manually in your own code, and need to create some space in the buffer.
oso *dinner = NULL;
const char *potato = "potato";
const char *salad = " salad";
const char *wbeans = " with beans";
osoput(&dinner, potato);
// Avoid an extra reallocation here
osomakeroomfor(&dinner, strlen(salad) + strlen(wbeans));
osocat(&dinner, salad);
osocat(&dinner, wbeans); */
OSO_NONNULL((1));
void
osoclear(oso **p)
/* Sets the length number to 0, and write a null terminator at position 0.
Leaves allocated heap memory in place.
Unless the oso is null, in which case it doesn't do anything at all. */
OSO_NONNULL((1));
void
osofree(oso *s);
/* Frees the memory. Calling with null is allowed. */
void
osowipe(oso **p)
/* Frees the memory, except you give it a pointer to a pointer, and it
sets the pointed-to pointer to null for you when it's done. Calling it with
null is not allowed, but calling it with the pointed-to pointer as null is
OK.
osowipe(NULL); // Bad, will probably crash.
oso *potato = NULL;
osoput(&potato, "nice spuds");
osowipe(&potato); // OK.
assert(*potato == NULL); // true
osowipe(&potato); // OK. */
OSO_NONNULL((1));
void
osopokelen(oso *s, size_t len)
/* Manually updates length field. Doesn't do anything else for you. */
OSO_NONNULL((1));
size_t
osolen(oso const *s);
/* Bytes in use by the string (not including the null terminator.) */
size_t
osocap(oso const *s);
/* Bytes allocated on heap (not including the null terminator.) */
void
osolencap(oso const *s, size_t *out_len, size_t *out_cap)
/* Get both the len and the cap in one call. */
OSO_NONNULL((2, 3));
size_t
osoavail(oso const *s);
/* osocap(s) - osolen(s) */
void
osotrim(oso *s, char const *cut_set)
/* Remove the characters in `cut_set` from the beginning and ending of `s`. */
OSO_NONNULL((2));
void
ososwap(oso **a, oso **b)
/* Swaps the two pointers. Why bother making a function for this? In case you
need to debug a memory management problem in the future. You can put some
debug code in the definition. */
OSO_NONNULL((1, 2));
/* clang-format on */
#undef OSO_PRINTF
#undef OSO_NONNULL