|
13 | 13 |
|
14 | 14 | #include "ops/glob.h" |
15 | 15 |
|
| 16 | +#define _GNU_SOURCE |
| 17 | +#include <string.h> |
| 18 | + |
16 | 19 | /* Lowercase an ASCII byte; non-ASCII passes through unchanged. */ |
17 | 20 | static inline char to_lower(char c) { |
18 | 21 | return (c >= 'A' && c <= 'Z') ? (char)(c + 32) : c; |
@@ -100,3 +103,96 @@ bool ray_glob_match(const char* s, size_t sn, const char* p, size_t pn) { |
100 | 103 | bool ray_glob_match_ci(const char* s, size_t sn, const char* p, size_t pn) { |
101 | 104 | return glob_impl(s, sn, p, pn, true); |
102 | 105 | } |
| 106 | + |
| 107 | +ray_glob_compiled_t ray_glob_compile(const char* p, size_t pn) { |
| 108 | + ray_glob_compiled_t c = { RAY_GLOB_SHAPE_NONE, NULL, 0 }; |
| 109 | + |
| 110 | + if (pn == 0) { |
| 111 | + c.shape = RAY_GLOB_SHAPE_EXACT; |
| 112 | + c.lit = p; c.lit_len = 0; |
| 113 | + return c; |
| 114 | + } |
| 115 | + |
| 116 | + /* Strip a single leading and trailing '*'; classify by the residual |
| 117 | + * pattern. Any other glob metachar (`?`, `[`, or interior `*`) |
| 118 | + * forces the general matcher. */ |
| 119 | + size_t lo = 0, hi = pn; |
| 120 | + bool leading_star = (p[0] == '*'); |
| 121 | + bool trailing_star = (pn > 0 && p[pn - 1] == '*' && |
| 122 | + /* don't double-count single '*' as both */ |
| 123 | + (pn > 1 || !leading_star)); |
| 124 | + if (leading_star) lo = 1; |
| 125 | + if (trailing_star) hi = pn - 1; |
| 126 | + |
| 127 | + /* Ensure the residual has no glob metacharacters. */ |
| 128 | + for (size_t i = lo; i < hi; i++) { |
| 129 | + char ch = p[i]; |
| 130 | + if (ch == '*' || ch == '?' || ch == '[') { |
| 131 | + c.shape = RAY_GLOB_SHAPE_NONE; |
| 132 | + return c; |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | + c.lit = p + lo; |
| 137 | + c.lit_len = hi - lo; |
| 138 | + |
| 139 | + if (leading_star && trailing_star) { |
| 140 | + c.shape = (c.lit_len == 0) ? RAY_GLOB_SHAPE_ANY |
| 141 | + : RAY_GLOB_SHAPE_CONTAINS; |
| 142 | + } else if (leading_star) { |
| 143 | + c.shape = RAY_GLOB_SHAPE_SUFFIX; |
| 144 | + } else if (trailing_star) { |
| 145 | + c.shape = RAY_GLOB_SHAPE_PREFIX; |
| 146 | + } else { |
| 147 | + c.shape = RAY_GLOB_SHAPE_EXACT; |
| 148 | + } |
| 149 | + return c; |
| 150 | +} |
| 151 | + |
| 152 | +bool ray_glob_match_compiled(const ray_glob_compiled_t* c, |
| 153 | + const char* s, size_t sn) { |
| 154 | + switch (c->shape) { |
| 155 | + case RAY_GLOB_SHAPE_ANY: |
| 156 | + return true; |
| 157 | + case RAY_GLOB_SHAPE_EXACT: |
| 158 | + return sn == c->lit_len && |
| 159 | + (c->lit_len == 0 || memcmp(s, c->lit, c->lit_len) == 0); |
| 160 | + case RAY_GLOB_SHAPE_PREFIX: |
| 161 | + return sn >= c->lit_len && |
| 162 | + (c->lit_len == 0 || memcmp(s, c->lit, c->lit_len) == 0); |
| 163 | + case RAY_GLOB_SHAPE_SUFFIX: |
| 164 | + return sn >= c->lit_len && |
| 165 | + (c->lit_len == 0 || |
| 166 | + memcmp(s + sn - c->lit_len, c->lit, c->lit_len) == 0); |
| 167 | + case RAY_GLOB_SHAPE_CONTAINS: |
| 168 | + if (c->lit_len == 0) return true; |
| 169 | + if (sn < c->lit_len) return false; |
| 170 | + /* glibc's memmem is SIMD-accelerated; use it where available. |
| 171 | + * Falls back to a portable Boyer-Moore-Horspool when not. */ |
| 172 | +#if defined(__GLIBC__) || defined(__APPLE__) || defined(__FreeBSD__) |
| 173 | + return memmem(s, sn, c->lit, c->lit_len) != NULL; |
| 174 | +#else |
| 175 | + { |
| 176 | + /* Portable fallback: short-needle byte scan with memchr. */ |
| 177 | + const char first = c->lit[0]; |
| 178 | + const char* haystack = s; |
| 179 | + size_t remaining = sn; |
| 180 | + while (remaining >= c->lit_len) { |
| 181 | + const char* hit = (const char*)memchr(haystack, first, |
| 182 | + remaining - c->lit_len + 1); |
| 183 | + if (!hit) return false; |
| 184 | + if (memcmp(hit, c->lit, c->lit_len) == 0) return true; |
| 185 | + size_t adv = (size_t)(hit - haystack) + 1; |
| 186 | + haystack = hit + 1; |
| 187 | + remaining -= adv; |
| 188 | + } |
| 189 | + return false; |
| 190 | + } |
| 191 | +#endif |
| 192 | + case RAY_GLOB_SHAPE_NONE: |
| 193 | + default: |
| 194 | + /* Caller contract violation — fall through to false rather than |
| 195 | + * silently matching everything. */ |
| 196 | + return false; |
| 197 | + } |
| 198 | +} |
0 commit comments