GCC Code Coverage Report


Directory: ../src/
File: /home/joels/Current/lispbm/src/extensions/string_extensions.c
Date: 2025-10-27 19:12:55
Exec Total Coverage
Lines: 417 436 95.6%
Functions: 22 22 100.0%
Branches: 248 268 92.5%

Line Branch Exec Source
1 /*
2 Copyright 2022, 2023 - 2025 Joel Svensson svenssonjoel@yahoo.se
3 Copyright 2022, 2023 Benjamin Vedder
4 Copyright 2024 Rasmus Söderhielm rasmus.soderhielm@gmail.com
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "extensions.h"
21 #include "lbm_memory.h"
22 #include "heap.h"
23 #include "fundamental.h"
24 #include "lbm_c_interop.h"
25 #include "eval_cps.h"
26 #include "print.h"
27
28 #include <ctype.h>
29
30 #ifdef LBM_OPT_STRING_EXTENSIONS_SIZE
31 #pragma GCC optimize ("-Os")
32 #endif
33 #ifdef LBM_OPT_STRING_EXTENSIONS_SIZE_AGGRESSIVE
34 #pragma GCC optimize ("-Oz")
35 #endif
36
37 #ifndef MIN
38 #define MIN(a,b) (((a)<(b))?(a):(b))
39 #endif
40 #ifndef MAX
41 #define MAX(a,b) (((a)>(b))?(a):(b))
42 #endif
43
44 static char print_val_buffer[256];
45
46 static lbm_uint sym_left;
47 static lbm_uint sym_case_insensitive;
48
49
50 384624 static size_t strlen_max(const char *s, size_t maxlen) {
51 size_t i;
52
2/2
✓ Branch 0 taken 1221774 times.
✓ Branch 1 taken 29 times.
1221803 for (i = 0; i < maxlen; i ++) {
53
2/2
✓ Branch 0 taken 384595 times.
✓ Branch 1 taken 837179 times.
1221774 if (s[i] == 0) break;
54 }
55 384624 return i;
56 }
57
58 193007 static bool dec_str_size(lbm_value v, char **data, size_t *size) {
59 193007 bool result = false;
60 193007 lbm_array_header_t *array = lbm_dec_array_r(v);
61
2/2
✓ Branch 0 taken 192938 times.
✓ Branch 1 taken 69 times.
193007 if (array) {
62 192938 *data = (char*)array->data;
63 192938 *size = array->size;
64 192938 result = true;
65 }
66 193007 return result;
67 }
68
69 40217 static lbm_value ext_str_from_n(lbm_value *args, lbm_uint argn) {
70
4/4
✓ Branch 0 taken 201 times.
✓ Branch 1 taken 40016 times.
✓ Branch 2 taken 84 times.
✓ Branch 3 taken 117 times.
40217 if (argn != 1 && argn != 2) {
71 84 lbm_set_error_reason((char*)lbm_error_str_num_args);
72 84 return ENC_SYM_EERROR;
73 }
74
2/2
✓ Branch 0 taken 57 times.
✓ Branch 1 taken 40076 times.
40133 if (!lbm_is_number(args[0])) {
75 57 return ENC_SYM_TERROR;
76 }
77
78
4/4
✓ Branch 0 taken 117 times.
✓ Branch 1 taken 39959 times.
✓ Branch 2 taken 30 times.
✓ Branch 3 taken 87 times.
40076 if (argn == 2 && !lbm_is_array_r(args[1])) {
79 30 return ENC_SYM_TERROR;
80 }
81
82 40046 char *format = 0;
83
2/2
✓ Branch 0 taken 87 times.
✓ Branch 1 taken 39959 times.
40046 if (argn == 2) {
84 87 format = lbm_dec_str(args[1]);
85 }
86
87 char buffer[100];
88 40046 size_t len = 0;
89
90
2/2
✓ Branch 0 taken 227 times.
✓ Branch 1 taken 39819 times.
40046 switch (lbm_type_of_functional(args[0])) {
91 227 case LBM_TYPE_DOUBLE: /* fall through */
92 case LBM_TYPE_FLOAT:
93
2/2
✓ Branch 0 taken 168 times.
✓ Branch 1 taken 59 times.
227 if (!format) {
94 168 format = "%g";
95 }
96 227 len = (size_t)snprintf(buffer, sizeof(buffer), format, lbm_dec_as_double(args[0]));
97 227 break;
98
99 39819 default:
100
2/2
✓ Branch 0 taken 39791 times.
✓ Branch 1 taken 28 times.
39819 if (!format) {
101 39791 format = "%d";
102 }
103 39819 len = (size_t)snprintf(buffer, sizeof(buffer), format, lbm_dec_as_i32(args[0]));
104 39819 break;
105 }
106
107 40046 len = MIN(len, sizeof(buffer));
108
109 lbm_value res;
110
2/2
✓ Branch 0 taken 40024 times.
✓ Branch 1 taken 22 times.
40046 if (lbm_create_array(&res, len + 1)) {
111 40024 lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(res);
112 40024 memcpy(arr->data, buffer, len);
113 40024 ((char*)(arr->data))[len] = '\0';
114 40024 return res;
115 } else {
116 22 return ENC_SYM_MERROR;
117 }
118 }
119
120 // signature: (str-join strings [delim]) -> str
121 96102 static lbm_value ext_str_join(lbm_value *args, lbm_uint argn) {
122 // This function does not check that the string arguments contain any
123 // terminating null bytes.
124
125
4/4
✓ Branch 0 taken 84353 times.
✓ Branch 1 taken 11749 times.
✓ Branch 2 taken 28 times.
✓ Branch 3 taken 84325 times.
96102 if (argn != 1 && argn != 2) {
126 28 lbm_set_error_reason((char *)lbm_error_str_num_args);
127 28 return ENC_SYM_EERROR;
128 }
129
130 96074 size_t str_len = 0;
131 96074 size_t str_count = 0;
132
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 96045 times.
96074 if (!lbm_is_list(args[0])) {
133 29 lbm_set_error_reason((char *)lbm_error_str_incorrect_arg);
134 29 lbm_set_error_suspect(args[0]);
135 29 return ENC_SYM_TERROR;
136 }
137
2/2
✓ Branch 0 taken 192248 times.
✓ Branch 1 taken 96016 times.
288264 for (lbm_value current = args[0]; lbm_is_cons(current); current = lbm_cdr(current)) {
138 192248 lbm_value car_val = lbm_car(current);
139 192248 char *str = NULL;
140 192248 size_t arr_size = 0;
141
2/2
✓ Branch 0 taken 192219 times.
✓ Branch 1 taken 29 times.
192248 if (dec_str_size(car_val, &str, &arr_size)) {
142 192219 str_len += strlen_max(str, arr_size);
143 192219 str_count += 1;
144 } else {
145 29 lbm_set_error_reason((char *)lbm_error_str_incorrect_arg);
146 29 lbm_set_error_suspect(args[0]);
147 29 return ENC_SYM_TERROR;
148 }
149 }
150
151 96016 const char *delim = "";
152
2/2
✓ Branch 0 taken 84269 times.
✓ Branch 1 taken 11747 times.
96016 if (argn >= 2) {
153 84269 delim = lbm_dec_str(args[1]);
154
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 84240 times.
84269 if (!delim) {
155 29 lbm_set_error_reason((char *)lbm_error_str_incorrect_arg);
156 29 lbm_set_error_suspect(args[1]);
157 29 return ENC_SYM_TERROR;
158 }
159 }
160
161 95987 size_t delim_len = strlen(delim);
162
2/2
✓ Branch 0 taken 67899 times.
✓ Branch 1 taken 28088 times.
95987 if (str_count > 0) {
163 67899 str_len += (str_count - 1) * delim_len;
164 }
165
166 lbm_value result;
167
2/2
✓ Branch 0 taken 96 times.
✓ Branch 1 taken 95891 times.
95987 if (!lbm_create_array(&result, str_len + 1)) {
168 96 return ENC_SYM_MERROR;
169 }
170 95891 char *result_str = lbm_dec_str(result);
171
172 95891 size_t i = 0;
173 95891 size_t offset = 0;
174
2/2
✓ Branch 0 taken 191968 times.
✓ Branch 1 taken 95891 times.
287859 for (lbm_value current = args[0]; lbm_is_cons(current); current = lbm_cdr(current)) {
175 191968 lbm_value car_val = lbm_car(current);
176 // All arrays have been prechecked.
177 191968 lbm_array_header_t *array = (lbm_array_header_t*) lbm_car(car_val);
178 191968 char *str = (char*)array->data;
179 191968 size_t len = strlen_max(str, array->size);
180
181 191968 memcpy(result_str + offset, str, len);
182 191968 offset += len;
183
184
2/2
✓ Branch 0 taken 124133 times.
✓ Branch 1 taken 67835 times.
191968 if (i != str_count - 1) {
185 124133 memcpy(result_str + offset, delim, delim_len);
186 124133 offset += delim_len;
187 }
188 191968 i++;
189 }
190
191 95891 result_str[str_len] = '\0';
192
193 95891 return result;
194 }
195
196 451 static lbm_value ext_str_to_i(lbm_value *args, lbm_uint argn) {
197
4/4
✓ Branch 0 taken 57 times.
✓ Branch 1 taken 394 times.
✓ Branch 2 taken 28 times.
✓ Branch 3 taken 29 times.
451 if (argn != 1 && argn != 2) {
198 28 lbm_set_error_reason((char*)lbm_error_str_num_args);
199 28 return ENC_SYM_EERROR;
200 }
201
202 423 char *str = lbm_dec_str(args[0]);
203
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 394 times.
423 if (!str) {
204 29 return ENC_SYM_TERROR;
205 }
206
207 394 int base = 0;
208
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 365 times.
394 if (argn == 2) {
209
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 28 times.
29 if (!lbm_is_number(args[1])) {
210 1 return ENC_SYM_TERROR;
211 }
212
213 28 base = (int)lbm_dec_as_u32(args[1]);
214 }
215
216 393 return lbm_enc_i32((int32_t)strtol(str, NULL, base));
217 }
218
219 113 static lbm_value ext_str_to_f(lbm_value *args, lbm_uint argn) {
220
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 85 times.
113 if (argn != 1) {
221 28 lbm_set_error_reason((char*)lbm_error_str_num_args);
222 28 return ENC_SYM_EERROR;
223 }
224
225 85 char *str = lbm_dec_str(args[0]);
226
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 56 times.
85 if (!str) {
227 29 return ENC_SYM_TERROR;
228 }
229
230 56 return lbm_enc_float(strtof(str, NULL));
231 }
232
233 119 static lbm_value ext_str_part(lbm_value *args, lbm_uint argn) {
234
6/6
✓ Branch 0 taken 87 times.
✓ Branch 1 taken 32 times.
✓ Branch 2 taken 59 times.
✓ Branch 3 taken 28 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 90 times.
119 if ((argn != 2 && argn != 3) || !lbm_is_number(args[1])) {
235 29 lbm_set_error_reason((char*)lbm_error_str_num_args);
236 29 return ENC_SYM_TERROR;
237 }
238
239 90 size_t str_arr_len = 0;
240 90 char *str = NULL;//lbm_dec_str(args[0]);
241
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 89 times.
90 if (!dec_str_size(args[0], &str, &str_arr_len)) {
242 1 return ENC_SYM_TERROR;
243 }
244
245 89 uint32_t len = (uint32_t)strlen_max(str, str_arr_len);
246
247 89 uint32_t start = lbm_dec_as_u32(args[1]);
248
249
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 87 times.
89 if (start >= len) {
250 2 return ENC_SYM_EERROR;
251 }
252
253 87 uint32_t n = len - start;
254
2/2
✓ Branch 0 taken 59 times.
✓ Branch 1 taken 28 times.
87 if (argn == 3) {
255
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 58 times.
59 if (!lbm_is_number(args[2])) {
256 1 return ENC_SYM_TERROR;
257 }
258
259
2/2
✓ Branch 0 taken 57 times.
✓ Branch 1 taken 1 times.
58 n = MIN(lbm_dec_as_u32(args[2]), n);
260 }
261
262 lbm_value res;
263
1/2
✓ Branch 0 taken 86 times.
✗ Branch 1 not taken.
86 if (lbm_create_array(&res, n + 1)) {
264 86 lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(res);
265 86 memcpy(arr->data, str + start, n);
266 86 ((char*)(arr->data))[n] = '\0';
267 86 return res;
268 } else {
269 return ENC_SYM_MERROR;
270 }
271 }
272
273 1325 static bool char_in(char c, char *delim, unsigned int max_ix) {
274 1325 char *d = delim;
275 1325 unsigned int i = 0;
276
3/4
✓ Branch 0 taken 2534 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1633 times.
✓ Branch 3 taken 901 times.
2534 while (i < max_ix && *d != '\0' ) {
277
2/2
✓ Branch 0 taken 424 times.
✓ Branch 1 taken 1209 times.
1633 if (c == *d) return true;
278 1209 d++; i++;
279 }
280 901 return false;
281 }
282
283 238 static lbm_value ext_str_split(lbm_value *args, lbm_uint argn) {
284
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 209 times.
238 if (argn != 2) {
285 29 lbm_set_error_reason((char*)lbm_error_str_num_args);
286 29 return ENC_SYM_TERROR;
287 }
288
289 209 size_t str_arr_size = 0;
290 209 char *str = NULL; //lbm_dec_str(args[0]);
291
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 208 times.
209 if (!dec_str_size(args[0], &str, &str_arr_size)) {
292 1 return ENC_SYM_TERROR;
293 }
294
295 208 char *delim = NULL;
296 208 size_t delim_arr_size = 0;
297
298
2/2
✓ Branch 0 taken 58 times.
✓ Branch 1 taken 150 times.
208 if (lbm_is_number(args[1])) {
299
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 28 times.
58 int step = MAX(lbm_dec_as_i32(args[1]), 1);
300 58 lbm_value res = ENC_SYM_NIL;
301 58 int len = (int)strlen_max(str, str_arr_size);
302
2/2
✓ Branch 0 taken 562 times.
✓ Branch 1 taken 58 times.
620 for (int i = len / step;i >= 0;i--) {
303 562 int ind_now = i * step;
304
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 534 times.
562 if (ind_now >= len) {
305 28 continue;
306 }
307
308 534 int step_now = step;
309
2/2
✓ Branch 0 taken 96 times.
✓ Branch 1 taken 534 times.
630 while ((ind_now + step_now) > len) {
310 96 step_now--;
311 }
312
313 lbm_value tok;
314
1/2
✓ Branch 0 taken 534 times.
✗ Branch 1 not taken.
534 if (lbm_create_array(&tok, (lbm_uint)step_now + 1)) {
315 534 lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(tok);
316 534 memcpy(arr->data, str + ind_now, (unsigned int)step_now);
317 534 ((char*)(arr->data))[step_now] = '\0';
318 534 res = lbm_cons(tok, res);
319 } else {
320 return ENC_SYM_MERROR;
321 }
322 }
323 58 return res;
324
1/2
✓ Branch 0 taken 150 times.
✗ Branch 1 not taken.
150 } else if (dec_str_size(args[1], &delim, &delim_arr_size)) {
325 150 lbm_value res = ENC_SYM_NIL;
326
327 150 unsigned int i_start = 0;
328 150 unsigned int i_end = 0;
329
330 // Abort when larger that array size. Protection against abuse
331 // with byte-arrays.
332
1/2
✓ Branch 0 taken 574 times.
✗ Branch 1 not taken.
574 while (i_end < str_arr_size) {
333
334
4/4
✓ Branch 0 taken 1325 times.
✓ Branch 1 taken 150 times.
✓ Branch 2 taken 901 times.
✓ Branch 3 taken 424 times.
1475 while (str[i_end] != '\0' && !char_in(str[i_end], delim, (unsigned int)delim_arr_size)) {
335 901 i_end ++;
336 }
337
338 574 unsigned int len = i_end - i_start;
339 574 char *s = &str[i_start];
340 lbm_value tok;
341
1/2
✓ Branch 0 taken 574 times.
✗ Branch 1 not taken.
574 if (lbm_create_array(&tok, len + 1)) {
342 574 lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(tok);
343 574 memcpy(arr->data, s, len);
344 574 ((char*)(arr->data))[len] = '\0';
345 574 res = lbm_cons(tok, res);
346
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 574 times.
574 if (res == ENC_SYM_MERROR) return res;
347 } else {
348 return ENC_SYM_MERROR;
349 }
350
351
2/2
✓ Branch 0 taken 150 times.
✓ Branch 1 taken 424 times.
574 if (str[i_end] == '\0') break;
352 424 i_start = i_end + 1;
353 424 i_end = i_end + 1;
354
355 }
356 150 return lbm_list_destructive_reverse(res);
357 }
358 return ENC_SYM_TERROR;
359 }
360
361 // Todo: Clean this up for 64bit
362 100 static lbm_value ext_str_replace(lbm_value *args, lbm_uint argn) {
363
4/4
✓ Branch 0 taken 70 times.
✓ Branch 1 taken 30 times.
✓ Branch 2 taken 30 times.
✓ Branch 3 taken 40 times.
100 if (argn != 2 && argn != 3) {
364 30 lbm_set_error_reason((char*)lbm_error_str_num_args);
365 30 return ENC_SYM_EERROR;
366 }
367
368 70 size_t orig_arr_size = 0;
369 70 char *orig = NULL; // lbm_dec_str(args[0]);
370
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 68 times.
70 if (!dec_str_size(args[0], &orig, &orig_arr_size)) {
371 2 return ENC_SYM_TERROR;
372 }
373
374 68 size_t rep_arr_size = 0;
375 68 char *rep = NULL; //lbm_dec_str(args[1]);
376
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 65 times.
68 if (!dec_str_size(args[1], &rep, &rep_arr_size)) {
377 3 return ENC_SYM_TERROR;
378 }
379
380 65 size_t with_arr_size = 0;
381 65 char *with = "";
382
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 29 times.
65 if (argn == 3) {
383
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 34 times.
36 if (!dec_str_size(args[2], &with, &with_arr_size)) {
384 2 return ENC_SYM_TERROR;
385 }
386 }
387
388 // See https://stackoverflow.com/questions/779875/what-function-is-to-replace-a-substring-from-a-string-in-c
389 //char *result; // the return string
390 char *ins; // the next insert point
391 char *tmp; // varies
392 size_t len_rep; // length of rep (the string to remove)
393 size_t len_with; // length of with (the string to replace rep with)
394 //size_t len_front; // distance between rep and end of last rep
395 int count; // number of replacements
396
397 63 len_rep = strlen_max(rep, rep_arr_size);
398
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 61 times.
63 if (len_rep == 0) {
399 2 return args[0]; // empty rep causes infinite loop during count
400 }
401
402 61 len_with = strlen_max(with,with_arr_size);
403
404 // count the number of replacements needed
405 61 ins = orig;
406
2/2
✓ Branch 0 taken 109 times.
✓ Branch 1 taken 61 times.
170 for (count = 0; (tmp = strstr(ins, rep)); ++count) {
407 109 ins = tmp + len_rep;
408 }
409
410 61 size_t len_res = strlen_max(orig, orig_arr_size) + (len_with - len_rep) * (unsigned int)count + 1;
411 lbm_value lbm_res;
412
1/2
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
61 if (lbm_create_array(&lbm_res, len_res)) {
413 61 lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(lbm_res);
414 61 tmp = (char*)arr->data;
415 } else {
416 return ENC_SYM_MERROR;
417 }
418
419 // first time through the loop, all the variable are set correctly
420 // from here on,
421 // tmp points to the end of the result string
422 // ins points to the next occurrence of rep in orig
423 // orig points to the remainder of orig after "end of rep"
424
2/2
✓ Branch 0 taken 109 times.
✓ Branch 1 taken 61 times.
170 while (count--) {
425 109 ins = strstr(orig, rep);
426 109 size_t len_front = (size_t)ins - (size_t)orig;
427 109 tmp = strncpy(tmp, orig, len_front) + len_front;
428 109 tmp = strncpy(tmp, with, len_with) + len_with;
429 109 orig += len_front + len_rep; // move to next "end of rep"
430 }
431 61 strcpy(tmp, orig);
432
433 61 return lbm_res;
434 }
435
436 125 static lbm_value change_case(lbm_value *args, lbm_uint argn, bool to_upper) {
437
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 69 times.
125 if (argn != 1) {
438 56 lbm_set_error_reason((char*)lbm_error_str_num_args);
439 56 return ENC_SYM_EERROR;
440 }
441
442 69 size_t orig_arr_size = 0;
443 69 char *orig = NULL; //lbm_dec_str(args[0]);
444
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 67 times.
69 if (!dec_str_size(args[0], &orig, &orig_arr_size)) {
445 2 return ENC_SYM_TERROR;
446 }
447
448 67 size_t len = strlen_max(orig,orig_arr_size);
449 lbm_value lbm_res;
450
1/2
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
67 if (lbm_create_array(&lbm_res, len + 1)) {
451 67 lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(lbm_res);
452
2/2
✓ Branch 0 taken 332 times.
✓ Branch 1 taken 67 times.
399 for (unsigned int i = 0;i < len;i++) {
453
2/2
✓ Branch 0 taken 171 times.
✓ Branch 1 taken 161 times.
332 if (to_upper) {
454 171 ((char*)(arr->data))[i] = (char)toupper(orig[i]);
455 } else {
456 161 ((char*)(arr->data))[i] = (char)tolower(orig[i]);
457 }
458 }
459 67 ((char*)(arr->data))[len] = '\0';
460 67 return lbm_res;
461 } else {
462 return ENC_SYM_MERROR;
463 }
464 }
465
466 62 static lbm_value ext_str_to_lower(lbm_value *args, lbm_uint argn) {
467 62 return change_case(args, argn, false);
468 }
469
470 63 static lbm_value ext_str_to_upper(lbm_value *args, lbm_uint argn) {
471 63 return change_case(args,argn, true);
472 }
473
474 364 static lbm_value ext_str_cmp(lbm_value *args, lbm_uint argn) {
475
4/4
✓ Branch 0 taken 58 times.
✓ Branch 1 taken 306 times.
✓ Branch 2 taken 28 times.
✓ Branch 3 taken 30 times.
364 if (argn != 2 && argn != 3) {
476 28 lbm_set_error_reason((char*)lbm_error_str_num_args);
477 28 return ENC_SYM_EERROR;
478 }
479
480 336 char *str1 = lbm_dec_str(args[0]);
481
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 307 times.
336 if (!str1) {
482 29 return ENC_SYM_TERROR;
483 }
484
485 307 char *str2 = lbm_dec_str(args[1]);
486
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 278 times.
307 if (!str2) {
487 29 return ENC_SYM_TERROR;
488 }
489
490 278 int n = -1;
491
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 248 times.
278 if (argn == 3) {
492
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 1 times.
30 if (!lbm_is_number(args[2])) {
493 29 return ENC_SYM_TERROR;
494 }
495
496 1 n = lbm_dec_as_i32(args[2]);
497 }
498
499
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 248 times.
249 if (n > 0) {
500 1 return lbm_enc_i(strncmp(str1, str2, (unsigned int)n));
501 } else {
502 248 return lbm_enc_i(strcmp(str1, str2));
503 }
504 }
505
506 // TODO: This is very similar to ext-print. Maybe they can share code.
507 1092 static lbm_value to_str(char *delimiter, lbm_value *args, lbm_uint argn) {
508 1092 const int str_len = 300;
509 1092 char *str = lbm_malloc((lbm_uint)str_len);
510
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1092 times.
1092 if (!str) {
511 return ENC_SYM_MERROR;
512 }
513
514 1092 int str_ofs = 0;
515
516
2/2
✓ Branch 0 taken 1400 times.
✓ Branch 1 taken 1092 times.
2492 for (lbm_uint i = 0; i < argn; i ++) {
517 1400 lbm_value t = args[i];
518 1400 int max = str_len - str_ofs - 1;
519
520 char *arr_str;
521 1400 int chars = 0;
522
523
2/2
✓ Branch 0 taken 252 times.
✓ Branch 1 taken 1148 times.
1400 if (lbm_value_is_printable_string(t, &arr_str)) {
524
2/2
✓ Branch 0 taken 168 times.
✓ Branch 1 taken 84 times.
252 if (str_ofs == 0) {
525 168 chars = snprintf(str + str_ofs, (unsigned int)max, "%s", arr_str);
526 } else {
527 84 chars = snprintf(str + str_ofs, (unsigned int)max, "%s%s", delimiter, arr_str);
528 }
529 } else {
530 1148 lbm_print_value(print_val_buffer, 256, t);
531
2/2
✓ Branch 0 taken 924 times.
✓ Branch 1 taken 224 times.
1148 if (str_ofs == 0) {
532 924 chars = snprintf(str + str_ofs, (unsigned int)max, "%s", print_val_buffer);
533 } else {
534 224 chars = snprintf(str + str_ofs, (unsigned int)max, "%s%s", delimiter, print_val_buffer);
535 }
536 }
537
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1400 times.
1400 if (chars >= max) {
538 str_ofs += max;
539 } else {
540 1400 str_ofs += chars;
541 }
542 }
543
544 lbm_value res;
545
1/2
✓ Branch 0 taken 1092 times.
✗ Branch 1 not taken.
1092 if (lbm_create_array(&res, (lbm_uint)str_ofs + 1)) {
546 1092 lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(res);
547 1092 strncpy((char*)arr->data, str, (unsigned int)str_ofs + 1);
548 1092 lbm_free(str);
549 1092 return res;
550 } else {
551 lbm_free(str);
552 return ENC_SYM_MERROR;
553 }
554 }
555
556 1064 static lbm_value ext_to_str(lbm_value *args, lbm_uint argn) {
557 1064 return to_str(" ", args, argn);
558 }
559
560 30 static lbm_value ext_to_str_delim(lbm_value *args, lbm_uint argn) {
561
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 29 times.
30 if (argn < 1) {
562 1 lbm_set_error_reason((char*)lbm_error_str_num_args);
563 1 return ENC_SYM_EERROR;
564 }
565
566 29 char *delim = lbm_dec_str(args[0]);
567
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 28 times.
29 if (!delim) {
568 1 return ENC_SYM_TERROR;
569 }
570
571 28 return to_str(delim, args + 1, argn - 1);
572 }
573
574 95 static lbm_value ext_str_len(lbm_value *args, lbm_uint argn) {
575
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 67 times.
95 LBM_CHECK_ARGN(1);
576
577 67 size_t str_arr_size = 0;
578 67 char *str = NULL; //lbm_dec_str(args[0]);
579
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 38 times.
67 if (!dec_str_size(args[0], &str, &str_arr_size)) {
580 29 return ENC_SYM_TERROR;
581 }
582
583 38 return lbm_enc_i((int)strlen_max(str, str_arr_size));
584 }
585
586 100 static lbm_value ext_str_replicate(lbm_value *args, lbm_uint argn) {
587
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 44 times.
100 if (argn != 2) {
588 56 lbm_set_error_reason((char*)lbm_error_str_num_args);
589 56 return ENC_SYM_EERROR;
590 }
591
592 44 lbm_value res = ENC_SYM_TERROR;
593
594
3/4
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 42 times.
✗ Branch 3 not taken.
86 if (lbm_is_number(args[0]) &&
595 42 lbm_is_number(args[1])) {
596 42 uint32_t len = lbm_dec_as_u32(args[0]);
597 42 uint8_t c = lbm_dec_as_char(args[1]);
598
599 lbm_value lbm_res;
600
1/2
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
42 if (lbm_create_array(&lbm_res, len + 1)) {
601 42 lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(lbm_res);
602
2/2
✓ Branch 0 taken 732 times.
✓ Branch 1 taken 42 times.
774 for (unsigned int i = 0;i < len;i++) {
603 732 ((char*)(arr->data))[i] = (char)c;
604 }
605 42 ((char*)(arr->data))[len] = '\0';
606 42 res = lbm_res;
607 } else {
608 res = ENC_SYM_MERROR;
609 }
610 }
611 44 return res;
612 }
613
614 765 bool ci_strncmp(const char *str1, const char *str2,int n) {
615 765 bool res = true;
616
2/2
✓ Branch 0 taken 1165 times.
✓ Branch 1 taken 254 times.
1419 for (int i = 0; i < n; i ++) {
617
2/2
✓ Branch 0 taken 511 times.
✓ Branch 1 taken 654 times.
1165 if (tolower(str1[i]) != tolower(str2[i])) {
618 511 res = false;
619 511 break;
620 }
621 }
622 765 return res;
623 }
624
625 // signature: (str-find str:byte-array substr [start:int] [occurrence:int] [dir] [case_sensitivity]) -> int
626 // where
627 // seq = string|(..string)
628 // dir = 'left|'right
629 // case_sensitivity = 'case-sensitive | 'case-insensitive
630 989 static lbm_value ext_str_find(lbm_value *args, lbm_uint argn) {
631
4/4
✓ Branch 0 taken 960 times.
✓ Branch 1 taken 29 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 959 times.
989 if (argn < 2 || 6 < argn) {
632 30 lbm_set_error_reason((char *)lbm_error_str_num_args);
633 30 return ENC_SYM_EERROR;
634 }
635 959 lbm_array_header_t *str_header = lbm_dec_array_r(args[0]);
636
2/2
✓ Branch 0 taken 931 times.
✓ Branch 1 taken 28 times.
959 if (str_header) {
637 931 const char *str = (const char *)str_header->data;
638 931 lbm_int str_size = (lbm_int)str_header->size;
639
640 // Guaranteed to be list containing strings.
641 lbm_value substrings;
642 931 lbm_int min_substr_len = LBM_INT_MAX;
643
2/2
✓ Branch 0 taken 761 times.
✓ Branch 1 taken 170 times.
931 if (lbm_is_array_r(args[1])) {
644 761 substrings = lbm_cons(args[1], ENC_SYM_NIL);
645
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 761 times.
761 if (substrings == ENC_SYM_MERROR) {
646 return ENC_SYM_MERROR;
647 }
648 761 lbm_array_header_t *header = (lbm_array_header_t *)lbm_car(args[1]);
649
650 761 lbm_int len = (lbm_int)header->size - 1;
651
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 760 times.
761 if (len < 0) {
652 // substr is zero length array
653 1 return lbm_enc_i(-1);
654 }
655 760 min_substr_len = len;
656
1/2
✓ Branch 0 taken 170 times.
✗ Branch 1 not taken.
170 } else if (lbm_is_list(args[1])) {
657
2/2
✓ Branch 0 taken 227 times.
✓ Branch 1 taken 170 times.
397 for (lbm_value current = args[1]; lbm_is_cons(current); current = lbm_cdr(current)) {
658 227 lbm_value car_val = lbm_car(current);
659 227 lbm_array_header_t *header = lbm_dec_array_r(car_val);
660
1/2
✓ Branch 0 taken 227 times.
✗ Branch 1 not taken.
227 if (header) {
661 227 lbm_int len = (lbm_int)header->size - 1;
662
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 226 times.
227 if (len < 0) {
663 // substr is zero length array
664 1 continue;
665 }
666
2/2
✓ Branch 0 taken 141 times.
✓ Branch 1 taken 85 times.
226 if (len < min_substr_len) {
667 141 min_substr_len = len;
668 }
669 } else {
670 lbm_set_error_suspect(args[1]);
671 lbm_set_error_reason((char *)lbm_error_str_incorrect_arg);
672 return ENC_SYM_TERROR;
673 }
674 }
675 170 substrings = args[1];
676 } else {
677 lbm_set_error_suspect(args[1]);
678 lbm_set_error_reason((char *)lbm_error_str_incorrect_arg);
679 return ENC_SYM_TERROR;
680 }
681
682 930 bool to_right = true;
683 930 bool case_sensitive = true;
684
685 930 int nums[2] = {0, 0};
686 930 bool nums_set[2] = {false, false};
687 930 int num_ix = 0;
688
689
690
2/2
✓ Branch 0 taken 3020 times.
✓ Branch 1 taken 930 times.
3950 for (int i = 0; i < (int)argn; i ++ ) {
691
3/4
✓ Branch 0 taken 651 times.
✓ Branch 1 taken 2369 times.
✓ Branch 2 taken 651 times.
✗ Branch 3 not taken.
3020 if (lbm_is_number(args[i]) && num_ix < 2) {
692 651 nums_set[num_ix] = true;
693 651 nums[num_ix++] = (int)lbm_dec_as_int(args[i]);
694 }
695
2/2
✓ Branch 0 taken 537 times.
✓ Branch 1 taken 2483 times.
3020 if (lbm_is_symbol(args[i])) {
696 537 lbm_uint symbol = lbm_dec_sym(args[i]);
697
2/2
✓ Branch 0 taken 283 times.
✓ Branch 1 taken 254 times.
537 if (symbol == sym_left) {
698 283 to_right = false;
699
2/2
✓ Branch 0 taken 198 times.
✓ Branch 1 taken 56 times.
254 } else if (symbol == sym_case_insensitive) {
700 198 case_sensitive = false;
701 }
702 }
703 }
704
705 930 uint32_t occurrence = 0;
706
2/2
✓ Branch 0 taken 647 times.
✓ Branch 1 taken 283 times.
930 lbm_int start = to_right ? 0 : str_size - min_substr_len;
707
2/2
✓ Branch 0 taken 509 times.
✓ Branch 1 taken 421 times.
930 if (nums_set[0]) {
708 509 start = nums[0];
709 }
710
2/2
✓ Branch 0 taken 142 times.
✓ Branch 1 taken 788 times.
930 if (nums_set[1]) {
711 142 occurrence = (uint32_t)nums[1];
712 }
713
714
2/2
✓ Branch 0 taken 226 times.
✓ Branch 1 taken 704 times.
930 if (start < 0) {
715 // start: -1 starts the search at the character index before the final null
716 // byte index.
717 226 start = str_size - 1 + start;
718 }
719
720
4/4
✓ Branch 0 taken 283 times.
✓ Branch 1 taken 647 times.
✓ Branch 2 taken 29 times.
✓ Branch 3 taken 254 times.
930 if (!to_right && (start > str_size - min_substr_len)) {
721 29 start = str_size - min_substr_len;
722 }
723
4/4
✓ Branch 0 taken 647 times.
✓ Branch 1 taken 254 times.
✓ Branch 2 taken 29 times.
✓ Branch 3 taken 618 times.
901 else if (to_right && (start < 0)) {
724 29 start = 0;
725 }
726
727
2/2
✓ Branch 0 taken 647 times.
✓ Branch 1 taken 283 times.
930 lbm_int dir = to_right ? 1 : -1;
728
4/4
✓ Branch 0 taken 1947 times.
✓ Branch 1 taken 485 times.
✓ Branch 2 taken 2289 times.
✓ Branch 3 taken 143 times.
2432 for (lbm_int i = start; to_right ? (i <= str_size - min_substr_len) : (i >= 0); i += dir) {
729
2/2
✓ Branch 0 taken 2548 times.
✓ Branch 1 taken 1502 times.
4050 for (lbm_value current = substrings; lbm_is_cons(current); current = lbm_cdr(current)) {
730 2548 lbm_array_header_t *header = (lbm_array_header_t *)lbm_car(lbm_car(current));
731 2548 lbm_int substr_len = (lbm_int)header->size - 1;
732 2548 const char *substr = (const char *)header->data;
733
734 2548 if (
735
1/2
✓ Branch 0 taken 2548 times.
✗ Branch 1 not taken.
2548 i > str_size - substr_len // substr length runs over str end.
736
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2548 times.
2548 || substr_len < 0 // empty substr substr was zero bytes in size
737 ) {
738 continue;
739 }
740
741
4/4
✓ Branch 0 taken 1783 times.
✓ Branch 1 taken 765 times.
✓ Branch 2 taken 1108 times.
✓ Branch 3 taken 675 times.
2548 if ((case_sensitive && memcmp(&str[i], substr, (size_t)substr_len) == 0) ||
742
4/4
✓ Branch 0 taken 765 times.
✓ Branch 1 taken 1108 times.
✓ Branch 2 taken 254 times.
✓ Branch 3 taken 511 times.
1873 (!case_sensitive && ci_strncmp(&str[i], substr, (int)substr_len))) {
743
2/2
✓ Branch 0 taken 787 times.
✓ Branch 1 taken 142 times.
929 if (occurrence == 0) {
744 787 return lbm_enc_i(i);
745 }
746 142 occurrence -= 1;
747 }
748 }
749 }
750 143 return lbm_enc_i(-1);
751 } else {
752 28 lbm_set_error_suspect(args[0]);
753 28 lbm_set_error_reason((char *)lbm_error_str_incorrect_arg);
754 28 return ENC_SYM_TERROR;
755 }
756 }
757
758 22317 void lbm_string_extensions_init(void) {
759
760 22317 lbm_add_symbol_const("left", &sym_left);
761 22317 lbm_add_symbol_const("nocase", &sym_case_insensitive);
762
763 22317 lbm_add_extension("str-from-n", ext_str_from_n);
764 22317 lbm_add_extension("str-join", ext_str_join);
765 22317 lbm_add_extension("str-to-i", ext_str_to_i);
766 22317 lbm_add_extension("str-to-f", ext_str_to_f);
767 22317 lbm_add_extension("str-part", ext_str_part);
768 22317 lbm_add_extension("str-split", ext_str_split);
769 22317 lbm_add_extension("str-replace", ext_str_replace);
770 22317 lbm_add_extension("str-to-lower", ext_str_to_lower);
771 22317 lbm_add_extension("str-to-upper", ext_str_to_upper);
772 22317 lbm_add_extension("str-cmp", ext_str_cmp);
773 22317 lbm_add_extension("to-str", ext_to_str);
774 22317 lbm_add_extension("to-str-delim", ext_to_str_delim);
775 22317 lbm_add_extension("str-len", ext_str_len);
776 22317 lbm_add_extension("str-replicate", ext_str_replicate);
777 22317 lbm_add_extension("str-find", ext_str_find);
778 22317 }
779