Coverage Report

Created: 2026-02-23 20:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/Users/alexjokela/projects/lattice/src/string_ops.c
Line
Count
Source
1
#include "string_ops.h"
2
3
#include <ctype.h>
4
#include <stdlib.h>
5
#include <string.h>
6
7
/* Helper: treat NULL as "" */
8
366
static const char *safe_str(const char *s) {
9
366
    return s ? s : "";
10
366
}
11
12
5
bool lat_str_contains(const char *s, const char *substr) {
13
5
    s = safe_str(s);
14
5
    substr = safe_str(substr);
15
5
    return strstr(s, substr) != NULL;
16
5
}
17
18
11
char **lat_str_split(const char *s, const char *delim, size_t *out_count) {
19
11
    s = safe_str(s);
20
11
    delim = safe_str(delim);
21
22
11
    size_t slen = strlen(s);
23
11
    size_t dlen = strlen(delim);
24
25
    /* Empty delimiter: split into individual characters */
26
11
    if (dlen == 0) {
27
0
        size_t count = slen > 0 ? slen : 1;
28
0
        char **result = malloc(count * sizeof(char *));
29
0
        if (!result) {
30
0
            *out_count = 0;
31
0
            return NULL;
32
0
        }
33
0
        if (slen == 0) {
34
0
            result[0] = strdup("");
35
0
            *out_count = 1;
36
0
            return result;
37
0
        }
38
0
        for (size_t i = 0; i < slen; i++) {
39
0
            result[i] = malloc(2);
40
0
            if (result[i]) {
41
0
                result[i][0] = s[i];
42
0
                result[i][1] = '\0';
43
0
            }
44
0
        }
45
0
        *out_count = slen;
46
0
        return result;
47
0
    }
48
49
    /* Count occurrences to pre-allocate */
50
11
    size_t count = 1;
51
11
    const char *p = s;
52
16
    while ((p = strstr(p, delim)) != NULL) {
53
5
        count++;
54
5
        p += dlen;
55
5
    }
56
57
11
    char **result = malloc(count * sizeof(char *));
58
11
    if (!result) {
59
0
        *out_count = 0;
60
0
        return NULL;
61
0
    }
62
63
11
    size_t idx = 0;
64
11
    const char *start = s;
65
11
    p = s;
66
16
    while ((p = strstr(p, delim)) != NULL) {
67
5
        size_t seg_len = (size_t)(p - start);
68
5
        result[idx] = malloc(seg_len + 1);
69
5
        if (result[idx]) {
70
5
            memcpy(result[idx], start, seg_len);
71
5
            result[idx][seg_len] = '\0';
72
5
        }
73
5
        idx++;
74
5
        p += dlen;
75
5
        start = p;
76
5
    }
77
78
    /* Final segment */
79
11
    result[idx] = strdup(start);
80
11
    idx++;
81
82
11
    *out_count = idx;
83
11
    return result;
84
11
}
85
86
43
char *lat_str_trim(const char *s) {
87
43
    s = safe_str(s);
88
43
    size_t len = strlen(s);
89
90
    /* Find first non-whitespace */
91
43
    size_t start = 0;
92
46
    while (start < len && isspace((unsigned char)s[start])) {
93
3
        start++;
94
3
    }
95
96
    /* Find last non-whitespace */
97
43
    size_t end = len;
98
50
    while (end > start && isspace((unsigned char)s[end - 1])) {
99
7
        end--;
100
7
    }
101
102
43
    size_t trimmed_len = end - start;
103
43
    char *result = malloc(trimmed_len + 1);
104
43
    if (result) {
105
43
        memcpy(result, s + start, trimmed_len);
106
43
        result[trimmed_len] = '\0';
107
43
    }
108
43
    return result;
109
43
}
110
111
45
bool lat_str_starts_with(const char *s, const char *prefix) {
112
45
    s = safe_str(s);
113
45
    prefix = safe_str(prefix);
114
45
    size_t plen = strlen(prefix);
115
45
    if (plen > strlen(s)) {
116
0
        return false;
117
0
    }
118
45
    return memcmp(s, prefix, plen) == 0;
119
45
}
120
121
2
bool lat_str_ends_with(const char *s, const char *suffix) {
122
2
    s = safe_str(s);
123
2
    suffix = safe_str(suffix);
124
2
    size_t slen = strlen(s);
125
2
    size_t suflen = strlen(suffix);
126
2
    if (suflen > slen) {
127
0
        return false;
128
0
    }
129
2
    return memcmp(s + slen - suflen, suffix, suflen) == 0;
130
2
}
131
132
4
char *lat_str_replace(const char *s, const char *old_str, const char *new_str) {
133
4
    s = safe_str(s);
134
4
    old_str = safe_str(old_str);
135
4
    new_str = safe_str(new_str);
136
137
4
    size_t slen = strlen(s);
138
4
    size_t old_len = strlen(old_str);
139
4
    size_t new_len = strlen(new_str);
140
141
    /* Empty old_str: return copy of s */
142
4
    if (old_len == 0) {
143
0
        return strdup(s);
144
0
    }
145
146
    /* Count occurrences */
147
4
    size_t count = 0;
148
4
    const char *p = s;
149
14
    while ((p = strstr(p, old_str)) != NULL) {
150
10
        count++;
151
10
        p += old_len;
152
10
    }
153
154
4
    if (count == 0) {
155
0
        return strdup(s);
156
0
    }
157
158
    /* Allocate result */
159
4
    size_t result_len = slen + count * (new_len - old_len);
160
4
    char *result = malloc(result_len + 1);
161
4
    if (!result) {
162
0
        return NULL;
163
0
    }
164
165
4
    char *dst = result;
166
4
    const char *src = s;
167
14
    while ((p = strstr(src, old_str)) != NULL) {
168
10
        size_t seg_len = (size_t)(p - src);
169
10
        memcpy(dst, src, seg_len);
170
10
        dst += seg_len;
171
10
        memcpy(dst, new_str, new_len);
172
10
        dst += new_len;
173
10
        src = p + old_len;
174
10
    }
175
176
    /* Copy remaining */
177
4
    strcpy(dst, src);
178
4
    return result;
179
4
}
180
181
3
char *lat_str_to_upper(const char *s) {
182
3
    s = safe_str(s);
183
3
    size_t len = strlen(s);
184
3
    char *result = malloc(len + 1);
185
3
    if (result) {
186
24
        for (size_t i = 0; i < len; i++) {
187
21
            result[i] = (char)toupper((unsigned char)s[i]);
188
21
        }
189
3
        result[len] = '\0';
190
3
    }
191
3
    return result;
192
3
}
193
194
2
char *lat_str_to_lower(const char *s) {
195
2
    s = safe_str(s);
196
2
    size_t len = strlen(s);
197
2
    char *result = malloc(len + 1);
198
2
    if (result) {
199
18
        for (size_t i = 0; i < len; i++) {
200
16
            result[i] = (char)tolower((unsigned char)s[i]);
201
16
        }
202
2
        result[len] = '\0';
203
2
    }
204
2
    return result;
205
2
}
206
207
64
char *lat_str_substring(const char *s, int64_t start, int64_t end) {
208
64
    s = safe_str(s);
209
64
    int64_t len = (int64_t)strlen(s);
210
211
    /* Clamp to [0, len] */
212
64
    if (start < 0) start = 0;
213
64
    if (end < 0) end = 0;
214
64
    if (start > len) start = len;
215
64
    if (end > len) end = len;
216
217
    /* start >= end: return empty string */
218
64
    if (start >= end) {
219
0
        return strdup("");
220
0
    }
221
222
64
    size_t sub_len = (size_t)(end - start);
223
64
    char *result = malloc(sub_len + 1);
224
64
    if (result) {
225
64
        memcpy(result, s + start, sub_len);
226
64
        result[sub_len] = '\0';
227
64
    }
228
64
    return result;
229
64
}
230
231
54
int64_t lat_str_index_of(const char *s, const char *substr) {
232
54
    s = safe_str(s);
233
54
    substr = safe_str(substr);
234
54
    const char *p = strstr(s, substr);
235
54
    if (!p) {
236
26
        return -1;
237
26
    }
238
28
    return (int64_t)(p - s);
239
54
}
240
241
0
int64_t lat_str_char_code_at(const char *s, size_t idx) {
242
0
    s = safe_str(s);
243
0
    if (idx >= strlen(s)) {
244
0
        return -1;
245
0
    }
246
0
    return (int64_t)(unsigned char)s[idx];
247
0
}
248
249
0
char *lat_str_from_char_code(int64_t code) {
250
0
    if (code < 0 || code > 127) {
251
0
        return strdup("");
252
0
    }
253
0
    char *result = malloc(2);
254
0
    if (result) {
255
0
        result[0] = (char)code;
256
0
        result[1] = '\0';
257
0
    }
258
0
    return result;
259
0
}
260
261
4
char *lat_str_repeat(const char *s, size_t count) {
262
4
    s = safe_str(s);
263
4
    size_t len = strlen(s);
264
265
4
    if (count == 0 || len == 0) {
266
2
        return strdup("");
267
2
    }
268
269
2
    size_t total = len * count;
270
2
    char *result = malloc(total + 1);
271
2
    if (result) {
272
2
        char *dst = result;
273
8
        for (size_t i = 0; i < count; i++) {
274
6
            memcpy(dst, s, len);
275
6
            dst += len;
276
6
        }
277
2
        *dst = '\0';
278
2
    }
279
2
    return result;
280
4
}
281
282
4
char *lat_str_reverse(const char *s) {
283
4
    s = safe_str(s);
284
4
    size_t len = strlen(s);
285
4
    char *result = malloc(len + 1);
286
4
    if (result) {
287
14
        for (size_t i = 0; i < len; i++) {
288
10
            result[i] = s[len - 1 - i];
289
10
        }
290
4
        result[len] = '\0';
291
4
    }
292
4
    return result;
293
4
}