main
xs_regex.h
1/* copyright (c) 2022 - 2026 grunfink et al. / MIT license */
2
3#ifndef _XS_REGEX_H
4
5#define _XS_REGEX_H
6
7#include "xs.h"
8
9int xs_regex_match(const char *str, const char *rx);
10xs_list *xs_regex_split_n(const char *str, const char *rx, int count);
11#define xs_regex_split(str, rx) xs_regex_split_n(str, rx, XS_ALL)
12xs_list *xs_regex_select_n(const char *str, const char *rx, int count);
13#define xs_regex_select(str, rx) xs_regex_select_n(str, rx, XS_ALL)
14xs_list *xs_regex_replace_in(xs_str *str, const char *rx, const char *rep, int count);
15#define xs_regex_replace_i(str, rx, rep) xs_regex_replace_in(str, rx, rep, XS_ALL)
16#define xs_regex_replace_n(str, rx, rep, count) xs_regex_replace_in(xs_dup(str), rx, rep, count)
17#define xs_regex_replace(str, rx, rep) xs_regex_replace_in(xs_dup(str), rx, rep, XS_ALL)
18
19#ifdef XS_IMPLEMENTATION
20
21#ifdef __TINYC__
22/* fix a compilation error in tcc */
23#define _REGEX_NELTS(n)
24#endif
25
26#include <regex.h>
27
28xs_list *xs_regex_split_n(const char *str, const char *rx, int count)
29/* splits str using regex as a separator, at most count times.
30 Always returns a list:
31 len == 0: regcomp error
32 len == 1: full string (no matches)
33 len == odd: first part [ separator / next part ]...
34*/
35{
36 regex_t re;
37 regmatch_t rm;
38 int offset = 0;
39 xs_list *list = xs_list_new();
40 const char *p = str;
41
42 if (regcomp(&re, rx, REG_EXTENDED))
43 return list;
44
45 while (count > 0 && !regexec(&re, p, 1, &rm, offset > 0 ? REG_NOTBOL : 0)) {
46 /* add first the leading part of the string */
47 xs *s1 = xs_str_new_sz(p, rm.rm_so);
48
49 list = xs_list_append(list, xs_is_string(s1) ? s1 : "");
50
51 /* add now the matched text as the separator */
52 xs *s2 = xs_str_new_sz(p + rm.rm_so, rm.rm_eo - rm.rm_so);
53
54 list = xs_list_append(list, xs_is_string(s2) ? s2 : "");
55
56 /* move forward */
57 offset += rm.rm_eo;
58
59 count--;
60 p = str + offset;
61 }
62
63 /* add the rest of the string */
64 list = xs_list_append(list, xs_is_string(p) ? p : "");
65
66 regfree(&re);
67
68 return list;
69}
70
71
72xs_list *xs_regex_select_n(const char *str, const char *rx, int count)
73/* selects all matches and return them as a list */
74{
75 xs_list *list = xs_list_new();
76 xs *split = NULL;
77 const xs_val *v;
78 int n = 0;
79
80 /* split */
81 split = xs_regex_split_n(str, rx, count);
82
83 /* now iterate to get only the 'separators' (odd ones) */
84 xs_list_foreach(split, v) {
85 if (n & 0x1)
86 list = xs_list_append(list, v);
87
88 n++;
89 }
90
91 return list;
92}
93
94
95xs_list *xs_regex_replace_in(xs_str *str, const char *rx, const char *rep, int count)
96/* replaces all matches with the rep string. If it contains unescaped &,
97 they are replaced with the match */
98{
99 xs_str *s = xs_str_new(NULL);
100 xs *split = xs_regex_split_n(str, rx, count);
101 const xs_val *v;
102 int n = 0;
103 int pholder = !!strchr(rep, '&');
104
105 xs_list_foreach(split, v) {
106 if (n & 0x1) {
107 if (pholder) {
108 /* rep has a placeholder; process char by char */
109 const char *p = rep;
110
111 while (*p) {
112 if (*p == '&')
113 s = xs_str_cat(s, v);
114 else {
115 if (*p == '\\')
116 p++;
117
118 if (!*p)
119 break;
120
121 s = xs_append_m(s, p, 1);
122 }
123
124 p++;
125 }
126 }
127 else
128 s = xs_str_cat(s, rep);
129 }
130 else
131 s = xs_str_cat(s, v);
132
133 n++;
134 }
135
136 xs_free(str);
137
138 return s;
139}
140
141
142int xs_regex_match(const char *str, const char *rx)
143/* returns if str matches the regex at least once */
144{
145 xs *l = xs_regex_select_n(str, rx, 1);
146
147 return xs_list_len(l) == 1;
148}
149
150
151#endif /* XS_IMPLEMENTATION */
152
153#endif /* XS_REGEX_H */