GCC Code Coverage Report


Directory: ./
File: string.c
Date: 2025-04-06 13:22:55
Exec Total Coverage
Lines: 495 544 91.0%
Functions: 60 60 100.0%
Branches: 308 366 84.2%

Line Branch Exec Source
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28 #include "cx/string.h"
29
30 #include <string.h>
31 #include <stdarg.h>
32 #include <assert.h>
33 #include <errno.h>
34 #include <limits.h>
35 #include <float.h>
36
37 #ifdef _WIN32
38 #define cx_strcasecmp_impl _strnicmp
39 #else
40 #include <strings.h>
41 #define cx_strcasecmp_impl strncasecmp
42 #endif
43
44 4 cxmutstr cx_mutstr(char *cstring) {
45 4 return (cxmutstr) {cstring, strlen(cstring)};
46 }
47
48 161 cxmutstr cx_mutstrn(
49 char *cstring,
50 size_t length
51 ) {
52 161 return (cxmutstr) {cstring, length};
53 }
54
55 992 cxstring cx_str(const char *cstring) {
56 992 return (cxstring) {cstring, strlen(cstring)};
57 }
58
59 755 cxstring cx_strn(
60 const char *cstring,
61 size_t length
62 ) {
63 755 return (cxstring) {cstring, length};
64 }
65
66 123 void cx_strfree(cxmutstr *str) {
67
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 123 times.
123 if (str == NULL) return;
68 123 free(str->ptr);
69 123 str->ptr = NULL;
70 123 str->length = 0;
71 }
72
73 115 void cx_strfree_a(
74 const CxAllocator *alloc,
75 cxmutstr *str
76 ) {
77
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 115 times.
115 if (str == NULL) return;
78 115 cxFree(alloc, str->ptr);
79 115 str->ptr = NULL;
80 115 str->length = 0;
81 }
82
83 4 size_t cx_strlen(
84 size_t count,
85 ...
86 ) {
87
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
4 if (count == 0) return 0;
88
89 va_list ap;
90 3 va_start(ap, count);
91 3 size_t size = 0;
92
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 3 times.
9 for (size_t i = 0; i < count; i++) {
93 6 cxstring str = va_arg(ap, cxstring);
94
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (size > SIZE_MAX - str.length) errno = EOVERFLOW;
95 6 size += str.length;
96 }
97 3 va_end(ap);
98
99 3 return size;
100 }
101
102 19 cxmutstr cx_strcat_ma(
103 const CxAllocator *alloc,
104 cxmutstr str,
105 size_t count,
106 ...
107 ) {
108
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
19 if (count == 0) return str;
109
110 cxstring strings_stack[8];
111 cxstring *strings;
112
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 18 times.
19 if (count > 8) {
113 1 strings = calloc(count, sizeof(cxstring));
114
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (strings == NULL) {
115 return (cxmutstr) {NULL, 0};
116 }
117 } else {
118 18 strings = strings_stack;
119 }
120
121 va_list ap;
122 19 va_start(ap, count);
123
124 // get all args and overall length
125 19 bool overflow = false;
126 19 size_t slen = str.length;
127
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 19 times.
58 for (size_t i = 0; i < count; i++) {
128 39 cxstring s = va_arg (ap, cxstring);
129 39 strings[i] = s;
130
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 39 times.
39 if (slen > SIZE_MAX - str.length) overflow = true;
131 39 slen += s.length;
132 }
133 19 va_end(ap);
134
135 // abort in case of overflow
136
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
19 if (overflow) {
137 errno = EOVERFLOW;
138 if (strings != strings_stack) {
139 free(strings);
140 }
141 return (cxmutstr) { NULL, 0 };
142 }
143
144 // reallocate or create new string
145 char *newstr;
146
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 13 times.
19 if (str.ptr == NULL) {
147 6 newstr = cxMalloc(alloc, slen + 1);
148 } else {
149 13 newstr = cxRealloc(alloc, str.ptr, slen + 1);
150 }
151
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
19 if (newstr == NULL) {
152 if (strings != strings_stack) {
153 free(strings);
154 }
155 return (cxmutstr) {NULL, 0};
156 }
157 19 str.ptr = newstr;
158
159 // concatenate strings
160 19 size_t pos = str.length;
161 19 str.length = slen;
162
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 19 times.
58 for (size_t i = 0; i < count; i++) {
163 39 cxstring s = strings[i];
164 39 memcpy(str.ptr + pos, s.ptr, s.length);
165 39 pos += s.length;
166 }
167
168 // terminate string
169 19 str.ptr[str.length] = '\0';
170
171 // free temporary array
172
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 18 times.
19 if (strings != strings_stack) {
173 1 free(strings);
174 }
175
176 19 return str;
177 }
178
179 20 cxstring cx_strsubs(
180 cxstring string,
181 size_t start
182 ) {
183 20 return cx_strsubsl(string, start, string.length - start);
184 }
185
186 1 cxmutstr cx_strsubs_m(
187 cxmutstr string,
188 size_t start
189 ) {
190 1 return cx_strsubsl_m(string, start, string.length - start);
191 }
192
193 40 cxstring cx_strsubsl(
194 cxstring string,
195 size_t start,
196 size_t length
197 ) {
198
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 39 times.
40 if (start > string.length) {
199 1 return (cxstring) {NULL, 0};
200 }
201
202 39 size_t rem_len = string.length - start;
203
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 37 times.
39 if (length > rem_len) {
204 2 length = rem_len;
205 }
206
207 39 return (cxstring) {string.ptr + start, length};
208 }
209
210 1 cxmutstr cx_strsubsl_m(
211 cxmutstr string,
212 size_t start,
213 size_t length
214 ) {
215 1 cxstring result = cx_strsubsl(cx_strcast(string), start, length);
216 1 return (cxmutstr) {(char *) result.ptr, result.length};
217 }
218
219 221 cxstring cx_strchr(
220 cxstring string,
221 int chr
222 ) {
223 221 char *ret = memchr(string.ptr, 0xFF & chr, string.length);
224
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 192 times.
221 if (ret == NULL) return (cxstring) {NULL, 0};
225 192 return (cxstring) {ret, string.length - (ret - string.ptr)};
226 }
227
228 1 cxmutstr cx_strchr_m(
229 cxmutstr string,
230 int chr
231 ) {
232 1 cxstring result = cx_strchr(cx_strcast(string), chr);
233 1 return (cxmutstr) {(char *) result.ptr, result.length};
234 }
235
236 3 cxstring cx_strrchr(
237 cxstring string,
238 int chr
239 ) {
240 3 chr = 0xFF & chr;
241 3 size_t i = string.length;
242
2/2
✓ Branch 0 taken 50 times.
✓ Branch 1 taken 2 times.
52 while (i > 0) {
243 50 i--;
244 // TODO: improve by comparing multiple bytes at once
245
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 49 times.
50 if (string.ptr[i] == chr) {
246 1 return cx_strsubs(string, i);
247 }
248 }
249 2 return (cxstring) {NULL, 0};
250 }
251
252 1 cxmutstr cx_strrchr_m(
253 cxmutstr string,
254 int chr
255 ) {
256 1 cxstring result = cx_strrchr(cx_strcast(string), chr);
257 1 return (cxmutstr) {(char *) result.ptr, result.length};
258 }
259
260 #ifndef CX_STRSTR_SBO_SIZE
261 #define CX_STRSTR_SBO_SIZE 128
262 #endif
263 const unsigned cx_strstr_sbo_size = CX_STRSTR_SBO_SIZE;
264
265 282 cxstring cx_strstr(
266 cxstring haystack,
267 cxstring needle
268 ) {
269
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 273 times.
282 if (needle.length == 0) {
270 9 return haystack;
271 }
272
273 // optimize for single-char needles
274
2/2
✓ Branch 0 taken 194 times.
✓ Branch 1 taken 79 times.
273 if (needle.length == 1) {
275 194 return cx_strchr(haystack, *needle.ptr);
276 }
277
278 /*
279 * IMPORTANT:
280 * Our prefix table contains the prefix length PLUS ONE
281 * this is our decision, because we want to use the full range of size_t.
282 * The original algorithm needs a (-1) at one single place,
283 * and we want to avoid that.
284 */
285
286 // local prefix table
287 size_t s_prefix_table[CX_STRSTR_SBO_SIZE];
288
289 // check needle length and use appropriate prefix table
290 // if the pattern exceeds static prefix table, allocate on the heap
291 79 const bool useheap = needle.length >= CX_STRSTR_SBO_SIZE;
292 1 register size_t *ptable = useheap ? calloc(needle.length + 1,
293
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 78 times.
79 sizeof(size_t)) : s_prefix_table;
294
295 // keep counter in registers
296 register size_t i, j;
297
298 // fill prefix table
299 79 i = 0;
300 79 j = 0;
301 79 ptable[i] = j;
302
2/2
✓ Branch 0 taken 535 times.
✓ Branch 1 taken 79 times.
614 while (i < needle.length) {
303
4/4
✓ Branch 0 taken 465 times.
✓ Branch 1 taken 315 times.
✓ Branch 2 taken 245 times.
✓ Branch 3 taken 220 times.
780 while (j >= 1 && needle.ptr[j - 1] != needle.ptr[i]) {
304 245 j = ptable[j - 1];
305 }
306 535 i++;
307 535 j++;
308 535 ptable[i] = j;
309 }
310
311 // search
312 79 cxstring result = {NULL, 0};
313 79 i = 0;
314 79 j = 1;
315
2/2
✓ Branch 0 taken 1151 times.
✓ Branch 1 taken 29 times.
1180 while (i < haystack.length) {
316
4/4
✓ Branch 0 taken 1209 times.
✓ Branch 1 taken 658 times.
✓ Branch 2 taken 716 times.
✓ Branch 3 taken 493 times.
1867 while (j >= 1 && haystack.ptr[i] != needle.ptr[j - 1]) {
317 716 j = ptable[j - 1];
318 }
319 1151 i++;
320 1151 j++;
321
2/2
✓ Branch 0 taken 50 times.
✓ Branch 1 taken 1101 times.
1151 if (j - 1 == needle.length) {
322 50 size_t start = i - needle.length;
323 50 result.ptr = haystack.ptr + start;
324 50 result.length = haystack.length - start;
325 50 break;
326 }
327 }
328
329 // if prefix table was allocated on the heap, free it
330
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 78 times.
79 if (useheap) {
331 1 free(ptable);
332 }
333
334 79 return result;
335 }
336
337 1 cxmutstr cx_strstr_m(
338 cxmutstr haystack,
339 cxstring needle
340 ) {
341 1 cxstring result = cx_strstr(cx_strcast(haystack), needle);
342 1 return (cxmutstr) {(char *) result.ptr, result.length};
343 }
344
345 28 size_t cx_strsplit(
346 cxstring string,
347 cxstring delim,
348 size_t limit,
349 cxstring *output
350 ) {
351 // special case: output limit is zero
352
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
28 if (limit == 0) return 0;
353
354 // special case: delimiter is empty
355
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 26 times.
28 if (delim.length == 0) {
356 2 output[0] = string;
357 2 return 1;
358 }
359
360 // special cases: delimiter is at least as large as the string
361
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 22 times.
26 if (delim.length >= string.length) {
362 // exact match
363
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
4 if (cx_strcmp(string, delim) == 0) {
364 2 output[0] = cx_strn(string.ptr, 0);
365 2 output[1] = cx_strn(string.ptr + string.length, 0);
366 2 return 2;
367 } else {
368 // no match possible
369 2 output[0] = string;
370 2 return 1;
371 }
372 }
373
374 22 size_t n = 0;
375 22 cxstring curpos = string;
376 34 while (1) {
377 56 ++n;
378 56 cxstring match = cx_strstr(curpos, delim);
379
2/2
✓ Branch 0 taken 40 times.
✓ Branch 1 taken 16 times.
56 if (match.length > 0) {
380 // is the limit reached?
381
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 6 times.
40 if (n < limit) {
382 // copy the current string to the array
383 34 cxstring item = cx_strn(curpos.ptr, match.ptr - curpos.ptr);
384 34 output[n - 1] = item;
385 34 size_t processed = item.length + delim.length;
386 34 curpos.ptr += processed;
387 34 curpos.length -= processed;
388 } else {
389 // limit reached, copy the _full_ remaining string
390 6 output[n - 1] = curpos;
391 6 break;
392 }
393 } else {
394 // no more matches, copy last string
395 16 output[n - 1] = curpos;
396 16 break;
397 }
398 }
399
400 22 return n;
401 }
402
403 14 size_t cx_strsplit_a(
404 const CxAllocator *allocator,
405 cxstring string,
406 cxstring delim,
407 size_t limit,
408 cxstring **output
409 ) {
410 // find out how many splits we're going to make and allocate memory
411 14 size_t n = 0;
412 14 cxstring curpos = string;
413 25 while (1) {
414 39 ++n;
415 39 cxstring match = cx_strstr(curpos, delim);
416
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 10 times.
39 if (match.length > 0) {
417 // is the limit reached?
418
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 4 times.
29 if (n < limit) {
419 25 size_t processed = match.ptr - curpos.ptr + delim.length;
420 25 curpos.ptr += processed;
421 25 curpos.length -= processed;
422 } else {
423 // limit reached
424 4 break;
425 }
426 } else {
427 // no more matches
428 10 break;
429 }
430 }
431 14 *output = cxCalloc(allocator, n, sizeof(cxstring));
432 14 return cx_strsplit(string, delim, n, *output);
433 }
434
435 1 size_t cx_strsplit_m(
436 cxmutstr string,
437 cxstring delim,
438 size_t limit,
439 cxmutstr *output
440 ) {
441 1 return cx_strsplit(cx_strcast(string),
442 delim, limit, (cxstring *) output);
443 }
444
445 1 size_t cx_strsplit_ma(
446 const CxAllocator *allocator,
447 cxmutstr string,
448 cxstring delim,
449 size_t limit,
450 cxmutstr **output
451 ) {
452 1 return cx_strsplit_a(allocator, cx_strcast(string),
453 delim, limit, (cxstring **) output);
454 }
455
456 682 int cx_strcmp(
457 cxstring s1,
458 cxstring s2
459 ) {
460
2/2
✓ Branch 0 taken 352 times.
✓ Branch 1 taken 330 times.
682 if (s1.length == s2.length) {
461 352 return strncmp(s1.ptr, s2.ptr, s1.length);
462
2/2
✓ Branch 0 taken 157 times.
✓ Branch 1 taken 173 times.
330 } else if (s1.length > s2.length) {
463 157 int r = strncmp(s1.ptr, s2.ptr, s2.length);
464
2/2
✓ Branch 0 taken 153 times.
✓ Branch 1 taken 4 times.
157 if (r != 0) return r;
465 4 return 1;
466 } else {
467 173 int r = strncmp(s1.ptr, s2.ptr, s1.length);
468
2/2
✓ Branch 0 taken 169 times.
✓ Branch 1 taken 4 times.
173 if (r != 0) return r;
469 4 return -1;
470 }
471 }
472
473 14 int cx_strcasecmp(
474 cxstring s1,
475 cxstring s2
476 ) {
477
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 7 times.
14 if (s1.length == s2.length) {
478 7 return cx_strcasecmp_impl(s1.ptr, s2.ptr, s1.length);
479
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3 times.
7 } else if (s1.length > s2.length) {
480 4 int r = cx_strcasecmp_impl(s1.ptr, s2.ptr, s2.length);
481
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (r != 0) return r;
482 2 return 1;
483 } else {
484 3 int r = cx_strcasecmp_impl(s1.ptr, s2.ptr, s1.length);
485
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if (r != 0) return r;
486 1 return -1;
487 }
488 }
489
490 2 int cx_strcmp_p(
491 const void *s1,
492 const void *s2
493 ) {
494 2 const cxstring *left = s1;
495 2 const cxstring *right = s2;
496 2 return cx_strcmp(*left, *right);
497 }
498
499 2 int cx_strcasecmp_p(
500 const void *s1,
501 const void *s2
502 ) {
503 2 const cxstring *left = s1;
504 2 const cxstring *right = s2;
505 2 return cx_strcasecmp(*left, *right);
506 }
507
508 281 cxmutstr cx_strdup_a_(
509 const CxAllocator *allocator,
510 cxstring string
511 ) {
512 281 cxmutstr result = {
513 281 cxMalloc(allocator, string.length + 1),
514 281 string.length
515 };
516
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 281 times.
281 if (result.ptr == NULL) {
517 result.length = 0;
518 return result;
519 }
520 281 memcpy(result.ptr, string.ptr, string.length);
521 281 result.ptr[string.length] = '\0';
522 281 return result;
523 }
524
525 497 static bool str_isspace(char c) {
526 // TODO: remove once UCX has public API for this
527
8/12
✓ Branch 0 taken 258 times.
✓ Branch 1 taken 239 times.
✓ Branch 2 taken 256 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 256 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 256 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 256 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 256 times.
497 return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\v' || c == '\f';
528 }
529
530 202 cxstring cx_strtrim(cxstring string) {
531 202 cxstring result = string;
532 // TODO: optimize by comparing multiple bytes at once
533
4/4
✓ Branch 0 taken 269 times.
✓ Branch 1 taken 74 times.
✓ Branch 3 taken 141 times.
✓ Branch 4 taken 128 times.
343 while (result.length > 0 && str_isspace(*result.ptr)) {
534 141 result.ptr++;
535 141 result.length--;
536 }
537
4/4
✓ Branch 0 taken 228 times.
✓ Branch 1 taken 74 times.
✓ Branch 3 taken 100 times.
✓ Branch 4 taken 128 times.
302 while (result.length > 0 && str_isspace(result.ptr[result.length - 1])) {
538 100 result.length--;
539 }
540 202 return result;
541 }
542
543 1 cxmutstr cx_strtrim_m(cxmutstr string) {
544 1 cxstring result = cx_strtrim(cx_strcast(string));
545 1 return (cxmutstr) {(char *) result.ptr, result.length};
546 }
547
548 5 bool cx_strprefix(
549 cxstring string,
550 cxstring prefix
551 ) {
552
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
5 if (string.length < prefix.length) return false;
553 4 return memcmp(string.ptr, prefix.ptr, prefix.length) == 0;
554 }
555
556 5 bool cx_strsuffix(
557 cxstring string,
558 cxstring suffix
559 ) {
560
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
5 if (string.length < suffix.length) return false;
561 4 return memcmp(string.ptr + string.length - suffix.length,
562 4 suffix.ptr, suffix.length) == 0;
563 }
564
565 5 bool cx_strcaseprefix(
566 cxstring string,
567 cxstring prefix
568 ) {
569
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
5 if (string.length < prefix.length) return false;
570 #ifdef _WIN32
571 return _strnicmp(string.ptr, prefix.ptr, prefix.length) == 0;
572 #else
573 4 return strncasecmp(string.ptr, prefix.ptr, prefix.length) == 0;
574 #endif
575 }
576
577 5 bool cx_strcasesuffix(
578 cxstring string,
579 cxstring suffix
580 ) {
581
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
5 if (string.length < suffix.length) return false;
582 #ifdef _WIN32
583 return _strnicmp(string.ptr+string.length-suffix.length,
584 suffix.ptr, suffix.length) == 0;
585 #else
586 4 return strncasecmp(string.ptr + string.length - suffix.length,
587 4 suffix.ptr, suffix.length) == 0;
588 #endif
589 }
590
591 #ifndef CX_STRREPLACE_INDEX_BUFFER_SIZE
592 #define CX_STRREPLACE_INDEX_BUFFER_SIZE 64
593 #endif
594
595 struct cx_strreplace_ibuf {
596 size_t *buf;
597 struct cx_strreplace_ibuf *next;
598 unsigned int len;
599 };
600
601 13 static void cx_strrepl_free_ibuf(struct cx_strreplace_ibuf *buf) {
602 // remember, the first data is on the stack!
603 13 buf = buf->next;
604
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13 times.
14 while (buf) {
605 1 struct cx_strreplace_ibuf *next = buf->next;
606 1 free(buf->buf);
607 1 free(buf);
608 1 buf = next;
609 }
610 13 }
611
612 14 cxmutstr cx_strreplacen_a(
613 const CxAllocator *allocator,
614 cxstring str,
615 cxstring search,
616 cxstring replacement,
617 size_t replmax
618 ) {
619
620
4/6
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 13 times.
14 if (search.length == 0 || search.length > str.length || replmax == 0)
621 1 return cx_strdup_a(allocator, str);
622
623 // Compute expected buffer length
624 13 size_t ibufmax = str.length / search.length;
625 13 size_t ibuflen = replmax < ibufmax ? replmax : ibufmax;
626
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 12 times.
13 if (ibuflen > CX_STRREPLACE_INDEX_BUFFER_SIZE) {
627 1 ibuflen = CX_STRREPLACE_INDEX_BUFFER_SIZE;
628 }
629
630 // First index buffer can be on the stack
631 13 struct cx_strreplace_ibuf ibuf, *curbuf = &ibuf;
632 size_t ibuf_sbo[CX_STRREPLACE_INDEX_BUFFER_SIZE];
633 13 ibuf.buf = ibuf_sbo;
634 13 ibuf.next = NULL;
635 13 ibuf.len = 0;
636
637 // Search occurrences
638 13 cxstring searchstr = str;
639 13 size_t found = 0;
640 do {
641 160 cxstring match = cx_strstr(searchstr, search);
642
2/2
✓ Branch 0 taken 154 times.
✓ Branch 1 taken 6 times.
160 if (match.length > 0) {
643 // Allocate next buffer in chain, if required
644
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 153 times.
154 if (curbuf->len == ibuflen) {
645 struct cx_strreplace_ibuf *nextbuf =
646 1 calloc(1, sizeof(struct cx_strreplace_ibuf));
647
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!nextbuf) {
648 cx_strrepl_free_ibuf(&ibuf);
649 return cx_mutstrn(NULL, 0);
650 }
651 1 nextbuf->buf = calloc(ibuflen, sizeof(size_t));
652
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!nextbuf->buf) {
653 free(nextbuf);
654 cx_strrepl_free_ibuf(&ibuf);
655 return cx_mutstrn(NULL, 0);
656 }
657 1 curbuf->next = nextbuf;
658 1 curbuf = nextbuf;
659 }
660
661 // Record match index
662 154 found++;
663 154 size_t idx = match.ptr - str.ptr;
664 154 curbuf->buf[curbuf->len++] = idx;
665 154 searchstr.ptr = match.ptr + search.length;
666 154 searchstr.length = str.length - idx - search.length;
667 } else {
668 6 break;
669 }
670
4/4
✓ Branch 0 taken 151 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 147 times.
✓ Branch 3 taken 4 times.
154 } while (searchstr.length > 0 && found < replmax);
671
672 // Allocate result string
673 cxmutstr result;
674 {
675 13 long long adjlen = (long long) replacement.length - (long long) search.length;
676 13 size_t rcount = 0;
677 13 curbuf = &ibuf;
678 do {
679 14 rcount += curbuf->len;
680 14 curbuf = curbuf->next;
681
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13 times.
14 } while (curbuf);
682 13 result.length = str.length + rcount * adjlen;
683 13 result.ptr = cxMalloc(allocator, result.length + 1);
684
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 if (!result.ptr) {
685 cx_strrepl_free_ibuf(&ibuf);
686 return cx_mutstrn(NULL, 0);
687 }
688 }
689
690 // Build result string
691 13 curbuf = &ibuf;
692 13 size_t srcidx = 0;
693 13 char *destptr = result.ptr;
694 do {
695
2/2
✓ Branch 0 taken 154 times.
✓ Branch 1 taken 14 times.
168 for (size_t i = 0; i < curbuf->len; i++) {
696 // Copy source part up to next match
697 154 size_t idx = curbuf->buf[i];
698 154 size_t srclen = idx - srcidx;
699
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 148 times.
154 if (srclen > 0) {
700 6 memcpy(destptr, str.ptr + srcidx, srclen);
701 6 destptr += srclen;
702 6 srcidx += srclen;
703 }
704
705 // Copy the replacement and skip the source pattern
706 154 srcidx += search.length;
707 154 memcpy(destptr, replacement.ptr, replacement.length);
708 154 destptr += replacement.length;
709 }
710 14 curbuf = curbuf->next;
711
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13 times.
14 } while (curbuf);
712 13 memcpy(destptr, str.ptr + srcidx, str.length - srcidx);
713
714 // Result is guaranteed to be zero-terminated
715 13 result.ptr[result.length] = '\0';
716
717 // Free index buffer
718 13 cx_strrepl_free_ibuf(&ibuf);
719
720 13 return result;
721 }
722
723 5 CxStrtokCtx cx_strtok_(
724 cxstring str,
725 cxstring delim,
726 size_t limit
727 ) {
728 CxStrtokCtx ctx;
729 5 ctx.str = str;
730 5 ctx.delim = delim;
731 5 ctx.limit = limit;
732 5 ctx.pos = 0;
733 5 ctx.next_pos = 0;
734 5 ctx.delim_pos = 0;
735 5 ctx.found = 0;
736 5 ctx.delim_more = NULL;
737 5 ctx.delim_more_count = 0;
738 5 return ctx;
739 }
740
741 16 bool cx_strtok_next(
742 CxStrtokCtx *ctx,
743 cxstring *token
744 ) {
745 // abortion criteria
746
4/4
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 13 times.
16 if (ctx->found >= ctx->limit || ctx->delim_pos >= ctx->str.length) {
747 3 return false;
748 }
749
750 // determine the search start
751 13 cxstring haystack = cx_strsubs(ctx->str, ctx->next_pos);
752
753 // search the next delimiter
754 13 cxstring delim = cx_strstr(haystack, ctx->delim);
755
756 // if found, make delim capture exactly the delimiter
757
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 5 times.
13 if (delim.length > 0) {
758 8 delim.length = ctx->delim.length;
759 }
760
761 // if more delimiters are specified, check them now
762
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 8 times.
13 if (ctx->delim_more_count > 0) {
763
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 5 times.
15 for (size_t i = 0; i < ctx->delim_more_count; i++) {
764 10 cxstring d = cx_strstr(haystack, ctx->delim_more[i]);
765
6/6
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 3 times.
10 if (d.length > 0 && (delim.length == 0 || d.ptr < delim.ptr)) {
766 4 delim.ptr = d.ptr;
767 4 delim.length = ctx->delim_more[i].length;
768 }
769 }
770 }
771
772 // store the token information and adjust the context
773 13 ctx->found++;
774 13 ctx->pos = ctx->next_pos;
775 13 token->ptr = &ctx->str.ptr[ctx->pos];
776 26 ctx->delim_pos = delim.length == 0 ?
777
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 11 times.
13 ctx->str.length : (size_t) (delim.ptr - ctx->str.ptr);
778 13 token->length = ctx->delim_pos - ctx->pos;
779 13 ctx->next_pos = ctx->delim_pos + delim.length;
780
781 13 return true;
782 }
783
784 6 bool cx_strtok_next_m(
785 CxStrtokCtx *ctx,
786 cxmutstr *token
787 ) {
788 6 return cx_strtok_next(ctx, (cxstring *) token);
789 }
790
791 2 void cx_strtok_delim(
792 CxStrtokCtx *ctx,
793 const cxstring *delim,
794 size_t count
795 ) {
796 2 ctx->delim_more = delim;
797 2 ctx->delim_more_count = count;
798 2 }
799
800 #define cx_strtoX_signed_impl(rtype, rmin, rmax) \
801 long long result; \
802 if (cx_strtoll_lc(str, &result, base, groupsep)) { \
803 return -1; \
804 } \
805 if (result < rmin || result > rmax) { \
806 errno = ERANGE; \
807 return -1; \
808 } \
809 *output = (rtype) result; \
810 return 0
811
812 46 int cx_strtos_lc_(cxstring str, short *output, int base, const char *groupsep) {
813
5/6
✗ Branch 2 not taken.
✓ Branch 3 taken 46 times.
✓ Branch 4 taken 32 times.
✓ Branch 5 taken 14 times.
✓ Branch 6 taken 14 times.
✓ Branch 7 taken 18 times.
46 cx_strtoX_signed_impl(short, SHRT_MIN, SHRT_MAX);
814 }
815
816 46 int cx_strtoi_lc_(cxstring str, int *output, int base, const char *groupsep) {
817
5/6
✗ Branch 2 not taken.
✓ Branch 3 taken 46 times.
✓ Branch 4 taken 39 times.
✓ Branch 5 taken 7 times.
✓ Branch 6 taken 7 times.
✓ Branch 7 taken 32 times.
46 cx_strtoX_signed_impl(int, INT_MIN, INT_MAX);
818 }
819
820 46 int cx_strtol_lc_(cxstring str, long *output, int base, const char *groupsep) {
821
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 46 times.
46 cx_strtoX_signed_impl(long, LONG_MIN, LONG_MAX);
822 }
823
824 408 int cx_strtoll_lc_(cxstring str, long long *output, int base, const char *groupsep) {
825 // strategy: parse as unsigned, check range, negate if required
826 408 bool neg = false;
827 408 size_t start_unsigned = 0;
828
829 // emptiness check
830
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 408 times.
408 if (str.length == 0) {
831 errno = EINVAL;
832 return -1;
833 }
834
835 // test if we have a negative sign character
836
2/2
✓ Branch 0 taken 194 times.
✓ Branch 1 taken 214 times.
408 if (str.ptr[start_unsigned] == '-') {
837 194 neg = true;
838 194 start_unsigned++;
839 // must not be followed by positive sign character
840
2/4
✓ Branch 0 taken 194 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 194 times.
194 if (str.length == 1 || str.ptr[start_unsigned] == '+') {
841 errno = EINVAL;
842 return -1;
843 }
844 }
845
846 // now parse the number with strtoull
847 unsigned long long v;
848 214 cxstring ustr = start_unsigned == 0 ? str
849
2/2
✓ Branch 0 taken 194 times.
✓ Branch 1 taken 214 times.
408 : cx_strn(str.ptr + start_unsigned, str.length - start_unsigned);
850 408 int ret = cx_strtoull_lc(ustr, &v, base, groupsep);
851
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 404 times.
408 if (ret != 0) return ret;
852
2/2
✓ Branch 0 taken 192 times.
✓ Branch 1 taken 212 times.
404 if (neg) {
853
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 192 times.
192 if (v - 1 > LLONG_MAX) {
854 errno = ERANGE;
855 return -1;
856 }
857 192 *output = -(long long) v;
858 192 return 0;
859 } else {
860
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 210 times.
212 if (v > LLONG_MAX) {
861 2 errno = ERANGE;
862 2 return -1;
863 }
864 210 *output = (long long) v;
865 210 return 0;
866 }
867 }
868
869 49 int cx_strtoi8_lc_(cxstring str, int8_t *output, int base, const char *groupsep) {
870
5/6
✗ Branch 2 not taken.
✓ Branch 3 taken 49 times.
✓ Branch 4 taken 29 times.
✓ Branch 5 taken 20 times.
✓ Branch 6 taken 20 times.
✓ Branch 7 taken 9 times.
49 cx_strtoX_signed_impl(int8_t, INT8_MIN, INT8_MAX);
871 }
872
873 51 int cx_strtoi16_lc_(cxstring str, int16_t *output, int base, const char *groupsep) {
874
6/6
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 50 times.
✓ Branch 4 taken 35 times.
✓ Branch 5 taken 15 times.
✓ Branch 6 taken 15 times.
✓ Branch 7 taken 20 times.
51 cx_strtoX_signed_impl(int16_t, INT16_MIN, INT16_MAX);
875 }
876
877 47 int cx_strtoi32_lc_(cxstring str, int32_t *output, int base, const char *groupsep) {
878
5/6
✗ Branch 2 not taken.
✓ Branch 3 taken 47 times.
✓ Branch 4 taken 40 times.
✓ Branch 5 taken 7 times.
✓ Branch 6 taken 7 times.
✓ Branch 7 taken 33 times.
47 cx_strtoX_signed_impl(int32_t, INT32_MIN, INT32_MAX);
879 }
880
881 73 int cx_strtoi64_lc_(cxstring str, int64_t *output, int base, const char *groupsep) {
882 assert(sizeof(long long) == sizeof(int64_t)); // should be true on all platforms
883 73 return cx_strtoll_lc(str, (long long*) output, base, groupsep);
884 }
885
886 #define cx_strtoX_unsigned_impl(rtype, rmax) \
887 uint64_t result; \
888 if (cx_strtou64_lc(str, &result, base, groupsep)) { \
889 return -1; \
890 } \
891 if (result > rmax) { \
892 errno = ERANGE; \
893 return -1; \
894 } \
895 *output = (rtype) result; \
896 return 0
897
898 23 int cx_strtous_lc_(cxstring str, unsigned short *output, int base, const char *groupsep) {
899
3/4
✗ Branch 2 not taken.
✓ Branch 3 taken 23 times.
✓ Branch 4 taken 11 times.
✓ Branch 5 taken 12 times.
23 cx_strtoX_unsigned_impl(unsigned short, USHRT_MAX);
900 }
901
902 23 int cx_strtou_lc_(cxstring str, unsigned int *output, int base, const char *groupsep) {
903
3/4
✗ Branch 2 not taken.
✓ Branch 3 taken 23 times.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 18 times.
23 cx_strtoX_unsigned_impl(unsigned int, UINT_MAX);
904 }
905
906 23 int cx_strtoul_lc_(cxstring str, unsigned long *output, int base, const char *groupsep) {
907
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 23 times.
23 cx_strtoX_unsigned_impl(unsigned long, ULONG_MAX);
908 }
909
910 653 int cx_strtoull_lc_(cxstring str, unsigned long long *output, int base, const char *groupsep) {
911 // some sanity checks
912
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 653 times.
653 if (str.length == 0) {
913 errno = EINVAL;
914 return -1;
915 }
916
7/8
✓ Branch 0 taken 648 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 448 times.
✓ Branch 3 taken 200 times.
✓ Branch 4 taken 223 times.
✓ Branch 5 taken 225 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 223 times.
653 if (!(base == 2 || base == 8 || base == 10 || base == 16)) {
917 errno = EINVAL;
918 return -1;
919 }
920
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 653 times.
653 if (groupsep == NULL) groupsep = "";
921
922 // find the actual start of the number
923
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 652 times.
653 if (str.ptr[0] == '+') {
924 1 str.ptr++;
925 1 str.length--;
926
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (str.length == 0) {
927 errno = EINVAL;
928 return -1;
929 }
930 }
931 653 size_t start = 0;
932
933 // if base is 2 or 16, some leading stuff may appear
934
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 648 times.
653 if (base == 2) {
935
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if ((str.ptr[0] | 32) == 'b') {
936 start = 1;
937
3/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
5 } else if (str.ptr[0] == '0' && str.length > 1) {
938
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if ((str.ptr[1] | 32) == 'b') {
939 start = 2;
940 }
941 }
942
2/2
✓ Branch 0 taken 223 times.
✓ Branch 1 taken 425 times.
648 } else if (base == 16) {
943
3/4
✓ Branch 0 taken 218 times.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 218 times.
223 if ((str.ptr[0] | 32) == 'x' || str.ptr[0] == '#') {
944 5 start = 1;
945
3/4
✓ Branch 0 taken 200 times.
✓ Branch 1 taken 18 times.
✓ Branch 2 taken 200 times.
✗ Branch 3 not taken.
218 } else if (str.ptr[0] == '0' && str.length > 1) {
946
2/2
✓ Branch 0 taken 187 times.
✓ Branch 1 taken 13 times.
200 if ((str.ptr[1] | 32) == 'x') {
947 187 start = 2;
948 }
949 }
950 }
951
952 // check if there are digits left
953
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 653 times.
653 if (start >= str.length) {
954 errno = EINVAL;
955 return -1;
956 }
957
958 // now parse the number
959 653 unsigned long long result = 0;
960
2/2
✓ Branch 0 taken 4768 times.
✓ Branch 1 taken 643 times.
5411 for (size_t i = start; i < str.length; i++) {
961 // ignore group separators
962
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 4755 times.
4768 if (strchr(groupsep, str.ptr[i])) continue;
963
964 // determine the digit value of the character
965 4755 unsigned char c = str.ptr[i];
966
2/2
✓ Branch 0 taken 275 times.
✓ Branch 1 taken 4480 times.
4755 if (c >= 'a') c = 10 + (c - 'a');
967
2/2
✓ Branch 0 taken 425 times.
✓ Branch 1 taken 4055 times.
4480 else if (c >= 'A') c = 10 + (c - 'A');
968
2/2
✓ Branch 0 taken 4046 times.
✓ Branch 1 taken 9 times.
4055 else if (c >= '0') c = c - '0';
969 9 else c = 255;
970
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 4746 times.
4755 if (c >= base) {
971 9 errno = EINVAL;
972 9 return -1;
973 }
974
975 // now combine the digit with what we already have
976 4746 unsigned long right = (result & 0xff) * base + c;
977 4746 unsigned long long left = (result >> 8) * base + (right >> 8);
978
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4745 times.
4746 if (left > (ULLONG_MAX >> 8)) {
979 1 errno = ERANGE;
980 1 return -1;
981 }
982 4745 result = (left << 8) + (right & 0xff);
983 }
984
985 643 *output = result;
986 643 return 0;
987 }
988
989 25 int cx_strtou8_lc_(cxstring str, uint8_t *output, int base, const char *groupsep) {
990
3/4
✗ Branch 2 not taken.
✓ Branch 3 taken 25 times.
✓ Branch 4 taken 18 times.
✓ Branch 5 taken 7 times.
25 cx_strtoX_unsigned_impl(uint8_t, UINT8_MAX);
991 }
992
993 54 int cx_strtou16_lc_(cxstring str, uint16_t *output, int base, const char *groupsep) {
994
4/4
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 50 times.
✓ Branch 4 taken 11 times.
✓ Branch 5 taken 39 times.
54 cx_strtoX_unsigned_impl(uint16_t, UINT16_MAX);
995 }
996
997 25 int cx_strtou32_lc_(cxstring str, uint32_t *output, int base, const char *groupsep) {
998
3/4
✗ Branch 2 not taken.
✓ Branch 3 taken 25 times.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 20 times.
25 cx_strtoX_unsigned_impl(uint32_t, UINT32_MAX);
999 }
1000
1001 196 int cx_strtou64_lc_(cxstring str, uint64_t *output, int base, const char *groupsep) {
1002 assert(sizeof(unsigned long long) == sizeof(uint64_t)); // should be true on all platforms
1003 196 return cx_strtoull_lc(str, (unsigned long long*) output, base, groupsep);
1004 }
1005
1006 23 int cx_strtoz_lc_(cxstring str, size_t *output, int base, const char *groupsep) {
1007 #if SIZE_MAX == UINT32_MAX
1008 return cx_strtou32_lc_(str, (uint32_t*) output, base, groupsep);
1009 #elif SIZE_MAX == UINT64_MAX
1010 23 return cx_strtoull_lc_(str, (unsigned long long *) output, base, groupsep);
1011 #else
1012 #error "unsupported size_t size"
1013 #endif
1014 }
1015
1016 18 int cx_strtof_lc_(cxstring str, float *output, char decsep, const char *groupsep) {
1017 // use string to double and add a range check
1018 double d;
1019 18 int ret = cx_strtod_lc_(str, &d, decsep, groupsep);
1020
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 13 times.
18 if (ret != 0) return ret;
1021 // note: FLT_MIN is the smallest POSITIVE number that can be represented
1022
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 10 times.
13 double test = d < 0 ? -d : d;
1023
4/4
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 10 times.
13 if (test < FLT_MIN || test > FLT_MAX) {
1024 3 errno = ERANGE;
1025 3 return -1;
1026 }
1027 10 *output = (float) d;
1028 10 return 0;
1029 }
1030
1031 233 static bool str_isdigit(char c) {
1032 // TODO: remove once UCX has public API for this
1033
4/4
✓ Branch 0 taken 201 times.
✓ Branch 1 taken 32 times.
✓ Branch 2 taken 184 times.
✓ Branch 3 taken 17 times.
233 return c >= '0' && c <= '9';
1034 }
1035
1036 34 int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupsep) {
1037 // TODO: overflow check
1038 // TODO: increase precision
1039
1040 // emptiness check
1041
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
34 if (str.length == 0) {
1042 errno = EINVAL;
1043 return -1;
1044 }
1045
1046 34 double result = 0.;
1047 34 int sign = 1;
1048
1049 // check if there is a sign
1050
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 27 times.
34 if (str.ptr[0] == '-') {
1051 7 sign = -1;
1052 7 str.ptr++;
1053 7 str.length--;
1054
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
27 } else if (str.ptr[0] == '+') {
1055 str.ptr++;
1056 str.length--;
1057 }
1058
1059 // there must be at least one char to parse
1060
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
34 if (str.length == 0) {
1061 errno = EINVAL;
1062 return -1;
1063 }
1064
1065 // parse all digits until we find the decsep
1066 34 size_t pos = 0;
1067 do {
1068
2/2
✓ Branch 1 taken 79 times.
✓ Branch 2 taken 36 times.
115 if (str_isdigit(str.ptr[pos])) {
1069 79 result = result * 10 + (str.ptr[pos] - '0');
1070
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 2 times.
36 } else if (strchr(groupsep, str.ptr[pos]) == NULL) {
1071 34 break;
1072 }
1073
1/2
✓ Branch 0 taken 81 times.
✗ Branch 1 not taken.
81 } while (++pos < str.length);
1074
1075 // already done?
1076
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
34 if (pos == str.length) {
1077 *output = result * sign;
1078 return 0;
1079 }
1080
1081 // is the next char the decsep?
1082
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 10 times.
34 if (str.ptr[pos] == decsep) {
1083 24 pos++;
1084 // it may end with the decsep, if it did not start with it
1085
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 if (pos == str.length) {
1086 if (str.length == 1) {
1087 errno = EINVAL;
1088 return -1;
1089 } else {
1090 *output = result * sign;
1091 return 0;
1092 }
1093 }
1094 // parse everything until exponent or end
1095 24 double factor = 1.;
1096 do {
1097
2/2
✓ Branch 1 taken 83 times.
✓ Branch 2 taken 13 times.
96 if (str_isdigit(str.ptr[pos])) {
1098 83 factor *= 0.1;
1099 83 result = result + factor * (str.ptr[pos] - '0');
1100
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 2 times.
13 } else if (strchr(groupsep, str.ptr[pos]) == NULL) {
1101 11 break;
1102 }
1103
2/2
✓ Branch 0 taken 72 times.
✓ Branch 1 taken 13 times.
85 } while (++pos < str.length);
1104 }
1105
1106 // no exponent?
1107
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 21 times.
34 if (pos == str.length) {
1108 13 *output = result * sign;
1109 13 return 0;
1110 }
1111
1112 // now the next separator MUST be the exponent separator
1113 // and at least one char must follow
1114
4/4
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 16 times.
21 if ((str.ptr[pos] | 32) != 'e' || str.length <= pos + 1) {
1115 5 errno = EINVAL;
1116 5 return -1;
1117 }
1118 16 pos++;
1119
1120 // check if we have a sign for the exponent
1121 16 double factor = 10.;
1122
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 9 times.
16 if (str.ptr[pos] == '-') {
1123 7 factor = .1;
1124 7 pos++;
1125
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
9 } else if (str.ptr[pos] == '+') {
1126 3 pos++;
1127 }
1128
1129 // at least one digit must follow
1130
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 14 times.
16 if (pos == str.length) {
1131 2 errno = EINVAL;
1132 2 return -1;
1133 }
1134
1135 // parse the exponent
1136 14 unsigned int exp = 0;
1137 do {
1138
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 if (str_isdigit(str.ptr[pos])) {
1139 22 exp = 10 * exp + (str.ptr[pos] - '0');
1140 } else if (strchr(groupsep, str.ptr[pos]) == NULL) {
1141 errno = EINVAL;
1142 return -1;
1143 }
1144
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 14 times.
22 } while (++pos < str.length);
1145
1146 // apply the exponent by fast exponentiation
1147 do {
1148
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 22 times.
56 if (exp & 1) {
1149 34 result *= factor;
1150 }
1151 56 factor *= factor;
1152
2/2
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 14 times.
56 } while ((exp >>= 1) > 0);
1153
1154 // store the result and exit
1155 14 *output = result * sign;
1156 14 return 0;
1157 }
1158