GCC Code Coverage Report


Directory: ./
File: string.c
Date: 2025-12-31 16:19:05
Exec Total Coverage
Lines: 465 465 100.0%
Functions: 47 47 100.0%
Branches: 303 326 92.9%

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
29 #ifdef WITH_MEMRCHR
30 #define _GNU_SOURCE
31 #endif
32
33 #include "cx/string.h"
34
35 #include <string.h>
36 #include <stdarg.h>
37 #include <assert.h>
38 #include <errno.h>
39 #include <limits.h>
40 #include <float.h>
41 #include <ctype.h>
42
43 #ifdef _WIN32
44 static int cx_fixed_strnicmp(const char* s1, const char* s2, size_t count) {
45 // Microsoft's implementation crashes when count == 0 and either string is NULL
46 if (count == 0) return 0;
47 return _strnicmp(s1, s2, count);
48 }
49 #define cx_strcasecmp_impl cx_fixed_strnicmp
50 #else
51 #include <strings.h>
52 #define cx_strcasecmp_impl strncasecmp
53 #endif
54
55 254 void cx_strfree(cxmutstr *str) {
56
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 253 times.
254 if (str == NULL) return;
57 253 cxFreeDefault(str->ptr);
58 253 str->ptr = NULL;
59 253 str->length = 0;
60 }
61
62 253 void cx_strfree_a(
63 const CxAllocator *alloc,
64 cxmutstr *str
65 ) {
66
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 252 times.
253 if (str == NULL) return;
67 252 cxFree(alloc, str->ptr);
68 252 str->ptr = NULL;
69 252 str->length = 0;
70 }
71
72 5 int cx_strcpy_a_(
73 const CxAllocator *alloc,
74 cxmutstr *dest,
75 cxstring src
76 ) {
77
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→5) taken 5 times.
5 if (cxReallocate(alloc, &dest->ptr, src.length + 1)) {
78 return 1; // LCOV_EXCL_LINE
79 }
80
81 5 memcpy(dest->ptr, src.ptr, src.length);
82 5 dest->length = src.length;
83 5 dest->ptr[dest->length] = '\0';
84
85 5 return 0;
86 }
87
88 4 size_t cx_strlen(
89 size_t count,
90 ...
91 ) {
92
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 3 times.
4 if (count == 0) return 0;
93
94 va_list ap;
95 3 va_start(ap, count);
96 3 size_t size = 0;
97
2/2
✓ Branch 0 (9→5) taken 6 times.
✓ Branch 1 (9→10) taken 3 times.
9 for (size_t i = 0; i < count; i++) {
98 6 cxstring str = va_arg(ap, cxstring);
99
1/2
✗ Branch 0 (6→7) not taken.
✓ Branch 1 (6→8) taken 6 times.
6 if (size > SIZE_MAX - str.length) errno = EOVERFLOW;
100 6 size += str.length;
101 }
102 3 va_end(ap);
103
104 3 return size;
105 }
106
107 24 cxmutstr cx_strcat_a(
108 const CxAllocator *alloc,
109 cxmutstr str,
110 size_t count,
111 ...
112 ) {
113
2/2
✓ Branch 0 (2→3) taken 3 times.
✓ Branch 1 (2→12) taken 21 times.
24 if (count == 0) {
114
1/2
✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→11) taken 3 times.
3 if (cxReallocate(alloc, &str.ptr, str.length + 1)) {
115 return CX_NULLSTR; // LCOV_EXCL_LINE
116 }
117 3 str.ptr[str.length] = '\0';
118 3 return str;
119 }
120 va_list ap;
121 21 va_start(ap, count);
122 va_list ap2;
123 21 va_copy(ap2, ap);
124
125 // compute overall length
126 21 bool overflow = false;
127 21 size_t slen = str.length;
128
2/2
✓ Branch 0 (17→13) taken 43 times.
✓ Branch 1 (17→18) taken 21 times.
64 for (size_t i = 0; i < count; i++) {
129 43 cxstring s = va_arg(ap, cxstring);
130
2/2
✓ Branch 0 (14→15) taken 1 times.
✓ Branch 1 (14→16) taken 42 times.
43 if (slen > SIZE_MAX - s.length) overflow = true;
131 43 slen += s.length;
132 }
133 21 va_end(ap);
134
135 // abort in case of overflow
136
2/2
✓ Branch 0 (18→19) taken 1 times.
✓ Branch 1 (18→25) taken 20 times.
21 if (overflow) {
137 1 va_end(ap2);
138
1/2
✗ Branch 0 (19→20) not taken.
✓ Branch 1 (19→21) taken 1 times.
1 errno = EOVERFLOW;
139 1 return CX_NULLSTR;
140 }
141
142 // reallocate or create a new string
143
1/2
✗ Branch 0 (26→27) not taken.
✓ Branch 1 (26→33) taken 20 times.
20 if (cxReallocate(alloc, &str.ptr, slen + 1)) {
144 // LCOV_EXCL_START
145 va_end(ap2);
146 return CX_NULLSTR;
147 // LCOV_EXCL_STOP
148 }
149
150 // concatenate strings
151 20 size_t pos = str.length;
152 20 str.length = slen;
153
2/2
✓ Branch 0 (36→34) taken 40 times.
✓ Branch 1 (36→37) taken 20 times.
60 for (size_t i = 0; i < count; i++) {
154 40 cxstring s = va_arg(ap2, cxstring);
155 40 memcpy(str.ptr + pos, s.ptr, s.length);
156 40 pos += s.length;
157 }
158 20 va_end(ap2);
159
160 // terminate string
161 20 str.ptr[str.length] = '\0';
162
163 20 return str;
164 }
165
166 343 cxstring cx_strsubs_(
167 cxstring string,
168 size_t start
169 ) {
170 343 return cx_strsubsl_(string, start, string.length);
171 }
172
173 371 cxstring cx_strsubsl_(
174 cxstring string,
175 size_t start,
176 size_t length
177 ) {
178
2/2
✓ Branch 0 (2→3) taken 4 times.
✓ Branch 1 (2→4) taken 367 times.
371 if (start > string.length) {
179 4 return (cxstring) {NULL, 0};
180 }
181
182 367 size_t rem_len = string.length - start;
183
2/2
✓ Branch 0 (4→5) taken 337 times.
✓ Branch 1 (4→6) taken 30 times.
367 if (length > rem_len) {
184 337 length = rem_len;
185 }
186
187 367 return (cxstring) {string.ptr + start, length};
188 }
189
190 396 cxstring cx_strchr_(
191 cxstring string,
192 int chr
193 ) {
194 396 char *ret = memchr(string.ptr, 0xFF & chr, string.length);
195
2/2
✓ Branch 0 (2→3) taken 53 times.
✓ Branch 1 (2→5) taken 343 times.
396 if (ret == NULL) return (cxstring) {NULL, 0};
196 343 return (cxstring) {ret, string.length - (ret - string.ptr)};
197 }
198
199 8 cxstring cx_strrchr_(
200 cxstring string,
201 int chr
202 ) {
203 #ifdef WITH_MEMRCHR
204 8 char *ret = memrchr(string.ptr, 0xFF & chr, string.length);
205
2/2
✓ Branch 0 (2→3) taken 2 times.
✓ Branch 1 (2→5) taken 6 times.
8 if (ret == NULL) return (cxstring) {NULL, 0};
206 6 return (cxstring) {ret, string.length - (ret - string.ptr)};
207 #else
208 chr = 0xFF & chr;
209 size_t i = string.length;
210 while (i > 0) {
211 i--;
212 if (string.ptr[i] == chr) {
213 return cx_strsubs(string, i);
214 }
215 }
216 return (cxstring) {NULL, 0};
217 #endif
218 }
219
220 #ifndef CX_STRSTR_SBO_SIZE
221 #define CX_STRSTR_SBO_SIZE 128
222 #endif
223 const unsigned cx_strstr_sbo_size = CX_STRSTR_SBO_SIZE;
224
225 439 cxstring cx_strstr_(cxstring haystack, cxstring needle) {
226
2/2
✓ Branch 0 (2→3) taken 9 times.
✓ Branch 1 (2→4) taken 430 times.
439 if (needle.length == 0) {
227 9 return haystack;
228 }
229
230 // optimize for single-char needles
231
2/2
✓ Branch 0 (4→5) taken 338 times.
✓ Branch 1 (4→9) taken 92 times.
430 if (needle.length == 1) {
232 676 return cx_strchr(haystack, *needle.ptr);
233 }
234
235 /*
236 * IMPORTANT:
237 * Our prefix table contains the prefix length PLUS ONE
238 * this is our decision, because we want to use the full range of size_t.
239 * The original algorithm needs a (-1) at one single place,
240 * and we want to avoid that.
241 */
242
243 // local prefix table
244 size_t s_prefix_table[CX_STRSTR_SBO_SIZE];
245
246 // check needle length and use appropriate prefix table
247 // if the pattern exceeds static prefix table, allocate on the heap
248 92 const bool useheap = needle.length >= CX_STRSTR_SBO_SIZE;
249 91 register size_t *ptable = useheap
250 1 ? cxCallocDefault(needle.length + 1, sizeof(size_t))
251
2/2
✓ Branch 0 (9→10) taken 1 times.
✓ Branch 1 (9→11) taken 91 times.
92 : s_prefix_table;
252
253 // keep counter in registers
254 register size_t i, j;
255
256 // fill prefix table
257 92 i = 0;
258 92 j = 0;
259 92 ptable[i] = j;
260
2/2
✓ Branch 0 (18→13) taken 614 times.
✓ Branch 1 (18→19) taken 92 times.
706 while (i < needle.length) {
261
4/4
✓ Branch 0 (15→16) taken 536 times.
✓ Branch 1 (15→17) taken 384 times.
✓ Branch 2 (16→14) taken 306 times.
✓ Branch 3 (16→17) taken 230 times.
920 while (j >= 1 && needle.ptr[j - 1] != needle.ptr[i]) {
262 306 j = ptable[j - 1];
263 }
264 614 i++;
265 614 j++;
266 614 ptable[i] = j;
267 }
268
269 // search
270 92 cxstring result = {NULL, 0};
271 92 i = 0;
272 92 j = 1;
273
2/2
✓ Branch 0 (26→20) taken 1211 times.
✓ Branch 1 (26→27) taken 32 times.
1243 while (i < haystack.length) {
274
4/4
✓ Branch 0 (22→23) taken 1269 times.
✓ Branch 1 (22→24) taken 664 times.
✓ Branch 2 (23→21) taken 722 times.
✓ Branch 3 (23→24) taken 547 times.
1933 while (j >= 1 && haystack.ptr[i] != needle.ptr[j - 1]) {
275 722 j = ptable[j - 1];
276 }
277 1211 i++;
278 1211 j++;
279
2/2
✓ Branch 0 (24→25) taken 60 times.
✓ Branch 1 (24→26) taken 1151 times.
1211 if (j - 1 == needle.length) {
280 60 size_t start = i - needle.length;
281 60 result.ptr = haystack.ptr + start;
282 60 result.length = haystack.length - start;
283 60 break;
284 }
285 }
286
287 // if prefix table was allocated on the heap, free it
288
2/2
✓ Branch 0 (27→28) taken 1 times.
✓ Branch 1 (27→29) taken 91 times.
92 if (useheap) {
289 1 cxFreeDefault(ptable);
290 }
291
292 92 return result;
293 }
294
295 28 size_t cx_strsplit_(
296 cxstring string,
297 cxstring delim,
298 size_t limit,
299 cxstring *output
300 ) {
301 // special case: output limit is zero
302
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 28 times.
28 if (limit == 0) return 0;
303
304 // special case: delimiter is empty
305
2/2
✓ Branch 0 (4→5) taken 2 times.
✓ Branch 1 (4→6) taken 26 times.
28 if (delim.length == 0) {
306 2 output[0] = string;
307 2 return 1;
308 }
309
310 // special cases: delimiter is at least as large as the string
311
2/2
✓ Branch 0 (6→7) taken 4 times.
✓ Branch 1 (6→23) taken 22 times.
26 if (delim.length >= string.length) {
312 // exact match
313
2/2
✓ Branch 0 (16→17) taken 2 times.
✓ Branch 1 (16→22) taken 2 times.
16 if (cx_strcmp(string, delim) == 0) {
314 2 output[0] = cx_strn(string.ptr, 0);
315 2 output[1] = cx_strn(string.ptr + string.length, 0);
316 2 return 2;
317 } else {
318 // no match possible
319 2 output[0] = string;
320 2 return 1;
321 }
322 }
323
324 22 size_t n = 0;
325 22 cxstring curpos = string;
326 34 while (1) {
327 56 ++n;
328 168 cxstring match = cx_strstr(curpos, delim);
329
2/2
✓ Branch 0 (31→32) taken 40 times.
✓ Branch 1 (31→37) taken 16 times.
56 if (match.length > 0) {
330 // is the limit reached?
331
2/2
✓ Branch 0 (32→33) taken 34 times.
✓ Branch 1 (32→36) taken 6 times.
40 if (n < limit) {
332 // copy the current string to the array
333 34 cxstring item = cx_strn(curpos.ptr, match.ptr - curpos.ptr);
334 34 output[n - 1] = item;
335 34 size_t processed = item.length + delim.length;
336 34 curpos.ptr += processed;
337 34 curpos.length -= processed;
338 } else {
339 // limit reached, copy the _full_ remaining string
340 6 output[n - 1] = curpos;
341 6 break;
342 }
343 } else {
344 // no more matches, copy last string
345 16 output[n - 1] = curpos;
346 16 break;
347 }
348 }
349
350 22 return n;
351 }
352
353 14 size_t cx_strsplit_a_(
354 const CxAllocator *allocator,
355 cxstring string,
356 cxstring delim,
357 size_t limit,
358 cxstring **output
359 ) {
360 // find out how many splits we're going to make and allocate memory
361 14 size_t n = 0;
362 14 cxstring curpos = string;
363 25 while (1) {
364 39 ++n;
365 117 cxstring match = cx_strstr(curpos, delim);
366
2/2
✓ Branch 0 (10→11) taken 29 times.
✓ Branch 1 (10→14) taken 10 times.
39 if (match.length > 0) {
367 // is the limit reached?
368
2/2
✓ Branch 0 (11→12) taken 25 times.
✓ Branch 1 (11→13) taken 4 times.
29 if (n < limit) {
369 25 size_t processed = match.ptr - curpos.ptr + delim.length;
370 25 curpos.ptr += processed;
371 25 curpos.length -= processed;
372 } else {
373 // limit reached
374 4 break;
375 }
376 } else {
377 // no more matches
378 10 break;
379 }
380 }
381 14 *output = cxCalloc(allocator, n, sizeof(cxstring));
382 14 return cx_strsplit_(string, delim, n, *output);
383 }
384
385 1 size_t cx_strsplit_m_(
386 cxmutstr string,
387 cxstring delim,
388 size_t limit,
389 cxmutstr *output
390 ) {
391 2 return cx_strsplit_(cx_strcast(string),
392 delim, limit, (cxstring *) output);
393 }
394
395 1 size_t cx_strsplit_ma_(
396 const CxAllocator *allocator,
397 cxmutstr string,
398 cxstring delim,
399 size_t limit,
400 cxmutstr **output
401 ) {
402 2 return cx_strsplit_a_(allocator, cx_strcast(string),
403 delim, limit, (cxstring **) output);
404 }
405
406 1123 int cx_strcmp_(
407 cxstring s1,
408 cxstring s2
409 ) {
410
2/2
✓ Branch 0 (2→3) taken 550 times.
✓ Branch 1 (2→4) taken 573 times.
1123 if (s1.length == s2.length) {
411 550 return strncmp(s1.ptr, s2.ptr, s1.length);
412
2/2
✓ Branch 0 (4→5) taken 139 times.
✓ Branch 1 (4→8) taken 434 times.
573 } else if (s1.length > s2.length) {
413 139 int r = strncmp(s1.ptr, s2.ptr, s2.length);
414
2/2
✓ Branch 0 (5→6) taken 137 times.
✓ Branch 1 (5→7) taken 2 times.
139 if (r != 0) return r;
415 2 return 1;
416 } else {
417 434 int r = strncmp(s1.ptr, s2.ptr, s1.length);
418
2/2
✓ Branch 0 (8→9) taken 431 times.
✓ Branch 1 (8→10) taken 3 times.
434 if (r != 0) return r;
419 3 return -1;
420 }
421 }
422
423 15 int cx_strcasecmp_(
424 cxstring s1,
425 cxstring s2
426 ) {
427
2/2
✓ Branch 0 (2→3) taken 8 times.
✓ Branch 1 (2→4) taken 7 times.
15 if (s1.length == s2.length) {
428 8 return cx_strcasecmp_impl(s1.ptr, s2.ptr, s1.length);
429
2/2
✓ Branch 0 (4→5) taken 4 times.
✓ Branch 1 (4→8) taken 3 times.
7 } else if (s1.length > s2.length) {
430 4 int r = cx_strcasecmp_impl(s1.ptr, s2.ptr, s2.length);
431
2/2
✓ Branch 0 (5→6) taken 2 times.
✓ Branch 1 (5→7) taken 2 times.
4 if (r != 0) return r;
432 2 return 1;
433 } else {
434 3 int r = cx_strcasecmp_impl(s1.ptr, s2.ptr, s1.length);
435
2/2
✓ Branch 0 (8→9) taken 2 times.
✓ Branch 1 (8→10) taken 1 times.
3 if (r != 0) return r;
436 1 return -1;
437 }
438 }
439
440 6 int cx_strcmp_p(
441 const void *s1,
442 const void *s2
443 ) {
444 6 const cxstring *left = s1;
445 6 const cxstring *right = s2;
446 24 return cx_strcmp(*left, *right);
447 }
448
449 2 int cx_strcasecmp_p(
450 const void *s1,
451 const void *s2
452 ) {
453 2 const cxstring *left = s1;
454 2 const cxstring *right = s2;
455 8 return cx_strcasecmp(*left, *right);
456 }
457
458 252 cxmutstr cx_strdup_a_(
459 const CxAllocator *allocator,
460 cxstring string
461 ) {
462 252 cxmutstr result = {
463 252 cxMalloc(allocator, string.length + 1),
464 252 string.length
465 };
466 // LCOV_EXCL_START
467 if (result.ptr == NULL) {
468 result.length = 0;
469 return result;
470 }
471 // LCOV_EXCL_STOP
472 252 memcpy(result.ptr, string.ptr, string.length);
473 252 result.ptr[string.length] = '\0';
474 252 return result;
475 }
476
477 220 cxstring cx_strtrim_(cxstring string) {
478 220 cxstring result = string;
479
3/4
✓ Branch 0 (8→9) taken 368 times.
✗ Branch 1 (8→10) not taken.
✓ Branch 2 (14→3) taken 148 times.
✓ Branch 3 (14→15) taken 220 times.
1472 while (isspace((unsigned char)cx_strat(result, 0))) {
480 148 result.ptr++;
481 148 result.length--;
482 }
483
3/4
✗ Branch 0 (21→22) not taken.
✓ Branch 1 (21→23) taken 327 times.
✓ Branch 2 (27→16) taken 107 times.
✓ Branch 3 (27→28) taken 220 times.
1308 while (isspace((unsigned char)cx_strat(result, -1))) {
484 107 result.length--;
485 }
486 220 return result;
487 }
488
489 5 bool cx_strprefix_(
490 cxstring string,
491 cxstring prefix
492 ) {
493
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 4 times.
5 if (string.length < prefix.length) return false;
494 4 return memcmp(string.ptr, prefix.ptr, prefix.length) == 0;
495 }
496
497 5 bool cx_strsuffix_(
498 cxstring string,
499 cxstring suffix
500 ) {
501
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 4 times.
5 if (string.length < suffix.length) return false;
502 4 return memcmp(string.ptr + string.length - suffix.length,
503 4 suffix.ptr, suffix.length) == 0;
504 }
505
506 5 bool cx_strcaseprefix_(
507 cxstring string,
508 cxstring prefix
509 ) {
510
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 4 times.
5 if (string.length < prefix.length) return false;
511 #ifdef _WIN32
512 return _strnicmp(string.ptr, prefix.ptr, prefix.length) == 0;
513 #else
514 4 return strncasecmp(string.ptr, prefix.ptr, prefix.length) == 0;
515 #endif
516 }
517
518 5 bool cx_strcasesuffix_(
519 cxstring string,
520 cxstring suffix
521 ) {
522
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 4 times.
5 if (string.length < suffix.length) return false;
523 #ifdef _WIN32
524 return _strnicmp(string.ptr+string.length-suffix.length,
525 suffix.ptr, suffix.length) == 0;
526 #else
527 4 return strncasecmp(string.ptr + string.length - suffix.length,
528 4 suffix.ptr, suffix.length) == 0;
529 #endif
530 }
531
532 15 cxmutstr cx_strreplace_(
533 const CxAllocator *allocator,
534 cxstring str,
535 cxstring search,
536 cxstring replacement,
537 size_t replmax
538 ) {
539 // special cases
540
4/6
✓ Branch 0 (2→3) taken 15 times.
✗ Branch 1 (2→5) not taken.
✓ Branch 2 (3→4) taken 14 times.
✓ Branch 3 (3→5) taken 1 times.
✗ Branch 4 (4→5) not taken.
✓ Branch 5 (4→11) taken 14 times.
15 if (search.length == 0 || search.length > str.length || replmax == 0) {
541 2 return cx_strdup_a(allocator, str);
542 }
543
544 14 size_t in_len = str.length;
545 14 size_t search_len = search.length;
546 14 size_t repl_len = replacement.length;
547
548 // first run, count the occurrences
549 // and remember where the first is
550 14 size_t occurrences = 1;
551 42 cxstring first = cx_strstr(str, search);
552
2/2
✓ Branch 0 (18→19) taken 1 times.
✓ Branch 1 (18→25) taken 13 times.
14 if (first.length == 0) {
553 // special case, no replacements
554 2 return cx_strdup_a(allocator, str);
555 }
556 13 cxstring tmp = cx_strsubs(first, search_len);
557
2/2
✓ Branch 0 (32→33) taken 149 times.
✓ Branch 1 (32→41) taken 5 times.
154 while (occurrences < replmax &&
558
2/2
✓ Branch 0 (40→29) taken 141 times.
✓ Branch 1 (40→41) taken 8 times.
447 (tmp = cx_strstr(tmp, search)).length > 0) {
559 141 occurrences++;
560 141 tmp = cx_strsubs(tmp, search_len);
561 }
562
563 // calculate necessary memory
564 13 signed long long diff_len = (signed long long) repl_len - search_len;
565 13 size_t out_len = in_len + diff_len * occurrences;
566 13 cxmutstr out = {
567 13 cxMalloc(allocator, out_len + 1),
568 out_len
569 };
570
1/2
✗ Branch 0 (42→43) not taken.
✓ Branch 1 (42→44) taken 13 times.
13 if (out.ptr == NULL) return out;
571
572 // second run: perform the replacements
573 // but start where we found the first occurrence
574 13 const char *inp = str.ptr;
575 13 tmp = first;
576 13 char *outp = out.ptr;
577
3/4
✓ Branch 0 (48→49) taken 154 times.
✓ Branch 1 (48→57) taken 13 times.
✓ Branch 2 (56→45) taken 154 times.
✗ Branch 3 (56→57) not taken.
629 while (occurrences-- > 0 && (tmp = cx_strstr(tmp, search)).length > 0) {
578 154 size_t copylen = tmp.ptr - inp;
579 154 memcpy(outp, inp, copylen);
580 154 outp += copylen;
581 154 memcpy(outp, replacement.ptr, repl_len);
582 154 outp += repl_len;
583 154 inp += copylen + search_len;
584 154 tmp = cx_strsubs(tmp, search_len);
585 }
586
587 // add the remaining string
588 13 size_t copylen = in_len - (inp - str.ptr);
589 13 memcpy(outp, inp, copylen);
590 13 out.ptr[out_len] = '\0';
591
592 13 return out;
593 }
594
595 5 CxStrtokCtx cx_strtok_(
596 cxstring str,
597 cxstring delim,
598 size_t limit
599 ) {
600 CxStrtokCtx ctx;
601 5 ctx.str = str;
602 5 ctx.delim = delim;
603 5 ctx.limit = limit;
604 5 ctx.pos = 0;
605 5 ctx.next_pos = 0;
606 5 ctx.delim_pos = 0;
607 5 ctx.found = 0;
608 5 ctx.delim_more = NULL;
609 5 ctx.delim_more_count = 0;
610 5 return ctx;
611 }
612
613 16 bool cx_strtok_next_(
614 CxStrtokCtx *ctx,
615 cxstring *token
616 ) {
617 // abortion criteria
618
4/4
✓ Branch 0 (2→3) taken 15 times.
✓ Branch 1 (2→4) taken 1 times.
✓ Branch 2 (3→4) taken 2 times.
✓ Branch 3 (3→5) taken 13 times.
16 if (ctx->found >= ctx->limit || ctx->delim_pos >= ctx->str.length) {
619 3 return false;
620 }
621
622 // determine the search start
623 26 cxstring haystack = cx_strsubs(ctx->str, ctx->next_pos);
624
625 // search the next delimiter
626 39 cxstring delim = cx_strstr(haystack, ctx->delim);
627
628 // if found, make delim capture exactly the delimiter
629
2/2
✓ Branch 0 (15→16) taken 8 times.
✓ Branch 1 (15→17) taken 5 times.
13 if (delim.length > 0) {
630 8 delim.length = ctx->delim.length;
631 }
632
633 // if more delimiters are specified, check them now
634
2/2
✓ Branch 0 (17→18) taken 5 times.
✓ Branch 1 (17→32) taken 8 times.
13 if (ctx->delim_more_count > 0) {
635
2/2
✓ Branch 0 (31→19) taken 10 times.
✓ Branch 1 (31→32) taken 5 times.
15 for (size_t i = 0; i < ctx->delim_more_count; i++) {
636 40 cxstring d = cx_strstr(haystack, ctx->delim_more[i]);
637
6/6
✓ Branch 0 (26→27) taken 7 times.
✓ Branch 1 (26→30) taken 3 times.
✓ Branch 2 (27→28) taken 4 times.
✓ Branch 3 (27→29) taken 3 times.
✓ Branch 4 (28→29) taken 1 times.
✓ Branch 5 (28→30) taken 3 times.
10 if (d.length > 0 && (delim.length == 0 || d.ptr < delim.ptr)) {
638 4 delim.ptr = d.ptr;
639 4 delim.length = ctx->delim_more[i].length;
640 }
641 }
642 }
643
644 // store the token information and adjust the context
645 13 ctx->found++;
646 13 ctx->pos = ctx->next_pos;
647 13 token->ptr = &ctx->str.ptr[ctx->pos];
648 26 ctx->delim_pos = delim.length == 0 ?
649
2/2
✓ Branch 0 (32→33) taken 2 times.
✓ Branch 1 (32→34) taken 11 times.
13 ctx->str.length : (size_t) (delim.ptr - ctx->str.ptr);
650 13 token->length = ctx->delim_pos - ctx->pos;
651 13 ctx->next_pos = ctx->delim_pos + delim.length;
652
653 13 return true;
654 }
655
656 2 void cx_strtok_delim(
657 CxStrtokCtx *ctx,
658 const cxstring *delim,
659 size_t count
660 ) {
661 2 ctx->delim_more = delim;
662 2 ctx->delim_more_count = count;
663 2 }
664
665 #define cx_strtoX_signed_impl(rtype, rmin, rmax) \
666 long long result; \
667 if (cx_strtoll_lc(str, &result, base, groupsep)) { \
668 return -1; \
669 } \
670 if (result < rmin || result > rmax) { \
671 errno = ERANGE; \
672 return -1; \
673 } \
674 *output = (rtype) result; \
675 return 0
676
677 52 int cx_strtos_lc_(cxstring str, short *output, int base, const char *groupsep) {
678
5/6
✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→9) taken 52 times.
✓ Branch 2 (9→10) taken 35 times.
✓ Branch 3 (9→11) taken 17 times.
✓ Branch 4 (10→11) taken 17 times.
✓ Branch 5 (10→12) taken 18 times.
104 cx_strtoX_signed_impl(short, SHRT_MIN, SHRT_MAX);
679 }
680
681 52 int cx_strtoi_lc_(cxstring str, int *output, int base, const char *groupsep) {
682
5/6
✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→9) taken 52 times.
✓ Branch 2 (9→10) taken 44 times.
✓ Branch 3 (9→11) taken 8 times.
✓ Branch 4 (10→11) taken 8 times.
✓ Branch 5 (10→12) taken 36 times.
104 cx_strtoX_signed_impl(int, INT_MIN, INT_MAX);
683 }
684
685 52 int cx_strtol_lc_(cxstring str, long *output, int base, const char *groupsep) {
686
1/2
✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→9) taken 52 times.
104 cx_strtoX_signed_impl(long, LONG_MIN, LONG_MAX);
687 }
688
689 575 int cx_strtoll_lc_(cxstring str, long long *output, int base, const char *groupsep) {
690 // strategy: parse as unsigned, check range, negate if required
691 575 bool neg = false;
692 575 size_t start_unsigned = 0;
693
694 // emptiness check
695
2/2
✓ Branch 0 (2→3) taken 2 times.
✓ Branch 1 (2→4) taken 573 times.
575 if (str.length == 0) {
696 2 errno = EINVAL;
697 2 return -1;
698 }
699
700 // test if we have a negative sign character
701
2/2
✓ Branch 0 (4→5) taken 223 times.
✓ Branch 1 (4→8) taken 350 times.
573 if (str.ptr[start_unsigned] == '-') {
702 223 neg = true;
703 223 start_unsigned++;
704 // must not be followed by positive sign character
705
4/4
✓ Branch 0 (5→6) taken 222 times.
✓ Branch 1 (5→7) taken 1 times.
✓ Branch 2 (6→7) taken 1 times.
✓ Branch 3 (6→8) taken 221 times.
223 if (str.length == 1 || str.ptr[start_unsigned] == '+') {
706 2 errno = EINVAL;
707 2 return -1;
708 }
709 }
710
711 // now parse the number with strtoull
712 unsigned long long v;
713 350 cxstring ustr = start_unsigned == 0 ? str
714
2/2
✓ Branch 0 (8→9) taken 221 times.
✓ Branch 1 (8→12) taken 350 times.
571 : cx_strn(str.ptr + start_unsigned, str.length - start_unsigned);
715 1142 int ret = cx_strtoull_lc(ustr, &v, base, groupsep);
716
2/2
✓ Branch 0 (18→19) taken 9 times.
✓ Branch 1 (18→20) taken 562 times.
571 if (ret != 0) return ret;
717
2/2
✓ Branch 0 (20→21) taken 218 times.
✓ Branch 1 (20→24) taken 344 times.
562 if (neg) {
718
2/2
✓ Branch 0 (21→22) taken 1 times.
✓ Branch 1 (21→23) taken 217 times.
218 if (v - 1 > LLONG_MAX) {
719 1 errno = ERANGE;
720 1 return -1;
721 }
722 217 *output = -(long long) v;
723 217 return 0;
724 } else {
725
2/2
✓ Branch 0 (24→25) taken 2 times.
✓ Branch 1 (24→26) taken 342 times.
344 if (v > LLONG_MAX) {
726 2 errno = ERANGE;
727 2 return -1;
728 }
729 342 *output = (long long) v;
730 342 return 0;
731 }
732 }
733
734 55 int cx_strtoi8_lc_(cxstring str, int8_t *output, int base, const char *groupsep) {
735
5/6
✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→9) taken 55 times.
✓ Branch 2 (9→10) taken 32 times.
✓ Branch 3 (9→11) taken 23 times.
✓ Branch 4 (10→11) taken 23 times.
✓ Branch 5 (10→12) taken 9 times.
110 cx_strtoX_signed_impl(int8_t, INT8_MIN, INT8_MAX);
736 }
737
738 57 int cx_strtoi16_lc_(cxstring str, int16_t *output, int base, const char *groupsep) {
739
6/6
✓ Branch 0 (7→8) taken 1 times.
✓ Branch 1 (7→9) taken 56 times.
✓ Branch 2 (9→10) taken 38 times.
✓ Branch 3 (9→11) taken 18 times.
✓ Branch 4 (10→11) taken 18 times.
✓ Branch 5 (10→12) taken 20 times.
114 cx_strtoX_signed_impl(int16_t, INT16_MIN, INT16_MAX);
740 }
741
742 53 int cx_strtoi32_lc_(cxstring str, int32_t *output, int base, const char *groupsep) {
743
5/6
✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→9) taken 53 times.
✓ Branch 2 (9→10) taken 45 times.
✓ Branch 3 (9→11) taken 8 times.
✓ Branch 4 (10→11) taken 8 times.
✓ Branch 5 (10→12) taken 37 times.
106 cx_strtoX_signed_impl(int32_t, INT32_MIN, INT32_MAX);
744 }
745
746 187 int cx_strtoi64_lc_(cxstring str, int64_t *output, int base, const char *groupsep) {
747 assert(sizeof(long long) == sizeof(int64_t)); // should be true on all platforms
748 374 return cx_strtoll_lc(str, (long long*) output, base, groupsep);
749 }
750
751 #define cx_strtoX_unsigned_impl(rtype, rmax) \
752 uint64_t result; \
753 if (cx_strtou64_lc(str, &result, base, groupsep)) { \
754 return -1; \
755 } \
756 if (result > rmax) { \
757 errno = ERANGE; \
758 return -1; \
759 } \
760 *output = (rtype) result; \
761 return 0
762
763 32 int cx_strtous_lc_(cxstring str, unsigned short *output, int base, const char *groupsep) {
764
3/4
✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→9) taken 32 times.
✓ Branch 2 (9→10) taken 14 times.
✓ Branch 3 (9→11) taken 18 times.
64 cx_strtoX_unsigned_impl(unsigned short, USHRT_MAX);
765 }
766
767 32 int cx_strtou_lc_(cxstring str, unsigned int *output, int base, const char *groupsep) {
768
3/4
✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→9) taken 32 times.
✓ Branch 2 (9→10) taken 5 times.
✓ Branch 3 (9→11) taken 27 times.
64 cx_strtoX_unsigned_impl(unsigned int, UINT_MAX);
769 }
770
771 32 int cx_strtoul_lc_(cxstring str, unsigned long *output, int base, const char *groupsep) {
772
1/2
✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→9) taken 32 times.
64 cx_strtoX_unsigned_impl(unsigned long, ULONG_MAX);
773 }
774
775 912 int cx_strtoull_lc_(cxstring str, unsigned long long *output, int base, const char *groupsep) {
776 // some sanity checks
777
2/2
✓ Branch 0 (2→3) taken 2 times.
✓ Branch 1 (2→4) taken 910 times.
912 if (str.length == 0) {
778 2 errno = EINVAL;
779 2 return -1;
780 }
781
8/8
✓ Branch 0 (4→5) taken 810 times.
✓ Branch 1 (4→9) taken 100 times.
✓ Branch 2 (5→6) taken 601 times.
✓ Branch 3 (5→9) taken 209 times.
✓ Branch 4 (6→7) taken 253 times.
✓ Branch 5 (6→9) taken 348 times.
✓ Branch 6 (7→8) taken 2 times.
✓ Branch 7 (7→9) taken 251 times.
910 if (!(base == 2 || base == 8 || base == 10 || base == 16)) {
782 2 errno = EINVAL;
783 2 return -1;
784 }
785
1/2
✗ Branch 0 (9→10) not taken.
✓ Branch 1 (9→11) taken 908 times.
908 if (groupsep == NULL) groupsep = "";
786
787 // find the actual start of the number
788
2/2
✓ Branch 0 (11→12) taken 4 times.
✓ Branch 1 (11→14) taken 904 times.
908 if (str.ptr[0] == '+') {
789 4 str.ptr++;
790 4 str.length--;
791
2/2
✓ Branch 0 (12→13) taken 1 times.
✓ Branch 1 (12→14) taken 3 times.
4 if (str.length == 0) {
792 1 errno = EINVAL;
793 1 return -1;
794 }
795 }
796 907 size_t start = 0;
797
798 // if base is 2 or 16, some leading stuff may appear
799
2/2
✓ Branch 0 (14→15) taken 100 times.
✓ Branch 1 (14→21) taken 807 times.
907 if (base == 2) {
800
2/2
✓ Branch 0 (15→16) taken 1 times.
✓ Branch 1 (15→17) taken 99 times.
100 if ((str.ptr[0] | 32) == 'b') {
801 1 start = 1;
802
4/4
✓ Branch 0 (17→18) taken 95 times.
✓ Branch 1 (17→29) taken 4 times.
✓ Branch 2 (18→19) taken 86 times.
✓ Branch 3 (18→29) taken 9 times.
99 } else if (str.ptr[0] == '0' && str.length > 1) {
803
2/2
✓ Branch 0 (19→20) taken 85 times.
✓ Branch 1 (19→29) taken 1 times.
86 if ((str.ptr[1] | 32) == 'b') {
804 85 start = 2;
805 }
806 }
807
2/2
✓ Branch 0 (21→22) taken 251 times.
✓ Branch 1 (21→29) taken 556 times.
807 } else if (base == 16) {
808
4/4
✓ Branch 0 (22→23) taken 245 times.
✓ Branch 1 (22→24) taken 6 times.
✓ Branch 2 (23→24) taken 1 times.
✓ Branch 3 (23→25) taken 244 times.
251 if ((str.ptr[0] | 32) == 'x' || str.ptr[0] == '#') {
809 7 start = 1;
810
4/4
✓ Branch 0 (25→26) taken 223 times.
✓ Branch 1 (25→29) taken 21 times.
✓ Branch 2 (26→27) taken 214 times.
✓ Branch 3 (26→29) taken 9 times.
244 } else if (str.ptr[0] == '0' && str.length > 1) {
811
2/2
✓ Branch 0 (27→28) taken 197 times.
✓ Branch 1 (27→29) taken 17 times.
214 if ((str.ptr[1] | 32) == 'x') {
812 197 start = 2;
813 }
814 }
815 }
816
817 // check if there are digits left
818
2/2
✓ Branch 0 (29→30) taken 5 times.
✓ Branch 1 (29→31) taken 902 times.
907 if (start >= str.length) {
819 5 errno = EINVAL;
820 5 return -1;
821 }
822
823 // now parse the number
824 902 unsigned long long result = 0;
825
2/2
✓ Branch 0 (47→32) taken 7454 times.
✓ Branch 1 (47→48) taken 890 times.
8344 for (size_t i = start; i < str.length; i++) {
826 // ignore group separators
827
2/2
✓ Branch 0 (32→33) taken 13 times.
✓ Branch 1 (32→34) taken 7441 times.
7454 if (strchr(groupsep, str.ptr[i])) continue;
828
829 // determine the digit value of the character
830 7441 unsigned char c = str.ptr[i];
831
2/2
✓ Branch 0 (34→35) taken 283 times.
✓ Branch 1 (34→36) taken 7158 times.
7441 if (c >= 'a') c = 10 + (c - 'a');
832
2/2
✓ Branch 0 (36→37) taken 425 times.
✓ Branch 1 (36→38) taken 6733 times.
7158 else if (c >= 'A') c = 10 + (c - 'A');
833
2/2
✓ Branch 0 (38→39) taken 6722 times.
✓ Branch 1 (38→40) taken 11 times.
6733 else if (c >= '0') c = c - '0';
834 11 else c = 255;
835
2/2
✓ Branch 0 (41→42) taken 11 times.
✓ Branch 1 (41→43) taken 7430 times.
7441 if (c >= base) {
836 11 errno = EINVAL;
837 11 return -1;
838 }
839
840 // now combine the digit with what we already have
841 7430 unsigned long right = (result & 0xff) * base + c;
842 7430 unsigned long long left = (result >> 8) * base + (right >> 8);
843
2/2
✓ Branch 0 (43→44) taken 1 times.
✓ Branch 1 (43→45) taken 7429 times.
7430 if (left > (ULLONG_MAX >> 8)) {
844 1 errno = ERANGE;
845 1 return -1;
846 }
847 7429 result = (left << 8) + (right & 0xff);
848 }
849
850 890 *output = result;
851 890 return 0;
852 }
853
854 34 int cx_strtou8_lc_(cxstring str, uint8_t *output, int base, const char *groupsep) {
855
3/4
✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→9) taken 34 times.
✓ Branch 2 (9→10) taken 21 times.
✓ Branch 3 (9→11) taken 13 times.
68 cx_strtoX_unsigned_impl(uint8_t, UINT8_MAX);
856 }
857
858 70 int cx_strtou16_lc_(cxstring str, uint16_t *output, int base, const char *groupsep) {
859
4/4
✓ Branch 0 (7→8) taken 3 times.
✓ Branch 1 (7→9) taken 67 times.
✓ Branch 2 (9→10) taken 14 times.
✓ Branch 3 (9→11) taken 53 times.
140 cx_strtoX_unsigned_impl(uint16_t, UINT16_MAX);
860 }
861
862 34 int cx_strtou32_lc_(cxstring str, uint32_t *output, int base, const char *groupsep) {
863
3/4
✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→9) taken 34 times.
✓ Branch 2 (9→10) taken 5 times.
✓ Branch 3 (9→11) taken 29 times.
68 cx_strtoX_unsigned_impl(uint32_t, UINT32_MAX);
864 }
865
866 266 int cx_strtou64_lc_(cxstring str, uint64_t *output, int base, const char *groupsep) {
867 assert(sizeof(unsigned long long) == sizeof(uint64_t)); // should be true on all platforms
868 532 return cx_strtoull_lc(str, (unsigned long long*) output, base, groupsep);
869 }
870
871 32 int cx_strtoz_lc_(cxstring str, size_t *output, int base, const char *groupsep) {
872 #if SIZE_MAX == UINT32_MAX
873 return cx_strtou32_lc_(str, (uint32_t*) output, base, groupsep);
874 #elif SIZE_MAX == UINT64_MAX
875 32 return cx_strtoull_lc_(str, (unsigned long long *) output, base, groupsep);
876 #else
877 #error "unsupported size_t size"
878 #endif
879 }
880
881 18 int cx_strtof_lc_(cxstring str, float *output, char decsep, const char *groupsep) {
882 // use string to double and add a range check
883 double d;
884 18 int ret = cx_strtod_lc_(str, &d, decsep, groupsep);
885
2/2
✓ Branch 0 (3→4) taken 5 times.
✓ Branch 1 (3→5) taken 13 times.
18 if (ret != 0) return ret;
886 // note: FLT_MIN is the smallest POSITIVE number that can be represented
887
2/2
✓ Branch 0 (5→6) taken 3 times.
✓ Branch 1 (5→7) taken 10 times.
13 double test = d < 0 ? -d : d;
888
4/4
✓ Branch 0 (8→9) taken 12 times.
✓ Branch 1 (8→10) taken 1 times.
✓ Branch 2 (9→10) taken 2 times.
✓ Branch 3 (9→11) taken 10 times.
13 if (test < FLT_MIN || test > FLT_MAX) {
889 3 errno = ERANGE;
890 3 return -1;
891 }
892 10 *output = (float) d;
893 10 return 0;
894 }
895
896 62 int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupsep) {
897 // TODO: overflow check
898 // TODO: increase precision
899
900 // emptiness check
901
2/2
✓ Branch 0 (2→3) taken 2 times.
✓ Branch 1 (2→4) taken 60 times.
62 if (str.length == 0) {
902 2 errno = EINVAL;
903 2 return -1;
904 }
905
906 60 double result = 0.;
907 60 int sign = 1;
908
909 // check if there is a sign
910
2/2
✓ Branch 0 (4→5) taken 11 times.
✓ Branch 1 (4→6) taken 49 times.
60 if (str.ptr[0] == '-') {
911 11 sign = -1;
912 11 str.ptr++;
913 11 str.length--;
914
2/2
✓ Branch 0 (6→7) taken 3 times.
✓ Branch 1 (6→8) taken 46 times.
49 } else if (str.ptr[0] == '+') {
915 3 str.ptr++;
916 3 str.length--;
917 }
918
919 // there must be at least one char to parse
920
2/2
✓ Branch 0 (8→9) taken 2 times.
✓ Branch 1 (8→10) taken 58 times.
60 if (str.length == 0) {
921 2 errno = EINVAL;
922 2 return -1;
923 }
924
925 // parse all digits until we find the decsep
926 58 size_t pos = 0;
927 do {
928
2/2
✓ Branch 0 (11→12) taken 110 times.
✓ Branch 1 (11→13) taken 60 times.
170 if (isdigit((unsigned char)str.ptr[pos])) {
929 110 result = result * 10 + (str.ptr[pos] - '0');
930
2/2
✓ Branch 0 (13→14) taken 57 times.
✓ Branch 1 (13→15) taken 3 times.
60 } else if (strchr(groupsep, str.ptr[pos]) == NULL) {
931 57 break;
932 }
933
2/2
✓ Branch 0 (15→11) taken 112 times.
✓ Branch 1 (15→16) taken 1 times.
113 } while (++pos < str.length);
934
935 // already done?
936
2/2
✓ Branch 0 (16→17) taken 1 times.
✓ Branch 1 (16→18) taken 57 times.
58 if (pos == str.length) {
937 1 *output = result * sign;
938 1 return 0;
939 }
940
941 // is the next char the decsep?
942
2/2
✓ Branch 0 (18→19) taken 41 times.
✓ Branch 1 (18→29) taken 16 times.
57 if (str.ptr[pos] == decsep) {
943 41 pos++;
944 // it may end with the decsep, if it did not start with it
945
2/2
✓ Branch 0 (19→20) taken 2 times.
✓ Branch 1 (19→23) taken 39 times.
41 if (pos == str.length) {
946
2/2
✓ Branch 0 (20→21) taken 1 times.
✓ Branch 1 (20→22) taken 1 times.
2 if (str.length == 1) {
947 1 errno = EINVAL;
948 1 return -1;
949 } else {
950 1 *output = result * sign;
951 1 return 0;
952 }
953 }
954 // parse everything until exponent or end
955 39 double factor = 1.;
956 do {
957
2/2
✓ Branch 0 (24→25) taken 125 times.
✓ Branch 1 (24→26) taken 13 times.
138 if (isdigit((unsigned char)str.ptr[pos])) {
958 125 factor *= 0.1;
959 125 result = result + factor * (str.ptr[pos] - '0');
960
2/2
✓ Branch 0 (26→27) taken 11 times.
✓ Branch 1 (26→28) taken 2 times.
13 } else if (strchr(groupsep, str.ptr[pos]) == NULL) {
961 11 break;
962 }
963
2/2
✓ Branch 0 (28→24) taken 99 times.
✓ Branch 1 (28→29) taken 28 times.
127 } while (++pos < str.length);
964 }
965
966 // no exponent?
967
2/2
✓ Branch 0 (29→30) taken 28 times.
✓ Branch 1 (29→31) taken 27 times.
55 if (pos == str.length) {
968 28 *output = result * sign;
969 28 return 0;
970 }
971
972 // now the next separator MUST be the exponent separator
973 // and at least one char must follow
974
4/4
✓ Branch 0 (31→32) taken 19 times.
✓ Branch 1 (31→33) taken 8 times.
✓ Branch 2 (32→33) taken 1 times.
✓ Branch 3 (32→34) taken 18 times.
27 if ((str.ptr[pos] | 32) != 'e' || str.length <= pos + 1) {
975 9 errno = EINVAL;
976 9 return -1;
977 }
978 18 pos++;
979
980 // check if we have a sign for the exponent
981 18 double factor = 10.;
982
2/2
✓ Branch 0 (34→35) taken 7 times.
✓ Branch 1 (34→36) taken 11 times.
18 if (str.ptr[pos] == '-') {
983 7 factor = .1;
984 7 pos++;
985
2/2
✓ Branch 0 (36→37) taken 3 times.
✓ Branch 1 (36→38) taken 8 times.
11 } else if (str.ptr[pos] == '+') {
986 3 pos++;
987 }
988
989 // at least one digit must follow
990
2/2
✓ Branch 0 (38→39) taken 2 times.
✓ Branch 1 (38→40) taken 16 times.
18 if (pos == str.length) {
991 2 errno = EINVAL;
992 2 return -1;
993 }
994
995 // parse the exponent
996 16 unsigned int exp = 0;
997 do {
998
2/2
✓ Branch 0 (41→42) taken 26 times.
✓ Branch 1 (41→43) taken 2 times.
28 if (isdigit((unsigned char)str.ptr[pos])) {
999 26 exp = 10 * exp + (str.ptr[pos] - '0');
1000
2/2
✓ Branch 0 (43→44) taken 1 times.
✓ Branch 1 (43→45) taken 1 times.
2 } else if (strchr(groupsep, str.ptr[pos]) == NULL) {
1001 1 errno = EINVAL;
1002 1 return -1;
1003 }
1004
2/2
✓ Branch 0 (45→41) taken 12 times.
✓ Branch 1 (45→46) taken 15 times.
27 } while (++pos < str.length);
1005
1006 // apply the exponent by fast exponentiation
1007 do {
1008
2/2
✓ Branch 0 (46→47) taken 39 times.
✓ Branch 1 (46→48) taken 23 times.
62 if (exp & 1) {
1009 39 result *= factor;
1010 }
1011 62 factor *= factor;
1012
2/2
✓ Branch 0 (48→46) taken 47 times.
✓ Branch 1 (48→49) taken 15 times.
62 } while ((exp >>= 1) > 0);
1013
1014 // store the result and exit
1015 15 *output = result * sign;
1016 15 return 0;
1017 }
1018