ucx update
[uwplayer.git] / ucx / string.c
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 #include "cx/string.h"
30 #include "cx/utils.h"
31
32 #include <string.h>
33 #include <stdarg.h>
34 #include <ctype.h>
35
36 #ifndef _WIN32
37
38 #include <strings.h> // for strncasecmp()
39
40 #endif // _WIN32
41
42 cxmutstr cx_mutstr(char *cstring) {
43     return (cxmutstr) {cstring, strlen(cstring)};
44 }
45
46 cxmutstr cx_mutstrn(
47         char *cstring,
48         size_t length
49 ) {
50     return (cxmutstr) {cstring, length};
51 }
52
53 cxstring cx_str(const char *cstring) {
54     return (cxstring) {cstring, strlen(cstring)};
55 }
56
57 cxstring cx_strn(
58         const char *cstring,
59         size_t length
60 ) {
61     return (cxstring) {cstring, length};
62 }
63
64 cxstring cx_strcast(cxmutstr str) {
65     return (cxstring) {str.ptr, str.length};
66 }
67
68 void cx_strfree(cxmutstr *str) {
69     free(str->ptr);
70     str->ptr = NULL;
71     str->length = 0;
72 }
73
74 void cx_strfree_a(
75         CxAllocator *alloc,
76         cxmutstr *str
77 ) {
78     cxFree(alloc, str->ptr);
79     str->ptr = NULL;
80     str->length = 0;
81 }
82
83 size_t cx_strlen(
84         size_t count,
85         ...
86 ) {
87     if (count == 0) return 0;
88
89     va_list ap;
90     va_start(ap, count);
91     size_t size = 0;
92     cx_for_n(i, count) {
93         cxstring str = va_arg(ap, cxstring);
94         size += str.length;
95     }
96     va_end(ap);
97
98     return size;
99 }
100
101 cxmutstr cx_strcat_a(
102         CxAllocator *alloc,
103         size_t count,
104         ...
105 ) {
106     cxstring *strings = calloc(count, sizeof(cxstring));
107     if (!strings) abort();
108
109     va_list ap;
110     va_start(ap, count);
111
112     // get all args and overall length
113     size_t slen = 0;
114     cx_for_n(i, count) {
115         cxstring s = va_arg (ap, cxstring);
116         strings[i] = s;
117         slen += s.length;
118     }
119
120     // create new string
121     cxmutstr result;
122     result.ptr = cxMalloc(alloc, slen + 1);
123     result.length = slen;
124     if (result.ptr == NULL) abort();
125
126     // concatenate strings
127     size_t pos = 0;
128     cx_for_n(i, count) {
129         cxstring s = strings[i];
130         memcpy(result.ptr + pos, s.ptr, s.length);
131         pos += s.length;
132     }
133
134     // terminate string
135     result.ptr[result.length] = '\0';
136
137     // free temporary array
138     free(strings);
139
140     return result;
141 }
142
143 cxstring cx_strsubs(
144         cxstring string,
145         size_t start
146 ) {
147     return cx_strsubsl(string, start, string.length - start);
148 }
149
150 cxmutstr cx_strsubs_m(
151         cxmutstr string,
152         size_t start
153 ) {
154     return cx_strsubsl_m(string, start, string.length - start);
155 }
156
157 cxstring cx_strsubsl(
158         cxstring string,
159         size_t start,
160         size_t length
161 ) {
162     if (start > string.length) {
163         return (cxstring) {NULL, 0};
164     }
165
166     size_t rem_len = string.length - start;
167     if (length > rem_len) {
168         length = rem_len;
169     }
170
171     return (cxstring) {string.ptr + start, length};
172 }
173
174 cxmutstr cx_strsubsl_m(
175         cxmutstr string,
176         size_t start,
177         size_t length
178 ) {
179     cxstring result = cx_strsubsl(cx_strcast(string), start, length);
180     return (cxmutstr) {(char *) result.ptr, result.length};
181 }
182
183 cxstring cx_strchr(
184         cxstring string,
185         int chr
186 ) {
187     chr = 0xFF & chr;
188     // TODO: improve by comparing multiple bytes at once
189     cx_for_n(i, string.length) {
190         if (string.ptr[i] == chr) {
191             return cx_strsubs(string, i);
192         }
193     }
194     return (cxstring) {NULL, 0};
195 }
196
197 cxmutstr cx_strchr_m(
198         cxmutstr string,
199         int chr
200 ) {
201     cxstring result = cx_strchr(cx_strcast(string), chr);
202     return (cxmutstr) {(char *) result.ptr, result.length};
203 }
204
205 cxstring cx_strrchr(
206         cxstring string,
207         int chr
208 ) {
209     chr = 0xFF & chr;
210     size_t i = string.length;
211     while (i > 0) {
212         i--;
213         // TODO: improve by comparing multiple bytes at once
214         if (string.ptr[i] == chr) {
215             return cx_strsubs(string, i);
216         }
217     }
218     return (cxstring) {NULL, 0};
219 }
220
221 cxmutstr cx_strrchr_m(
222         cxmutstr string,
223         int chr
224 ) {
225     cxstring result = cx_strrchr(cx_strcast(string), chr);
226     return (cxmutstr) {(char *) result.ptr, result.length};
227 }
228
229 #ifndef CX_STRSTR_SBO_SIZE
230 #define CX_STRSTR_SBO_SIZE 512
231 #endif
232
233 cxstring cx_strstr(
234         cxstring haystack,
235         cxstring needle
236 ) {
237     if (needle.length == 0) {
238         return haystack;
239     }
240
241     // optimize for single-char needles
242     if (needle.length == 1) {
243         return cx_strchr(haystack, *needle.ptr);
244     }
245
246     /*
247      * IMPORTANT:
248      * Our prefix table contains the prefix length PLUS ONE
249      * this is our decision, because we want to use the full range of size_t.
250      * The original algorithm needs a (-1) at one single place,
251      * and we want to avoid that.
252      */
253
254     // local prefix table
255     size_t s_prefix_table[CX_STRSTR_SBO_SIZE];
256
257     // check needle length and use appropriate prefix table
258     // if the pattern exceeds static prefix table, allocate on the heap
259     bool useheap = needle.length >= CX_STRSTR_SBO_SIZE;
260     register size_t *ptable = useheap ? calloc(needle.length + 1,
261                                                sizeof(size_t)) : s_prefix_table;
262
263     // keep counter in registers
264     register size_t i, j;
265
266     // fill prefix table
267     i = 0;
268     j = 0;
269     ptable[i] = j;
270     while (i < needle.length) {
271         while (j >= 1 && needle.ptr[j - 1] != needle.ptr[i]) {
272             j = ptable[j - 1];
273         }
274         i++;
275         j++;
276         ptable[i] = j;
277     }
278
279     // search
280     cxstring result = {NULL, 0};
281     i = 0;
282     j = 1;
283     while (i < haystack.length) {
284         while (j >= 1 && haystack.ptr[i] != needle.ptr[j - 1]) {
285             j = ptable[j - 1];
286         }
287         i++;
288         j++;
289         if (j - 1 == needle.length) {
290             size_t start = i - needle.length;
291             result.ptr = haystack.ptr + start;
292             result.length = haystack.length - start;
293             break;
294         }
295     }
296
297     // if prefix table was allocated on the heap, free it
298     if (ptable != s_prefix_table) {
299         free(ptable);
300     }
301
302     return result;
303 }
304
305 cxmutstr cx_strstr_m(
306         cxmutstr haystack,
307         cxstring needle
308 ) {
309     cxstring result = cx_strstr(cx_strcast(haystack), needle);
310     return (cxmutstr) {(char *) result.ptr, result.length};
311 }
312
313 size_t cx_strsplit(
314         cxstring string,
315         cxstring delim,
316         size_t limit,
317         cxstring *output
318 ) {
319     // special case: output limit is zero
320     if (limit == 0) return 0;
321
322     // special case: delimiter is empty
323     if (delim.length == 0) {
324         output[0] = string;
325         return 1;
326     }
327
328     // special cases: delimiter is at least as large as the string
329     if (delim.length >= string.length) {
330         // exact match
331         if (cx_strcmp(string, delim) == 0) {
332             output[0] = cx_strn(string.ptr, 0);
333             output[1] = cx_strn(string.ptr + string.length, 0);
334             return 2;
335         } else {
336             // no match possible
337             output[0] = string;
338             return 1;
339         }
340     }
341
342     size_t n = 0;
343     cxstring curpos = string;
344     while (1) {
345         ++n;
346         cxstring match = cx_strstr(curpos, delim);
347         if (match.length > 0) {
348             // is the limit reached?
349             if (n < limit) {
350                 // copy the current string to the array
351                 cxstring item = cx_strn(curpos.ptr, match.ptr - curpos.ptr);
352                 output[n - 1] = item;
353                 size_t processed = item.length + delim.length;
354                 curpos.ptr += processed;
355                 curpos.length -= processed;
356             } else {
357                 // limit reached, copy the _full_ remaining string
358                 output[n - 1] = curpos;
359                 break;
360             }
361         } else {
362             // no more matches, copy last string
363             output[n - 1] = curpos;
364             break;
365         }
366     }
367
368     return n;
369 }
370
371 size_t cx_strsplit_a(
372         CxAllocator *allocator,
373         cxstring string,
374         cxstring delim,
375         size_t limit,
376         cxstring **output
377 ) {
378     // find out how many splits we're going to make and allocate memory
379     size_t n = 0;
380     cxstring curpos = string;
381     while (1) {
382         ++n;
383         cxstring match = cx_strstr(curpos, delim);
384         if (match.length > 0) {
385             // is the limit reached?
386             if (n < limit) {
387                 size_t processed = match.ptr - curpos.ptr + delim.length;
388                 curpos.ptr += processed;
389                 curpos.length -= processed;
390             } else {
391                 // limit reached
392                 break;
393             }
394         } else {
395             // no more matches
396             break;
397         }
398     }
399     *output = cxCalloc(allocator, n, sizeof(cxstring));
400     return cx_strsplit(string, delim, n, *output);
401 }
402
403 size_t cx_strsplit_m(
404         cxmutstr string,
405         cxstring delim,
406         size_t limit,
407         cxmutstr *output
408 ) {
409     return cx_strsplit(cx_strcast(string),
410                        delim, limit, (cxstring *) output);
411 }
412
413 size_t cx_strsplit_ma(
414         CxAllocator *allocator,
415         cxmutstr string,
416         cxstring delim,
417         size_t limit,
418         cxmutstr **output
419 ) {
420     return cx_strsplit_a(allocator, cx_strcast(string),
421                          delim, limit, (cxstring **) output);
422 }
423
424 int cx_strcmp(
425         cxstring s1,
426         cxstring s2
427 ) {
428     if (s1.length == s2.length) {
429         return memcmp(s1.ptr, s2.ptr, s1.length);
430     } else if (s1.length > s2.length) {
431         return 1;
432     } else {
433         return -1;
434     }
435 }
436
437 int cx_strcasecmp(
438         cxstring s1,
439         cxstring s2
440 ) {
441     if (s1.length == s2.length) {
442 #ifdef _WIN32
443         return _strnicmp(s1.ptr, s2.ptr, s1.length);
444 #else
445         return strncasecmp(s1.ptr, s2.ptr, s1.length);
446 #endif
447     } else if (s1.length > s2.length) {
448         return 1;
449     } else {
450         return -1;
451     }
452 }
453
454 int cx_strcmp_p(
455         void const *s1,
456         void const *s2
457 ) {
458     cxstring const *left = s1;
459     cxstring const *right = s2;
460     return cx_strcmp(*left, *right);
461 }
462
463 int cx_strcasecmp_p(
464         void const *s1,
465         void const *s2
466 ) {
467     cxstring const *left = s1;
468     cxstring const *right = s2;
469     return cx_strcasecmp(*left, *right);
470 }
471
472 cxmutstr cx_strdup_a(
473         CxAllocator *allocator,
474         cxstring string
475 ) {
476     cxmutstr result = {
477             cxMalloc(allocator, string.length + 1),
478             string.length
479     };
480     if (result.ptr == NULL) {
481         result.length = 0;
482         return result;
483     }
484     memcpy(result.ptr, string.ptr, string.length);
485     result.ptr[string.length] = '\0';
486     return result;
487 }
488
489 cxstring cx_strtrim(cxstring string) {
490     cxstring result = string;
491     // TODO: optimize by comparing multiple bytes at once
492     while (result.length > 0 && isspace(*result.ptr)) {
493         result.ptr++;
494         result.length--;
495     }
496     while (result.length > 0 && isspace(result.ptr[result.length - 1])) {
497         result.length--;
498     }
499     return result;
500 }
501
502 cxmutstr cx_strtrim_m(cxmutstr string) {
503     cxstring result = cx_strtrim(cx_strcast(string));
504     return (cxmutstr) {(char *) result.ptr, result.length};
505 }
506
507 bool cx_strprefix(
508         cxstring string,
509         cxstring prefix
510 ) {
511     if (string.length < prefix.length) return false;
512     return memcmp(string.ptr, prefix.ptr, prefix.length) == 0;
513 }
514
515 bool cx_strsuffix(
516         cxstring string,
517         cxstring suffix
518 ) {
519     if (string.length < suffix.length) return false;
520     return memcmp(string.ptr + string.length - suffix.length,
521                   suffix.ptr, suffix.length) == 0;
522 }
523
524 bool cx_strcaseprefix(
525         cxstring string,
526         cxstring prefix
527 ) {
528     if (string.length < prefix.length) return false;
529 #ifdef _WIN32
530     return _strnicmp(string.ptr, prefix.ptr, prefix.length) == 0;
531 #else
532     return strncasecmp(string.ptr, prefix.ptr, prefix.length) == 0;
533 #endif
534 }
535
536 bool cx_strcasesuffix(
537         cxstring string,
538         cxstring suffix
539 ) {
540     if (string.length < suffix.length) return false;
541 #ifdef _WIN32
542     return _strnicmp(string.ptr+string.length-suffix.length,
543                   suffix.ptr, suffix.length) == 0;
544 #else
545     return strncasecmp(string.ptr + string.length - suffix.length,
546                        suffix.ptr, suffix.length) == 0;
547 #endif
548 }
549
550 void cx_strlower(cxmutstr string) {
551     cx_for_n(i, string.length) {
552         string.ptr[i] = (char) tolower(string.ptr[i]);
553     }
554 }
555
556 void cx_strupper(cxmutstr string) {
557     cx_for_n(i, string.length) {
558         string.ptr[i] = (char) toupper(string.ptr[i]);
559     }
560 }
561
562 #ifndef CX_STRREPLACE_INDEX_BUFFER_SIZE
563 #define CX_STRREPLACE_INDEX_BUFFER_SIZE 64
564 #endif
565
566 struct cx_strreplace_ibuf {
567     size_t *buf;
568     struct cx_strreplace_ibuf *next;
569     unsigned int len;
570 };
571
572 static void cx_strrepl_free_ibuf(struct cx_strreplace_ibuf *buf) {
573     while (buf) {
574         struct cx_strreplace_ibuf *next = buf->next;
575         free(buf->buf);
576         free(buf);
577         buf = next;
578     }
579 }
580
581 cxmutstr cx_strreplacen_a(
582         CxAllocator *allocator,
583         cxstring str,
584         cxstring pattern,
585         cxstring replacement,
586         size_t replmax
587 ) {
588
589     if (pattern.length == 0 || pattern.length > str.length || replmax == 0)
590         return cx_strdup_a(allocator, str);
591
592     // Compute expected buffer length
593     size_t ibufmax = str.length / pattern.length;
594     size_t ibuflen = replmax < ibufmax ? replmax : ibufmax;
595     if (ibuflen > CX_STRREPLACE_INDEX_BUFFER_SIZE) {
596         ibuflen = CX_STRREPLACE_INDEX_BUFFER_SIZE;
597     }
598
599     // Allocate first index buffer
600     struct cx_strreplace_ibuf *firstbuf, *curbuf;
601     firstbuf = curbuf = calloc(1, sizeof(struct cx_strreplace_ibuf));
602     if (!firstbuf) return cx_mutstrn(NULL, 0);
603     firstbuf->buf = calloc(ibuflen, sizeof(size_t));
604     if (!firstbuf->buf) {
605         free(firstbuf);
606         return cx_mutstrn(NULL, 0);
607     }
608
609     // Search occurrences
610     cxstring searchstr = str;
611     size_t found = 0;
612     do {
613         cxstring match = cx_strstr(searchstr, pattern);
614         if (match.length > 0) {
615             // Allocate next buffer in chain, if required
616             if (curbuf->len == ibuflen) {
617                 struct cx_strreplace_ibuf *nextbuf =
618                         calloc(1, sizeof(struct cx_strreplace_ibuf));
619                 if (!nextbuf) {
620                     cx_strrepl_free_ibuf(firstbuf);
621                     return cx_mutstrn(NULL, 0);
622                 }
623                 nextbuf->buf = calloc(ibuflen, sizeof(size_t));
624                 if (!nextbuf->buf) {
625                     free(nextbuf);
626                     cx_strrepl_free_ibuf(firstbuf);
627                     return cx_mutstrn(NULL, 0);
628                 }
629                 curbuf->next = nextbuf;
630                 curbuf = nextbuf;
631             }
632
633             // Record match index
634             found++;
635             size_t idx = match.ptr - str.ptr;
636             curbuf->buf[curbuf->len++] = idx;
637             searchstr.ptr = match.ptr + pattern.length;
638             searchstr.length = str.length - idx - pattern.length;
639         } else {
640             break;
641         }
642     } while (searchstr.length > 0 && found < replmax);
643
644     // Allocate result string
645     cxmutstr result;
646     {
647         ssize_t adjlen = (ssize_t) replacement.length - (ssize_t) pattern.length;
648         size_t rcount = 0;
649         curbuf = firstbuf;
650         do {
651             rcount += curbuf->len;
652             curbuf = curbuf->next;
653         } while (curbuf);
654         result.length = str.length + rcount * adjlen;
655         result.ptr = cxMalloc(allocator, result.length + 1);
656         if (!result.ptr) {
657             cx_strrepl_free_ibuf(firstbuf);
658             return cx_mutstrn(NULL, 0);
659         }
660     }
661
662     // Build result string
663     curbuf = firstbuf;
664     size_t srcidx = 0;
665     char *destptr = result.ptr;
666     do {
667         for (size_t i = 0; i < curbuf->len; i++) {
668             // Copy source part up to next match
669             size_t idx = curbuf->buf[i];
670             size_t srclen = idx - srcidx;
671             if (srclen > 0) {
672                 memcpy(destptr, str.ptr + srcidx, srclen);
673                 destptr += srclen;
674                 srcidx += srclen;
675             }
676
677             // Copy the replacement and skip the source pattern
678             srcidx += pattern.length;
679             memcpy(destptr, replacement.ptr, replacement.length);
680             destptr += replacement.length;
681         }
682         curbuf = curbuf->next;
683     } while (curbuf);
684     memcpy(destptr, str.ptr + srcidx, str.length - srcidx);
685
686     // Result is guaranteed to be zero-terminated
687     result.ptr[result.length] = '\0';
688
689     // Free index buffer
690     cx_strrepl_free_ibuf(firstbuf);
691
692     return result;
693 }
694
695 CxStrtokCtx cx_strtok(
696         cxstring str,
697         cxstring delim,
698         size_t limit
699 ) {
700     CxStrtokCtx ctx;
701     ctx.str = str;
702     ctx.delim = delim;
703     ctx.limit = limit;
704     ctx.pos = 0;
705     ctx.next_pos = 0;
706     ctx.delim_pos = 0;
707     ctx.found = 0;
708     ctx.delim_more = NULL;
709     ctx.delim_more_count = 0;
710     return ctx;
711 }
712
713 CxStrtokCtx cx_strtok_m(
714         cxmutstr str,
715         cxstring delim,
716         size_t limit
717 ) {
718     return cx_strtok(cx_strcast(str), delim, limit);
719 }
720
721 bool cx_strtok_next(
722         CxStrtokCtx *ctx,
723         cxstring *token
724 ) {
725     // abortion criteria
726     if (ctx->found >= ctx->limit || ctx->delim_pos >= ctx->str.length) {
727         return false;
728     }
729
730     // determine the search start
731     cxstring haystack = cx_strsubs(ctx->str, ctx->next_pos);
732
733     // search the next delimiter
734     cxstring delim = cx_strstr(haystack, ctx->delim);
735
736     // if found, make delim capture exactly the delimiter
737     if (delim.length > 0) {
738         delim.length = ctx->delim.length;
739     }
740
741     // if more delimiters are specified, check them now
742     if (ctx->delim_more_count > 0) {
743         cx_for_n(i, ctx->delim_more_count) {
744             cxstring d = cx_strstr(haystack, ctx->delim_more[i]);
745             if (d.length > 0 && (delim.length == 0 || d.ptr < delim.ptr)) {
746                 delim.ptr = d.ptr;
747                 delim.length = ctx->delim_more[i].length;
748             }
749         }
750     }
751
752     // store the token information and adjust the context
753     ctx->found++;
754     ctx->pos = ctx->next_pos;
755     token->ptr = &ctx->str.ptr[ctx->pos];
756     ctx->delim_pos = delim.length == 0 ?
757                      ctx->str.length : (size_t) (delim.ptr - ctx->str.ptr);
758     token->length = ctx->delim_pos - ctx->pos;
759     ctx->next_pos = ctx->delim_pos + delim.length;
760
761     return true;
762 }
763
764 bool cx_strtok_next_m(
765         CxStrtokCtx *ctx,
766         cxmutstr *token
767 ) {
768     return cx_strtok_next(ctx, (cxstring *) token);
769 }
770
771 void cx_strtok_delim(
772         CxStrtokCtx *ctx,
773         cxstring const *delim,
774         size_t count
775 ) {
776     ctx->delim_more = delim;
777     ctx->delim_more_count = count;
778 }