1#include <stdarg.h>
2#include <string.h>
3#include <assert.h>
4#include <string.h>
5#include <stdio.h>
6#include <stdlib.h>
7#include <stdint.h>
8#include <limits.h>
9
10#include "config.h"
11#include "cmark_ctype.h"
12#include "buffer.h"
13
14/* Used as default value for cmark_strbuf->ptr so that people can always
15 * assume ptr is non-NULL and zero terminated even for new cmark_strbufs.
16 */
17unsigned char cmark_strbuf__initbuf[1];
18
19#ifndef MIN
20#define MIN(x, y) ((x < y) ? x : y)
21#endif
22
23void cmark_strbuf_init(cmark_mem *mem, cmark_strbuf *buf,
24 bufsize_t initial_size) {
25 buf->mem = mem;
26 buf->asize = 0;
27 buf->size = 0;
28 buf->ptr = cmark_strbuf__initbuf;
29
30 if (initial_size > 0)
31 cmark_strbuf_grow(buf, initial_size);
32}
33
34static CMARK_INLINE void S_strbuf_grow_by(cmark_strbuf *buf, bufsize_t add) {
35 cmark_strbuf_grow(buf, buf->size + add);
36}
37
38void cmark_strbuf_grow(cmark_strbuf *buf, bufsize_t target_size) {
39 assert(target_size > 0);
40
41 if (target_size < buf->asize)
42 return;
43
44 if (target_size > (bufsize_t)(INT32_MAX / 2)) {
45 fprintf(stderr,
46 "[cmark] cmark_strbuf_grow requests buffer with size > %d, aborting\n",
47 (INT32_MAX / 2));
48 abort();
49 }
50
51 /* Oversize the buffer by 50% to guarantee amortized linear time
52 * complexity on append operations. */
53 bufsize_t new_size = target_size + target_size / 2;
54 new_size += 1;
55 new_size = (new_size + 7) & ~7;
56
57 buf->ptr = (unsigned char *)buf->mem->realloc(buf->asize ? buf->ptr : NULL,
58 new_size);
59 buf->asize = new_size;
60}
61
62bufsize_t cmark_strbuf_len(const cmark_strbuf *buf) { return buf->size; }
63
64void cmark_strbuf_free(cmark_strbuf *buf) {
65 if (!buf)
66 return;
67
68 if (buf->ptr != cmark_strbuf__initbuf)
69 buf->mem->free(buf->ptr);
70
71 cmark_strbuf_init(buf->mem, buf, 0);
72}
73
74void cmark_strbuf_clear(cmark_strbuf *buf) {
75 buf->size = 0;
76
77 if (buf->asize > 0)
78 buf->ptr[0] = '\0';
79}
80
81void cmark_strbuf_set(cmark_strbuf *buf, const unsigned char *data,
82 bufsize_t len) {
83 if (len <= 0 || data == NULL) {
84 cmark_strbuf_clear(buf);
85 } else {
86 if (data != buf->ptr) {
87 if (len >= buf->asize)
88 cmark_strbuf_grow(buf, len);
89 memmove(buf->ptr, data, len);
90 }
91 buf->size = len;
92 buf->ptr[buf->size] = '\0';
93 }
94}
95
96void cmark_strbuf_sets(cmark_strbuf *buf, const char *string) {
97 cmark_strbuf_set(buf, (const unsigned char *)string,
98 string ? strlen(string) : 0);
99}
100
101void cmark_strbuf_putc(cmark_strbuf *buf, int c) {
102 S_strbuf_grow_by(buf, 1);
103 buf->ptr[buf->size++] = (unsigned char)(c & 0xFF);
104 buf->ptr[buf->size] = '\0';
105}
106
107void cmark_strbuf_put(cmark_strbuf *buf, const unsigned char *data,
108 bufsize_t len) {
109 if (len <= 0)
110 return;
111
112 S_strbuf_grow_by(buf, len);
113 memmove(buf->ptr + buf->size, data, len);
114 buf->size += len;
115 buf->ptr[buf->size] = '\0';
116}
117
118void cmark_strbuf_puts(cmark_strbuf *buf, const char *string) {
119 cmark_strbuf_put(buf, (const unsigned char *)string, strlen(string));
120}
121
122void cmark_strbuf_copy_cstr(char *data, bufsize_t datasize,
123 const cmark_strbuf *buf) {
124 bufsize_t copylen;
125
126 assert(buf);
127 if (!data || datasize <= 0)
128 return;
129
130 data[0] = '\0';
131
132 if (buf->size == 0 || buf->asize <= 0)
133 return;
134
135 copylen = buf->size;
136 if (copylen > datasize - 1)
137 copylen = datasize - 1;
138 memmove(data, buf->ptr, copylen);
139 data[copylen] = '\0';
140}
141
142void cmark_strbuf_swap(cmark_strbuf *buf_a, cmark_strbuf *buf_b) {
143 cmark_strbuf t = *buf_a;
144 *buf_a = *buf_b;
145 *buf_b = t;
146}
147
148unsigned char *cmark_strbuf_detach(cmark_strbuf *buf) {
149 unsigned char *data = buf->ptr;
150
151 if (buf->asize == 0) {
152 /* return an empty string */
153 return (unsigned char *)buf->mem->calloc(1, 1);
154 }
155
156 cmark_strbuf_init(buf->mem, buf, 0);
157 return data;
158}
159
160int cmark_strbuf_cmp(const cmark_strbuf *a, const cmark_strbuf *b) {
161 int result = memcmp(a->ptr, b->ptr, MIN(a->size, b->size));
162 return (result != 0) ? result
163 : (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0;
164}
165
166bufsize_t cmark_strbuf_strchr(const cmark_strbuf *buf, int c, bufsize_t pos) {
167 if (pos >= buf->size)
168 return -1;
169 if (pos < 0)
170 pos = 0;
171
172 const unsigned char *p =
173 (unsigned char *)memchr(buf->ptr + pos, c, buf->size - pos);
174 if (!p)
175 return -1;
176
177 return (bufsize_t)(p - (const unsigned char *)buf->ptr);
178}
179
180bufsize_t cmark_strbuf_strrchr(const cmark_strbuf *buf, int c, bufsize_t pos) {
181 if (pos < 0 || buf->size == 0)
182 return -1;
183 if (pos >= buf->size)
184 pos = buf->size - 1;
185
186 bufsize_t i;
187 for (i = pos; i >= 0; i--) {
188 if (buf->ptr[i] == (unsigned char)c)
189 return i;
190 }
191
192 return -1;
193}
194
195void cmark_strbuf_truncate(cmark_strbuf *buf, bufsize_t len) {
196 if (len < 0)
197 len = 0;
198
199 if (len < buf->size) {
200 buf->size = len;
201 buf->ptr[buf->size] = '\0';
202 }
203}
204
205void cmark_strbuf_drop(cmark_strbuf *buf, bufsize_t n) {
206 if (n > 0) {
207 if (n > buf->size)
208 n = buf->size;
209 buf->size = buf->size - n;
210 if (buf->size)
211 memmove(buf->ptr, buf->ptr + n, buf->size);
212
213 buf->ptr[buf->size] = '\0';
214 }
215}
216
217void cmark_strbuf_rtrim(cmark_strbuf *buf) {
218 if (!buf->size)
219 return;
220
221 while (buf->size > 0) {
222 if (!cmark_isspace(buf->ptr[buf->size - 1]))
223 break;
224
225 buf->size--;
226 }
227
228 buf->ptr[buf->size] = '\0';
229}
230
231void cmark_strbuf_trim(cmark_strbuf *buf) {
232 bufsize_t i = 0;
233
234 if (!buf->size)
235 return;
236
237 while (i < buf->size && cmark_isspace(buf->ptr[i]))
238 i++;
239
240 cmark_strbuf_drop(buf, i);
241
242 cmark_strbuf_rtrim(buf);
243}
244
245// Destructively modify string, collapsing consecutive
246// space and newline characters into a single space.
247void cmark_strbuf_normalize_whitespace(cmark_strbuf *s) {
248 bool last_char_was_space = false;
249 bufsize_t r, w;
250
251 for (r = 0, w = 0; r < s->size; ++r) {
252 if (cmark_isspace(s->ptr[r])) {
253 if (!last_char_was_space) {
254 s->ptr[w++] = ' ';
255 last_char_was_space = true;
256 }
257 } else {
258 s->ptr[w++] = s->ptr[r];
259 last_char_was_space = false;
260 }
261 }
262
263 cmark_strbuf_truncate(s, w);
264}
265
266// Destructively unescape a string: remove backslashes before punctuation chars.
267extern void cmark_strbuf_unescape(cmark_strbuf *buf) {
268 bufsize_t r, w;
269
270 for (r = 0, w = 0; r < buf->size; ++r) {
271 if (buf->ptr[r] == '\\' && cmark_ispunct(buf->ptr[r + 1]))
272 r++;
273
274 buf->ptr[w++] = buf->ptr[r];
275 }
276
277 cmark_strbuf_truncate(buf, w);
278}
279