GCC Code Coverage Report


Directory: ../src/
File: /home/joels/Current/lispbm/src/extensions/string_extensions.c
Date: 2025-08-08 18:10:24
Exec Total Coverage
Lines: 401 436 92.0%
Functions: 22 22 100.0%
Branches: 230 268 85.8%

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 768888 static size_t strlen_max(const char *s, size_t maxlen) {
51 size_t i;
52
2/2
✓ Branch 0 taken 2441239 times.
✓ Branch 1 taken 56 times.
2441295 for (i = 0; i < maxlen; i ++) {
53
2/2
✓ Branch 0 taken 768832 times.
✓ Branch 1 taken 1672407 times.
2441239 if (s[i] == 0) break;
54 }
55 768888 return i;
56 }
57
58 385737 static bool dec_str_size(lbm_value v, char **data, size_t *size) {
59 385737 bool result = false;
60 385737 lbm_array_header_t *array = lbm_dec_array_r(v);
61
2/2
✓ Branch 0 taken 385624 times.
✓ Branch 1 taken 113 times.
385737 if (array) {
62 385624 *data = (char*)array->data;
63 385624 *size = array->size;
64 385624 result = true;
65 }
66 385737 return result;
67 }
68
69 80355 static lbm_value ext_str_from_n(lbm_value *args, lbm_uint argn) {
70
4/4
✓ Branch 0 taken 393 times.
✓ Branch 1 taken 79962 times.
✓ Branch 2 taken 168 times.
✓ Branch 3 taken 225 times.
80355 if (argn != 1 && argn != 2) {
71 168 lbm_set_error_reason((char*)lbm_error_str_num_args);
72 168 return ENC_SYM_EERROR;
73 }
74
2/2
✓ Branch 0 taken 112 times.
✓ Branch 1 taken 80075 times.
80187 if (!lbm_is_number(args[0])) {
75 112 return ENC_SYM_TERROR;
76 }
77
78
4/4
✓ Branch 0 taken 225 times.
✓ Branch 1 taken 79850 times.
✓ Branch 2 taken 57 times.
✓ Branch 3 taken 168 times.
80075 if (argn == 2 && !lbm_is_array_r(args[1])) {
79 57 return ENC_SYM_TERROR;
80 }
81
82 80018 char *format = 0;
83
2/2
✓ Branch 0 taken 168 times.
✓ Branch 1 taken 79850 times.
80018 if (argn == 2) {
84 168 format = lbm_dec_str(args[1]);
85 }
86
87 char buffer[100];
88 80018 size_t len = 0;
89
90
2/2
✓ Branch 0 taken 448 times.
✓ Branch 1 taken 79570 times.
80018 switch (lbm_type_of(args[0])) {
91 448 case LBM_TYPE_DOUBLE: /* fall through */
92 case LBM_TYPE_FLOAT:
93
2/2
✓ Branch 0 taken 336 times.
✓ Branch 1 taken 112 times.
448 if (!format) {
94 336 format = "%g";
95 }
96 448 len = (size_t)snprintf(buffer, sizeof(buffer), format, lbm_dec_as_double(args[0]));
97 448 break;
98
99 79570 default:
100
2/2
✓ Branch 0 taken 79514 times.
✓ Branch 1 taken 56 times.
79570 if (!format) {
101 79514 format = "%d";
102 }
103 79570 len = (size_t)snprintf(buffer, sizeof(buffer), format, lbm_dec_as_i32(args[0]));
104 79570 break;
105 }
106
107 80018 len = MIN(len, sizeof(buffer));
108
109 lbm_value res;
110
2/2
✓ Branch 0 taken 79974 times.
✓ Branch 1 taken 44 times.
80018 if (lbm_create_array(&res, len + 1)) {
111 79974 lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(res);
112 79974 memcpy(arr->data, buffer, len);
113 79974 ((char*)(arr->data))[len] = '\0';
114 79974 return res;
115 } else {
116 44 return ENC_SYM_MERROR;
117 }
118 }
119
120 // signature: (str-join strings [delim]) -> str
121 192165 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 168700 times.
✓ Branch 1 taken 23465 times.
✓ Branch 2 taken 56 times.
✓ Branch 3 taken 168644 times.
192165 if (argn != 1 && argn != 2) {
126 56 lbm_set_error_reason((char *)lbm_error_str_num_args);
127 56 return ENC_SYM_EERROR;
128 }
129
130 192109 size_t str_len = 0;
131 192109 size_t str_count = 0;
132
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 192053 times.
192109 if (!lbm_is_list(args[0])) {
133 56 lbm_set_error_reason((char *)lbm_error_str_incorrect_arg);
134 56 lbm_set_error_suspect(args[0]);
135 56 return ENC_SYM_TERROR;
136 }
137
2/2
✓ Branch 0 taken 384340 times.
✓ Branch 1 taken 191997 times.
576337 for (lbm_value current = args[0]; lbm_is_cons(current); current = lbm_cdr(current)) {
138 384340 lbm_value car_val = lbm_car(current);
139 384340 char *str = NULL;
140 384340 size_t arr_size = 0;
141
2/2
✓ Branch 0 taken 384284 times.
✓ Branch 1 taken 56 times.
384340 if (dec_str_size(car_val, &str, &arr_size)) {
142 384284 str_len += strlen_max(str, arr_size);
143 384284 str_count += 1;
144 } else {
145 56 lbm_set_error_reason((char *)lbm_error_str_incorrect_arg);
146 56 lbm_set_error_suspect(args[0]);
147 56 return ENC_SYM_TERROR;
148 }
149 }
150
151 191997 const char *delim = "";
152
2/2
✓ Branch 0 taken 168532 times.
✓ Branch 1 taken 23465 times.
191997 if (argn >= 2) {
153 168532 delim = lbm_dec_str(args[1]);
154
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 168476 times.
168532 if (!delim) {
155 56 lbm_set_error_reason((char *)lbm_error_str_incorrect_arg);
156 56 lbm_set_error_suspect(args[1]);
157 56 return ENC_SYM_TERROR;
158 }
159 }
160
161 191941 size_t delim_len = strlen(delim);
162
2/2
✓ Branch 0 taken 135765 times.
✓ Branch 1 taken 56176 times.
191941 if (str_count > 0) {
163 135765 str_len += (str_count - 1) * delim_len;
164 }
165
166 lbm_value result;
167
2/2
✓ Branch 0 taken 192 times.
✓ Branch 1 taken 191749 times.
191941 if (!lbm_create_array(&result, str_len + 1)) {
168 192 return ENC_SYM_MERROR;
169 }
170 191749 char *result_str = lbm_dec_str(result);
171
172 191749 size_t i = 0;
173 191749 size_t offset = 0;
174
2/2
✓ Branch 0 taken 383788 times.
✓ Branch 1 taken 191749 times.
575537 for (lbm_value current = args[0]; lbm_is_cons(current); current = lbm_cdr(current)) {
175 383788 lbm_value car_val = lbm_car(current);
176 // All arrays have been prechecked.
177 383788 lbm_array_header_t *array = (lbm_array_header_t*) lbm_car(car_val);
178 383788 char *str = (char*)array->data;
179 383788 size_t len = strlen_max(str, array->size);
180
181 383788 memcpy(result_str + offset, str, len);
182 383788 offset += len;
183
184
2/2
✓ Branch 0 taken 248151 times.
✓ Branch 1 taken 135637 times.
383788 if (i != str_count - 1) {
185 248151 memcpy(result_str + offset, delim, delim_len);
186 248151 offset += delim_len;
187 }
188 383788 i++;
189 }
190
191 191749 result_str[str_len] = '\0';
192
193 191749 return result;
194 }
195
196 897 static lbm_value ext_str_to_i(lbm_value *args, lbm_uint argn) {
197
4/4
✓ Branch 0 taken 112 times.
✓ Branch 1 taken 785 times.
✓ Branch 2 taken 56 times.
✓ Branch 3 taken 56 times.
897 if (argn != 1 && argn != 2) {
198 56 lbm_set_error_reason((char*)lbm_error_str_num_args);
199 56 return ENC_SYM_EERROR;
200 }
201
202 841 char *str = lbm_dec_str(args[0]);
203
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 785 times.
841 if (!str) {
204 56 return ENC_SYM_TERROR;
205 }
206
207 785 int base = 0;
208
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 729 times.
785 if (argn == 2) {
209
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 56 times.
56 if (!lbm_is_number(args[1])) {
210 return ENC_SYM_TERROR;
211 }
212
213 56 base = (int)lbm_dec_as_u32(args[1]);
214 }
215
216 785 return lbm_enc_i32((int32_t)strtol(str, NULL, base));
217 }
218
219 224 static lbm_value ext_str_to_f(lbm_value *args, lbm_uint argn) {
220
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 168 times.
224 if (argn != 1) {
221 56 lbm_set_error_reason((char*)lbm_error_str_num_args);
222 56 return ENC_SYM_EERROR;
223 }
224
225 168 char *str = lbm_dec_str(args[0]);
226
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 112 times.
168 if (!str) {
227 56 return ENC_SYM_TERROR;
228 }
229
230 112 return lbm_enc_float(strtof(str, NULL));
231 }
232
233 226 static lbm_value ext_str_part(lbm_value *args, lbm_uint argn) {
234
5/6
✓ Branch 0 taken 170 times.
✓ Branch 1 taken 56 times.
✓ Branch 2 taken 114 times.
✓ Branch 3 taken 56 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 170 times.
226 if ((argn != 2 && argn != 3) || !lbm_is_number(args[1])) {
235 56 lbm_set_error_reason((char*)lbm_error_str_num_args);
236 56 return ENC_SYM_TERROR;
237 }
238
239 170 size_t str_arr_len = 0;
240 170 char *str = NULL;//lbm_dec_str(args[0]);
241
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 170 times.
170 if (!dec_str_size(args[0], &str, &str_arr_len)) {
242 return ENC_SYM_TERROR;
243 }
244
245 170 uint32_t len = (uint32_t)strlen_max(str, str_arr_len);
246
247 170 uint32_t start = lbm_dec_as_u32(args[1]);
248
249
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 170 times.
170 if (start >= len) {
250 return ENC_SYM_EERROR;
251 }
252
253 170 uint32_t n = len - start;
254
2/2
✓ Branch 0 taken 114 times.
✓ Branch 1 taken 56 times.
170 if (argn == 3) {
255
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 114 times.
114 if (!lbm_is_number(args[2])) {
256 return ENC_SYM_TERROR;
257 }
258
259
2/2
✓ Branch 0 taken 113 times.
✓ Branch 1 taken 1 times.
114 n = MIN(lbm_dec_as_u32(args[2]), n);
260 }
261
262 lbm_value res;
263
1/2
✓ Branch 0 taken 170 times.
✗ Branch 1 not taken.
170 if (lbm_create_array(&res, n + 1)) {
264 170 lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(res);
265 170 memcpy(arr->data, str + start, n);
266 170 ((char*)(arr->data))[n] = '\0';
267 170 return res;
268 } else {
269 return ENC_SYM_MERROR;
270 }
271 }
272
273 2557 static bool char_in(char c, char *delim, unsigned int max_ix) {
274 2557 char *d = delim;
275 2557 unsigned int i = 0;
276
3/4
✓ Branch 0 taken 4914 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3173 times.
✓ Branch 3 taken 1741 times.
4914 while (i < max_ix && *d != '\0' ) {
277
2/2
✓ Branch 0 taken 816 times.
✓ Branch 1 taken 2357 times.
3173 if (c == *d) return true;
278 2357 d++; i++;
279 }
280 1741 return false;
281 }
282
283 459 static lbm_value ext_str_split(lbm_value *args, lbm_uint argn) {
284
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 403 times.
459 if (argn != 2) {
285 56 lbm_set_error_reason((char*)lbm_error_str_num_args);
286 56 return ENC_SYM_TERROR;
287 }
288
289 403 size_t str_arr_size = 0;
290 403 char *str = NULL; //lbm_dec_str(args[0]);
291
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 403 times.
403 if (!dec_str_size(args[0], &str, &str_arr_size)) {
292 return ENC_SYM_TERROR;
293 }
294
295 403 char *delim = NULL;
296 403 size_t delim_arr_size = 0;
297
298
2/2
✓ Branch 0 taken 113 times.
✓ Branch 1 taken 290 times.
403 if (lbm_is_number(args[1])) {
299
2/2
✓ Branch 0 taken 57 times.
✓ Branch 1 taken 56 times.
113 int step = MAX(lbm_dec_as_i32(args[1]), 1);
300 113 lbm_value res = ENC_SYM_NIL;
301 113 int len = (int)strlen_max(str, str_arr_size);
302
2/2
✓ Branch 0 taken 1121 times.
✓ Branch 1 taken 113 times.
1234 for (int i = len / step;i >= 0;i--) {
303 1121 int ind_now = i * step;
304
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 1065 times.
1121 if (ind_now >= len) {
305 56 continue;
306 }
307
308 1065 int step_now = step;
309
2/2
✓ Branch 0 taken 93 times.
✓ Branch 1 taken 1065 times.
1158 while ((ind_now + step_now) > len) {
310 93 step_now--;
311 }
312
313 lbm_value tok;
314
1/2
✓ Branch 0 taken 1065 times.
✗ Branch 1 not taken.
1065 if (lbm_create_array(&tok, (lbm_uint)step_now + 1)) {
315 1065 lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(tok);
316 1065 memcpy(arr->data, str + ind_now, (unsigned int)step_now);
317 1065 ((char*)(arr->data))[step_now] = '\0';
318 1065 res = lbm_cons(tok, res);
319 } else {
320 return ENC_SYM_MERROR;
321 }
322 }
323 113 return res;
324
1/2
✓ Branch 0 taken 290 times.
✗ Branch 1 not taken.
290 } else if (dec_str_size(args[1], &delim, &delim_arr_size)) {
325 290 lbm_value res = ENC_SYM_NIL;
326
327 290 unsigned int i_start = 0;
328 290 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 1106 times.
✗ Branch 1 not taken.
1106 while (i_end < str_arr_size) {
333
334
4/4
✓ Branch 0 taken 2557 times.
✓ Branch 1 taken 290 times.
✓ Branch 2 taken 1741 times.
✓ Branch 3 taken 816 times.
2847 while (str[i_end] != '\0' && !char_in(str[i_end], delim, delim_arr_size)) {
335 1741 i_end ++;
336 }
337
338 1106 unsigned int len = i_end - i_start;
339 1106 char *s = &str[i_start];
340 lbm_value tok;
341
1/2
✓ Branch 0 taken 1106 times.
✗ Branch 1 not taken.
1106 if (lbm_create_array(&tok, len + 1)) {
342 1106 lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(tok);
343 1106 memcpy(arr->data, s, len);
344 1106 ((char*)(arr->data))[len] = '\0';
345 1106 res = lbm_cons(tok, res);
346
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1106 times.
1106 if (res == ENC_SYM_MERROR) return res;
347 } else {
348 return ENC_SYM_MERROR;
349 }
350
351
2/2
✓ Branch 0 taken 290 times.
✓ Branch 1 taken 816 times.
1106 if (str[i_end] == '\0') break;
352 816 i_start = i_end + 1;
353 816 i_end = i_end + 1;
354
355 }
356 290 return lbm_list_destructive_reverse(res);
357 }
358 return ENC_SYM_TERROR;
359 }
360
361 // Todo: Clean this up for 64bit
362 172 static lbm_value ext_str_replace(lbm_value *args, lbm_uint argn) {
363
4/4
✓ Branch 0 taken 116 times.
✓ Branch 1 taken 56 times.
✓ Branch 2 taken 56 times.
✓ Branch 3 taken 60 times.
172 if (argn != 2 && argn != 3) {
364 56 lbm_set_error_reason((char*)lbm_error_str_num_args);
365 56 return ENC_SYM_EERROR;
366 }
367
368 116 size_t orig_arr_size = 0;
369 116 char *orig = NULL; // lbm_dec_str(args[0]);
370
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 115 times.
116 if (!dec_str_size(args[0], &orig, &orig_arr_size)) {
371 1 return ENC_SYM_TERROR;
372 }
373
374 115 size_t rep_arr_size = 0;
375 115 char *rep = NULL; //lbm_dec_str(args[1]);
376
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 115 times.
115 if (!dec_str_size(args[1], &rep, &rep_arr_size)) {
377 return ENC_SYM_TERROR;
378 }
379
380 115 size_t with_arr_size = 0;
381 115 char *with = "";
382
2/2
✓ Branch 0 taken 59 times.
✓ Branch 1 taken 56 times.
115 if (argn == 3) {
383
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
59 if (!dec_str_size(args[2], &with, &with_arr_size)) {
384 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 115 len_rep = strlen_max(rep, rep_arr_size);
398
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 115 times.
115 if (len_rep == 0) {
399 return args[0]; // empty rep causes infinite loop during count
400 }
401
402 115 len_with = strlen_max(with,with_arr_size);
403
404 // count the number of replacements needed
405 115 ins = orig;
406
2/2
✓ Branch 0 taken 163 times.
✓ Branch 1 taken 115 times.
278 for (count = 0; (tmp = strstr(ins, rep)); ++count) {
407 163 ins = tmp + len_rep;
408 }
409
410 115 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 115 times.
✗ Branch 1 not taken.
115 if (lbm_create_array(&lbm_res, len_res)) {
413 115 lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(lbm_res);
414 115 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 163 times.
✓ Branch 1 taken 115 times.
278 while (count--) {
425 163 ins = strstr(orig, rep);
426 163 size_t len_front = (size_t)ins - (size_t)orig;
427 163 tmp = strncpy(tmp, orig, len_front) + len_front;
428 163 tmp = strncpy(tmp, with, len_with) + len_with;
429 163 orig += len_front + len_rep; // move to next "end of rep"
430 }
431 115 strcpy(tmp, orig);
432
433 115 return lbm_res;
434 }
435
436 234 static lbm_value change_case(lbm_value *args, lbm_uint argn, bool to_upper) {
437
2/2
✓ Branch 0 taken 112 times.
✓ Branch 1 taken 122 times.
234 if (argn != 1) {
438 112 lbm_set_error_reason((char*)lbm_error_str_num_args);
439 112 return ENC_SYM_EERROR;
440 }
441
442 122 size_t orig_arr_size = 0;
443 122 char *orig = NULL; //lbm_dec_str(args[0]);
444
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 122 times.
122 if (!dec_str_size(args[0], &orig, &orig_arr_size)) {
445 return ENC_SYM_TERROR;
446 }
447
448 122 size_t len = strlen_max(orig,orig_arr_size);
449 lbm_value lbm_res;
450
1/2
✓ Branch 0 taken 122 times.
✗ Branch 1 not taken.
122 if (lbm_create_array(&lbm_res, len + 1)) {
451 122 lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(lbm_res);
452
2/2
✓ Branch 0 taken 601 times.
✓ Branch 1 taken 122 times.
723 for (unsigned int i = 0;i < len;i++) {
453
2/2
✓ Branch 0 taken 300 times.
✓ Branch 1 taken 301 times.
601 if (to_upper) {
454 300 ((char*)(arr->data))[i] = (char)toupper(orig[i]);
455 } else {
456 301 ((char*)(arr->data))[i] = (char)tolower(orig[i]);
457 }
458 }
459 122 ((char*)(arr->data))[len] = '\0';
460 122 return lbm_res;
461 } else {
462 return ENC_SYM_MERROR;
463 }
464 }
465
466 117 static lbm_value ext_str_to_lower(lbm_value *args, lbm_uint argn) {
467 117 return change_case(args, argn, false);
468 }
469
470 117 static lbm_value ext_str_to_upper(lbm_value *args, lbm_uint argn) {
471 117 return change_case(args,argn, true);
472 }
473
474 696 static lbm_value ext_str_cmp(lbm_value *args, lbm_uint argn) {
475
4/4
✓ Branch 0 taken 112 times.
✓ Branch 1 taken 584 times.
✓ Branch 2 taken 56 times.
✓ Branch 3 taken 56 times.
696 if (argn != 2 && argn != 3) {
476 56 lbm_set_error_reason((char*)lbm_error_str_num_args);
477 56 return ENC_SYM_EERROR;
478 }
479
480 640 char *str1 = lbm_dec_str(args[0]);
481
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 584 times.
640 if (!str1) {
482 56 return ENC_SYM_TERROR;
483 }
484
485 584 char *str2 = lbm_dec_str(args[1]);
486
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 528 times.
584 if (!str2) {
487 56 return ENC_SYM_TERROR;
488 }
489
490 528 int n = -1;
491
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 472 times.
528 if (argn == 3) {
492
1/2
✓ Branch 0 taken 56 times.
✗ Branch 1 not taken.
56 if (!lbm_is_number(args[2])) {
493 56 return ENC_SYM_TERROR;
494 }
495
496 n = lbm_dec_as_i32(args[2]);
497 }
498
499
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 472 times.
472 if (n > 0) {
500 return lbm_enc_i(strncmp(str1, str2, (unsigned int)n));
501 } else {
502 472 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 2184 static lbm_value to_str(char *delimiter, lbm_value *args, lbm_uint argn) {
508 2184 const int str_len = 300;
509 2184 char *str = lbm_malloc((lbm_uint)str_len);
510
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2184 times.
2184 if (!str) {
511 return ENC_SYM_MERROR;
512 }
513
514 2184 int str_ofs = 0;
515
516
2/2
✓ Branch 0 taken 2800 times.
✓ Branch 1 taken 2184 times.
4984 for (lbm_uint i = 0; i < argn; i ++) {
517 2800 lbm_value t = args[i];
518 2800 int max = str_len - str_ofs - 1;
519
520 char *arr_str;
521 2800 int chars = 0;
522
523
2/2
✓ Branch 0 taken 504 times.
✓ Branch 1 taken 2296 times.
2800 if (lbm_value_is_printable_string(t, &arr_str)) {
524
2/2
✓ Branch 0 taken 336 times.
✓ Branch 1 taken 168 times.
504 if (str_ofs == 0) {
525 336 chars = snprintf(str + str_ofs, (unsigned int)max, "%s", arr_str);
526 } else {
527 168 chars = snprintf(str + str_ofs, (unsigned int)max, "%s%s", delimiter, arr_str);
528 }
529 } else {
530 2296 lbm_print_value(print_val_buffer, 256, t);
531
2/2
✓ Branch 0 taken 1848 times.
✓ Branch 1 taken 448 times.
2296 if (str_ofs == 0) {
532 1848 chars = snprintf(str + str_ofs, (unsigned int)max, "%s", print_val_buffer);
533 } else {
534 448 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 2800 times.
2800 if (chars >= max) {
538 str_ofs += max;
539 } else {
540 2800 str_ofs += chars;
541 }
542 }
543
544 lbm_value res;
545
1/2
✓ Branch 0 taken 2184 times.
✗ Branch 1 not taken.
2184 if (lbm_create_array(&res, (lbm_uint)str_ofs + 1)) {
546 2184 lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(res);
547 2184 strncpy((char*)arr->data, str, (unsigned int)str_ofs + 1);
548 2184 lbm_free(str);
549 2184 return res;
550 } else {
551 lbm_free(str);
552 return ENC_SYM_MERROR;
553 }
554 }
555
556 2128 static lbm_value ext_to_str(lbm_value *args, lbm_uint argn) {
557 2128 return to_str(" ", args, argn);
558 }
559
560 56 static lbm_value ext_to_str_delim(lbm_value *args, lbm_uint argn) {
561
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 56 times.
56 if (argn < 1) {
562 lbm_set_error_reason((char*)lbm_error_str_num_args);
563 return ENC_SYM_EERROR;
564 }
565
566 56 char *delim = lbm_dec_str(args[0]);
567
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 56 times.
56 if (!delim) {
568 return ENC_SYM_TERROR;
569 }
570
571 56 return to_str(delim, args + 1, argn - 1);
572 }
573
574 178 static lbm_value ext_str_len(lbm_value *args, lbm_uint argn) {
575
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 122 times.
178 LBM_CHECK_ARGN(1);
576
577 122 size_t str_arr_size = 0;
578 122 char *str = NULL; //lbm_dec_str(args[0]);
579
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 66 times.
122 if (!dec_str_size(args[0], &str, &str_arr_size)) {
580 56 return ENC_SYM_TERROR;
581 }
582
583 66 return lbm_enc_i((int)strlen_max(str, str_arr_size));
584 }
585
586 180 static lbm_value ext_str_replicate(lbm_value *args, lbm_uint argn) {
587
2/2
✓ Branch 0 taken 112 times.
✓ Branch 1 taken 68 times.
180 if (argn != 2) {
588 112 lbm_set_error_reason((char*)lbm_error_str_num_args);
589 112 return ENC_SYM_EERROR;
590 }
591
592 68 lbm_value res = ENC_SYM_TERROR;
593
594
2/4
✓ Branch 0 taken 68 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 68 times.
✗ Branch 3 not taken.
136 if (lbm_is_number(args[0]) &&
595 68 lbm_is_number(args[1])) {
596 68 uint32_t len = lbm_dec_as_u32(args[0]);
597 68 uint8_t c = lbm_dec_as_char(args[1]);
598
599 lbm_value lbm_res;
600
1/2
✓ Branch 0 taken 68 times.
✗ Branch 1 not taken.
68 if (lbm_create_array(&lbm_res, len + 1)) {
601 68 lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(lbm_res);
602
2/2
✓ Branch 0 taken 839 times.
✓ Branch 1 taken 68 times.
907 for (unsigned int i = 0;i < len;i++) {
603 839 ((char*)(arr->data))[i] = (char)c;
604 }
605 68 ((char*)(arr->data))[len] = '\0';
606 68 res = lbm_res;
607 } else {
608 res = ENC_SYM_MERROR;
609 }
610 }
611 68 return res;
612 }
613
614 1512 bool ci_strncmp(const char *str1, const char *str2,int n) {
615 1512 bool res = true;
616
2/2
✓ Branch 0 taken 2296 times.
✓ Branch 1 taken 504 times.
2800 for (int i = 0; i < n; i ++) {
617
2/2
✓ Branch 0 taken 1008 times.
✓ Branch 1 taken 1288 times.
2296 if (tolower(str1[i]) != tolower(str2[i])) {
618 1008 res = false;
619 1008 break;
620 }
621 }
622 1512 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 1927 static lbm_value ext_str_find(lbm_value *args, lbm_uint argn) {
631
3/4
✓ Branch 0 taken 1871 times.
✓ Branch 1 taken 56 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1871 times.
1927 if (argn < 2 || 6 < argn) {
632 56 lbm_set_error_reason((char *)lbm_error_str_num_args);
633 56 return ENC_SYM_EERROR;
634 }
635 1871 lbm_array_header_t *str_header = lbm_dec_array_r(args[0]);
636
2/2
✓ Branch 0 taken 1815 times.
✓ Branch 1 taken 56 times.
1871 if (str_header) {
637 1815 const char *str = (const char *)str_header->data;
638 1815 lbm_int str_size = (lbm_int)str_header->size;
639
640 // Guaranteed to be list containing strings.
641 lbm_value substrings;
642 1815 lbm_int min_substr_len = LBM_INT_MAX;
643
2/2
✓ Branch 0 taken 1479 times.
✓ Branch 1 taken 336 times.
1815 if (lbm_is_array_r(args[1])) {
644 1479 substrings = lbm_cons(args[1], ENC_SYM_NIL);
645
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1479 times.
1479 if (substrings == ENC_SYM_MERROR) {
646 return ENC_SYM_MERROR;
647 }
648 1479 lbm_array_header_t *header = (lbm_array_header_t *)lbm_car(args[1]);
649
650 1479 lbm_int len = (lbm_int)header->size - 1;
651
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1479 times.
1479 if (len < 0) {
652 // substr is zero length array
653 return lbm_enc_i(-1);
654 }
655 1479 min_substr_len = len;
656
1/2
✓ Branch 0 taken 336 times.
✗ Branch 1 not taken.
336 } else if (lbm_is_list(args[1])) {
657
2/2
✓ Branch 0 taken 448 times.
✓ Branch 1 taken 336 times.
784 for (lbm_value current = args[1]; lbm_is_cons(current); current = lbm_cdr(current)) {
658 448 lbm_value car_val = lbm_car(current);
659 448 lbm_array_header_t *header = lbm_dec_array_r(car_val);
660
1/2
✓ Branch 0 taken 448 times.
✗ Branch 1 not taken.
448 if (header) {
661 448 lbm_int len = (lbm_int)header->size - 1;
662
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 448 times.
448 if (len < 0) {
663 // substr is zero length array
664 continue;
665 }
666
2/2
✓ Branch 0 taken 280 times.
✓ Branch 1 taken 168 times.
448 if (len < min_substr_len) {
667 280 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 336 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 1815 bool to_right = true;
683 1815 bool case_sensitive = true;
684
685 1815 int nums[2] = {0, 0};
686 1815 bool nums_set[2] = {false, false};
687 1815 int num_ix = 0;
688
689
690
2/2
✓ Branch 0 taken 5926 times.
✓ Branch 1 taken 1815 times.
7741 for (int i = 0; i < (int)argn; i ++ ) {
691
3/4
✓ Branch 0 taken 1288 times.
✓ Branch 1 taken 4638 times.
✓ Branch 2 taken 1288 times.
✗ Branch 3 not taken.
5926 if (lbm_is_number(args[i]) && num_ix < 2) {
692 1288 nums_set[num_ix] = true;
693 1288 nums[num_ix++] = lbm_dec_as_int(args[i]);
694 }
695
2/2
✓ Branch 0 taken 1064 times.
✓ Branch 1 taken 4862 times.
5926 if (lbm_is_symbol(args[i])) {
696 1064 lbm_uint symbol = lbm_dec_sym(args[i]);
697
2/2
✓ Branch 0 taken 560 times.
✓ Branch 1 taken 504 times.
1064 if (symbol == sym_left) {
698 560 to_right = false;
699
2/2
✓ Branch 0 taken 392 times.
✓ Branch 1 taken 112 times.
504 } else if (symbol == sym_case_insensitive) {
700 392 case_sensitive = false;
701 }
702 }
703 }
704
705 1815 uint32_t occurrence = 0;
706
2/2
✓ Branch 0 taken 1255 times.
✓ Branch 1 taken 560 times.
1815 lbm_int start = to_right ? 0 : str_size - min_substr_len;
707
2/2
✓ Branch 0 taken 1008 times.
✓ Branch 1 taken 807 times.
1815 if (nums_set[0]) {
708 1008 start = nums[0];
709 }
710
2/2
✓ Branch 0 taken 280 times.
✓ Branch 1 taken 1535 times.
1815 if (nums_set[1]) {
711 280 occurrence = (uint32_t)nums[1];
712 }
713
714
2/2
✓ Branch 0 taken 448 times.
✓ Branch 1 taken 1367 times.
1815 if (start < 0) {
715 // start: -1 starts the search at the character index before the final null
716 // byte index.
717 448 start = str_size - 1 + start;
718 }
719
720
4/4
✓ Branch 0 taken 560 times.
✓ Branch 1 taken 1255 times.
✓ Branch 2 taken 56 times.
✓ Branch 3 taken 504 times.
1815 if (!to_right && (start > str_size - min_substr_len)) {
721 56 start = str_size - min_substr_len;
722 }
723
4/4
✓ Branch 0 taken 1255 times.
✓ Branch 1 taken 504 times.
✓ Branch 2 taken 56 times.
✓ Branch 3 taken 1199 times.
1759 else if (to_right && (start < 0)) {
724 56 start = 0;
725 }
726
727
2/2
✓ Branch 0 taken 1255 times.
✓ Branch 1 taken 560 times.
1815 lbm_int dir = to_right ? 1 : -1;
728
4/4
✓ Branch 0 taken 3396 times.
✓ Branch 1 taken 952 times.
✓ Branch 2 taken 4067 times.
✓ Branch 3 taken 281 times.
4348 for (lbm_int i = start; to_right ? (i <= str_size - min_substr_len) : (i >= 0); i += dir) {
729
2/2
✓ Branch 0 taken 4571 times.
✓ Branch 1 taken 2533 times.
7104 for (lbm_value current = substrings; lbm_is_cons(current); current = lbm_cdr(current)) {
730 4571 lbm_array_header_t *header = (lbm_array_header_t *)lbm_car(lbm_car(current));
731 4571 lbm_int substr_len = (lbm_int)header->size - 1;
732 4571 const char *substr = (const char *)header->data;
733
734 4571 if (
735
1/2
✓ Branch 0 taken 4571 times.
✗ Branch 1 not taken.
4571 i > str_size - substr_len // substr length runs over str end.
736
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4571 times.
4571 || substr_len < 0 // empty substr substr was zero bytes in size
737 ) {
738 continue;
739 }
740
741
4/4
✓ Branch 0 taken 3059 times.
✓ Branch 1 taken 1512 times.
✓ Branch 2 taken 1749 times.
✓ Branch 3 taken 1310 times.
4571 if ((case_sensitive && memcmp(&str[i], substr, (size_t)substr_len) == 0) ||
742
4/4
✓ Branch 0 taken 1512 times.
✓ Branch 1 taken 1749 times.
✓ Branch 2 taken 504 times.
✓ Branch 3 taken 1008 times.
3261 (!case_sensitive && ci_strncmp(&str[i], substr, (int)substr_len))) {
743
2/2
✓ Branch 0 taken 1534 times.
✓ Branch 1 taken 280 times.
1814 if (occurrence == 0) {
744 1534 return lbm_enc_i(i);
745 }
746 280 occurrence -= 1;
747 }
748 }
749 }
750 281 return lbm_enc_i(-1);
751 } else {
752 56 lbm_set_error_suspect(args[0]);
753 56 lbm_set_error_reason((char *)lbm_error_str_incorrect_arg);
754 56 return ENC_SYM_TERROR;
755 }
756 }
757
758 44260 void lbm_string_extensions_init(void) {
759
760 44260 lbm_add_symbol_const("left", &sym_left);
761 44260 lbm_add_symbol_const("nocase", &sym_case_insensitive);
762
763 44260 lbm_add_extension("str-from-n", ext_str_from_n);
764 44260 lbm_add_extension("str-join", ext_str_join);
765 44260 lbm_add_extension("str-to-i", ext_str_to_i);
766 44260 lbm_add_extension("str-to-f", ext_str_to_f);
767 44260 lbm_add_extension("str-part", ext_str_part);
768 44260 lbm_add_extension("str-split", ext_str_split);
769 44260 lbm_add_extension("str-replace", ext_str_replace);
770 44260 lbm_add_extension("str-to-lower", ext_str_to_lower);
771 44260 lbm_add_extension("str-to-upper", ext_str_to_upper);
772 44260 lbm_add_extension("str-cmp", ext_str_cmp);
773 44260 lbm_add_extension("to-str", ext_to_str);
774 44260 lbm_add_extension("to-str-delim", ext_to_str_delim);
775 44260 lbm_add_extension("str-len", ext_str_len);
776 44260 lbm_add_extension("str-replicate", ext_str_replicate);
777 44260 lbm_add_extension("str-find", ext_str_find);
778 44260 }
779