egra: agg mini clipping bugfix; some fixes in widget rendering (buttons, etc.)
[iv.d.git] / strex.d
bloba570dae9d19947bb1b748d650c013a776d4dbe0c
1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 // some string operations: quoting, `indexOf()` for non-utf8
18 module iv.strex /*is aliced*/;
21 /// quote string: append double quotes, screen all special chars;
22 /// so quoted string forms valid D string literal.
23 /// allocates.
24 string quote (const(char)[] s) {
25 import std.array : appender;
26 import std.format : formatElement, FormatSpec;
27 auto res = appender!string();
28 FormatSpec!char fspc; // defaults to 's'
29 formatElement(res, s, fspc);
30 return res.data;
34 /// convert integral number to number with commas
35 char[] intWithCommas(T) (char[] dest, T nn, char comma=',') if (__traits(isIntegral, T)) {
36 static if (__traits(isUnsigned, T)) {
37 enum neg = false;
38 //alias n = nn;
39 static if (T.sizeof < 8) {
40 uint n = nn;
41 } else {
42 ulong n = nn;
44 } else {
45 bool neg = (nn < 0);
46 static if (T.sizeof < 8) {
47 long n = nn;
48 if (neg) n = -n;
49 if (n < 0) n = T.max;
50 } else {
51 //alias n = nn;
52 long n = nn;
53 if (neg) n = -n;
54 if (n < 0) n = T.max; //FIXME
57 char[256] buf = void;
58 int bpos = cast(int)buf.length;
59 int leftToComma = 3;
60 do {
61 if (leftToComma-- == 0) { buf[--bpos] = comma; leftToComma = 2; }
62 buf[--bpos] = cast(char)('0'+n%10);
63 } while ((n /= 10) != 0);
64 if (neg) buf[--bpos] = '-';
65 auto len = buf.length-bpos;
66 if (dest is null) dest = new char[](len);
67 if (len > dest.length) len = dest.length;
68 dest[0..len] = buf[bpos..bpos+len];
69 return dest[0..len];
72 char[] intWithCommas(T) (T nn, char comma=',') if (__traits(isIntegral, T)) { return intWithCommas(null, nn, comma); }
75 //char tolower (char ch) pure nothrow @trusted @nogc { pragma(inline, true); return (ch >= 'A' && ch <= 'Z' ? cast(char)(ch-'A'+'a') : ch); }
76 //char toupper (char ch) pure nothrow @trusted @nogc { pragma(inline, true); return (ch >= 'a' && ch <= 'z' ? cast(char)(ch-'a'+'A') : ch); }
77 char tolower (char ch) pure nothrow @trusted @nogc { pragma(inline, true); return cast(char)(ch+((ch >= 'A' && ch <= 'Z')<<5)); }
78 char toupper (char ch) pure nothrow @trusted @nogc { pragma(inline, true); return cast(char)(ch-((ch >= 'a' && ch <= 'z')<<5)); }
80 bool islower (char ch) pure nothrow @trusted @nogc { pragma(inline, true); return (ch >= 'a' && ch <= 'z'); }
81 bool isupper (char ch) pure nothrow @trusted @nogc { pragma(inline, true); return (ch >= 'A' && ch <= 'Z'); }
83 bool isalpha (char ch) pure nothrow @trusted @nogc { pragma(inline, true); return ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')); }
84 bool isdigit (char ch) pure nothrow @trusted @nogc { pragma(inline, true); return (ch >= '0' && ch <= '9'); }
85 bool isalnum (char ch) pure nothrow @trusted @nogc { pragma(inline, true); return ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')); }
86 bool isxdigit (char ch) pure nothrow @trusted @nogc { pragma(inline, true); return ((ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f') || (ch >= '0' && ch <= '9')); }
88 /// case-insensitive char compare for ASCII
89 bool charEquCI (const char c0, const char c1) pure nothrow @trusted @nogc {
90 pragma(inline, true);
91 // (c0 |= 0x20) is lowercase-conversion for ASCII
92 // the good thing is that only uppercase letters will become lowercase letters,
93 // other things will become a garbage
94 // also, let's hope that any decent compiler is able to perform CSE here
95 return
96 c0 == c1 || // try the easiest case first
97 ((c0|0x20) >= 'a' && (c0|0x20) <= 'z' && // it wasn't a letter, no need to check the second char
98 (c0|0x20) == (c1|0x20)); // c1 will become a lowercase ascii only if it was uppercase/lowercase ascii
101 int digitInBase (const char ch, const int base=10) pure nothrow @trusted @nogc {
102 pragma(inline, true);
103 return
104 ch >= '0' && ch <= '9' && ch-'0' < base ? ch-'0' :
105 base > 10 && ch >= 'A' && ch < 'Z' && ch-'A'+10 < base ? ch-'A'+10 :
106 base > 10 && ch >= 'a' && ch < 'z' && ch-'a'+10 < base ? ch-'a'+10 :
111 alias atof = atofd!float; /// very simple atof/atod converter. accepts exponents. returns NaN on error.
112 alias atod = atofd!double; /// very simple atof/atod converter. accepts exponents. returns NaN on error.
114 /// very simple atof/atod converter. accepts exponents.
115 /// returns NaN on error.
116 T atofd(T) (const(char)[] str) pure nothrow @trusted @nogc if (is(T == float) || is(T == double)) {
117 if (str.length == 0) return T.nan; // oops
119 const(char)[] s = str;
120 double res = 0.0, sign = 1.0;
121 bool hasIntPart = false, hasFracPart = false;
123 char peekChar () nothrow @trusted @nogc { pragma(inline, true); return (s.length ? s.ptr[0] : '\0'); }
124 void skipChar () nothrow @trusted @nogc { pragma(inline, true); if (s.length > 0) s = s[1..$]; }
125 char getChar () nothrow @trusted @nogc { char ch = 0; if (s.length > 0) { ch = s.ptr[0]; s = s[1..$]; } return ch; }
127 // optional sign
128 switch (peekChar) {
129 case '-': sign = -1; goto case;
130 case '+': skipChar(); break;
131 default: break;
134 // integer part
135 if (isdigit(peekChar)) {
136 hasIntPart = true;
137 while (isdigit(peekChar)) res = res*10.0+(getChar()-'0');
140 // fractional part.
141 if (peekChar == '.') {
142 skipChar(); // skip '.'
143 if (isdigit(peekChar)) {
144 hasFracPart = true;
145 int divisor = 1;
146 long num = 0;
147 while (isdigit(peekChar)) {
148 divisor *= 10;
149 num = num*10+(getChar()-'0');
151 res += cast(double)num/divisor;
155 // valid number should have integer or fractional part
156 if (!hasIntPart && !hasFracPart) return T.nan;
158 // optional exponent
159 if (peekChar == 'e' || peekChar == 'E') {
160 skipChar(); // skip 'E'
161 // optional sign
162 bool epositive = true;
163 switch (peekChar) {
164 case '-': epositive = false; goto case;
165 case '+': skipChar(); break;
166 default: break;
168 int expPart = 0;
169 while (isdigit(peekChar)) expPart = expPart*10+(getChar()-'0');
170 if (epositive) {
171 foreach (immutable _; 0..expPart) res *= 10.0;
172 } else {
173 foreach (immutable _; 0..expPart) res /= 10.0;
177 return cast(T)(res*sign);
181 // ascii only
182 bool strEquCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
183 if (s0.length != s1.length) return false;
184 if (s0.ptr == s1.ptr) return true;
185 foreach (immutable idx, char c0; s0) {
186 // try the easiest case first
187 if (__ctfe) {
188 if (c0 == s1[idx]) continue;
189 } else {
190 if (c0 == s1.ptr[idx]) continue;
192 c0 |= 0x20; // convert to ascii lowercase
193 if (c0 < 'a' || c0 > 'z') return false; // it wasn't a letter, no need to check the second char
194 // c0 is guaranteed to be a lowercase ascii here
195 if (__ctfe) {
196 if (c0 != (s1[idx]|0x20)) return false; // c1 will become a lowercase ascii only if it was uppercase/lowercase ascii
197 } else {
198 if (c0 != (s1.ptr[idx]|0x20)) return false; // c1 will become a lowercase ascii only if it was uppercase/lowercase ascii
201 return true;
205 version(test_strex) unittest {
206 assert(strEquCI("Alice", "alice"));
207 assert(strEquCI("alice", "Alice"));
208 assert(strEquCI("alice", "alice"));
212 // ascii only
213 int strCmpCI (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc {
214 auto slen = s0.length;
215 if (s1.length == slen && s0.ptr == s1.ptr) return 0;
216 if (slen > s1.length) slen = s1.length;
217 char c1;
218 foreach (immutable idx, char c0; s0[0..slen]) {
219 c0 = c0.tolower;
220 if (__ctfe) {
221 c1 = s1[idx].tolower;
222 } else {
223 c1 = s1.ptr[idx].tolower;
225 if (c0 < c1) return -1;
226 if (c0 > c1) return 1;
228 if (s0.length < s1.length) return -1;
229 if (s0.length > s1.length) return +1;
230 return 0;
234 inout(char)[] xstrip (inout(char)[] s) pure nothrow @trusted @nogc {
235 if (__ctfe) {
236 while (s.length && s[0] <= ' ') s = s[1..$];
237 } else {
238 while (s.length && s.ptr[0] <= ' ') s = s[1..$];
240 while (s.length && s[$-1] <= ' ') s = s[0..$-1];
241 return s;
245 inout(char)[] xstripleft (inout(char)[] s) pure nothrow @trusted @nogc {
246 if (__ctfe) {
247 while (s.length && s[0] <= ' ') s = s[1..$];
248 } else {
249 while (s.length && s.ptr[0] <= ' ') s = s[1..$];
251 return s;
255 inout(char)[] xstripright (inout(char)[] s) pure nothrow @trusted @nogc {
256 while (s.length && s[$-1] <= ' ') s = s[0..$-1];
257 return s;
261 bool startsWith (const(char)[] str, const(char)[] pat) pure nothrow @trusted @nogc {
262 if (pat.length > str.length) return false;
263 return (str[0..pat.length] == pat);
267 bool endsWith (const(char)[] str, const(char)[] pat) pure nothrow @trusted @nogc {
268 if (pat.length > str.length) return false;
269 return (str[$-pat.length..$] == pat);
273 // ascii only
274 bool startsWithCI (const(char)[] str, const(char)[] pat) pure nothrow @trusted @nogc {
275 if (pat.length > str.length) return false;
276 return strEquCI(str[0..pat.length], pat);
280 // ascii only
281 bool endsWithCI (const(char)[] str, const(char)[] pat) pure nothrow @trusted @nogc {
282 if (pat.length > str.length) return false;
283 return strEquCI(str[$-pat.length..$], pat);
287 ptrdiff_t indexOf (const(char)[] hay, const(char)[] need, size_t stIdx=0) pure nothrow @trusted @nogc {
288 if (hay.length <= stIdx || need.length == 0 || need.length > hay.length-stIdx) {
289 return -1;
290 } else {
291 if (need.length == 1) {
292 if (__ctfe) {
293 return indexOf(hay, need[0], stIdx);
294 } else {
295 return indexOf(hay, need.ptr[0], stIdx);
297 } else {
298 if (__ctfe) {
299 foreach (immutable idx; stIdx..hay.length-need.length+1) {
300 if (hay[idx..idx+need.length] == need) return idx;
302 return -1;
303 } else {
304 auto res = cast(const(char)*)memmem(hay.ptr+stIdx, hay.length-stIdx, need.ptr, need.length);
305 return (res !is null ? cast(ptrdiff_t)(res-hay.ptr) : -1);
311 ptrdiff_t indexOf (const(char)[] hay, char ch, size_t stIdx=0) pure nothrow @trusted @nogc {
312 if (hay.length <= stIdx) {
313 return -1;
314 } else {
315 if (__ctfe) {
316 foreach (immutable idx; stIdx..hay.length) {
317 if (hay[idx] == ch) return idx;
319 return -1;
320 } else {
321 import core.stdc.string : memchr;
322 auto res = cast(const(char)*)memchr(hay.ptr+stIdx, ch, hay.length-stIdx);
323 return (res !is null ? cast(ptrdiff_t)(res-hay.ptr) : -1);
329 ptrdiff_t lastIndexOf (const(char)[] hay, const(char)[] need, size_t stIdx=0) pure nothrow @trusted @nogc {
330 if (hay.length <= stIdx || need.length == 0 || need.length > hay.length-stIdx) {
331 return -1;
332 } else {
333 if (hay.length == 1) {
334 if (__ctfe) {
335 return lastIndexOf(hay, need[0], stIdx);
336 } else {
337 return lastIndexOf(hay, need.ptr[0], stIdx);
339 } else {
340 if (__ctfe) {
341 foreach_reverse (immutable idx; stIdx..hay.length-need.length+1) {
342 if (hay[idx..idx+need.length] == need) return idx;
344 return -1;
345 } else {
346 auto res = cast(char*)memrmem(hay.ptr+stIdx, hay.length-stIdx, need.ptr, need.length);
347 return (res !is null ? cast(ptrdiff_t)(res-hay.ptr) : -1);
353 ptrdiff_t lastIndexOf (const(char)[] hay, char ch, size_t stIdx=0) pure nothrow @trusted @nogc {
354 if (hay.length <= stIdx) {
355 return -1;
356 } else {
357 if (__ctfe) {
358 foreach_reverse (immutable idx; stIdx..hay.length) {
359 if (hay[idx] == ch) return idx;
361 return -1;
362 } else {
363 auto res = cast(const(char)*)memrchr(hay.ptr+stIdx, ch, hay.length-stIdx);
364 return (res !is null ? cast(ptrdiff_t)(res-hay.ptr) : -1);
370 version(test_strex) unittest {
371 assert(indexOf("Alice & Miriel", " & ") == 5);
372 assert(indexOf("Alice & Miriel", " &!") == -1);
373 assert(indexOf("Alice & Miriel", "Alice & Miriel was here!") == -1);
374 assert(indexOf("Alice & Miriel", '&') == 6);
375 char ch = ' ';
376 assert(indexOf("Alice & Miriel", ch) == 5);
378 assert(indexOf("Alice & Miriel", "i") == 2);
379 assert(indexOf("Alice & Miriel", "i", 6) == 9);
380 assert(indexOf("Alice & Miriel", "i", 12) == -1);
382 assert(indexOf("Alice & Miriel", "Miriel", 8) == 8);
383 assert(indexOf("Alice & Miriel", "Miriel", 9) == -1);
385 assert(lastIndexOf("Alice & Miriel", "i") == 11);
386 assert(lastIndexOf("Alice & Miriel", "i", 6) == 11);
387 assert(lastIndexOf("Alice & Miriel", "i", 11) == 11);
388 assert(lastIndexOf("Alice & Miriel", "i", 12) == -1);
390 assert(lastIndexOf("iiii", "ii") == 2);
394 string detab (const(char)[] s, uint tabSize=8) {
395 assert(tabSize > 0);
397 import std.array : appender;
398 auto res = appender!string();
399 uint col = 0;
401 foreach (char ch; s) {
402 if (ch == '\n' || ch == '\r') {
403 col = 0;
404 } else if (ch == '\t') {
405 auto spins = tabSize-col%tabSize;
406 col += spins;
407 while (spins-- > 1) res.put(' ');
408 ch = ' ';
409 } else {
410 ++col;
412 res.put(ch);
415 return res.data;
419 version(test_strex) unittest {
420 assert(detab(" \n\tx", 9) == " \n x");
421 assert(detab(" ab\t asdf ") == " ab asdf ");
425 auto byLine(T) (T s) if (is(T:const(char)[])) {
426 static struct Range(T) {
427 nothrow @safe @nogc:
428 private:
429 T s;
430 size_t llen, npos;
431 this (T as) { s = as; popFront(); }
432 public:
433 @property bool empty () const { pragma(inline, true); return (s.length == 0); }
434 @property T front () const { pragma(inline, true); return cast(T)s[0..llen]; } // fuckin' const!
435 auto save () const @trusted { Range!T res = void; res.s = s; res.llen = llen; res.npos = npos; return res; }
436 void popFront () @trusted {
437 s = s[npos..$];
438 llen = npos = 0;
439 while (npos < s.length) {
440 if (s.ptr[npos] == '\r') {
441 llen = npos;
442 if (s.length-npos > 1 && s.ptr[npos+1] == '\n') ++npos;
443 ++npos;
444 return;
446 if (s.ptr[npos] == '\n') {
447 llen = npos;
448 ++npos;
449 return;
451 ++npos;
453 llen = npos;
456 return Range!T(s);
460 version(test_strex) unittest {
461 enum s = q{
462 import std.stdio;
463 void main() {
464 writeln("Hello");
467 enum ugly = q{
468 import std.stdio;
469 void main() {
470 writeln("Hello");
474 foreach (/+auto+/ line; s.byLine) {
475 import std.stdio;
476 writeln("LN: [", line, "]");
479 foreach (/+auto+/ line; ugly.byLine) {
480 import std.stdio;
481 writeln("LN: [", line, "]");
486 // string should be detabbed!
487 string outdentAll (const(char)[] s) {
488 import std.array : appender;
489 // first calculate maximum indent spaces
490 uint maxspc = uint.max;
491 foreach (/*auto*/ line; s.byLine) {
492 uint col = 0;
493 while (col < line.length && line.ptr[col] <= ' ') {
494 if (line.ptr[col] == '\t') assert(0, "can't outdent shit with tabs");
495 ++col;
497 if (col >= line.length) continue; // empty line, don't care
498 if (col < maxspc) maxspc = col;
499 if (col == 0) break; // nothing to do anymore
502 auto res = appender!string();
503 foreach (/*auto*/ line; s.byLine) {
504 uint col = 0;
505 while (col < line.length && line.ptr[col] <= ' ') ++col;
506 if (col < line.length) {
507 // non-empty line
508 res.put(line[maxspc..$]);
510 res.put('\n');
513 return res.data;
517 version(test_strex) unittest {
518 enum pretty = q{
519 import std.stdio;
520 void main() {
521 writeln("Hello");
523 }.outdentAll;
525 enum ugly = q{
526 import std.stdio;
527 void main() {
528 writeln("Hello");
533 import std.stdio;
534 assert(pretty == ugly);
538 //From: Yahoo Groups <confirm-s2-2ny0qbq23nljzefbilh5vpjrg1pik5hf-ketmar=ketmar.no-ip.org@yahoogroups.com>
539 private bool isValidEmailNameChar (char ch) pure nothrow @safe @nogc {
540 pragma(inline, true);
541 if (ch <= 32) return false;
542 if (ch >= '0' && ch <= '9') return true;
543 if (ch >= 'a' && ch <= 'z') ch -= 32; // poor man's tolower
544 if (ch >= 'A' && ch <= 'Z') return true;
545 if (ch == '_' || ch == '+' || ch == '-' || ch == '=' || ch == '.' || ch == '$') return true;
546 if (ch >= 128) return true; // why not?
547 // why not?
548 if (ch == '!' || ch == '%' || ch == '^' || ch == '&' || ch == '(' || ch == ')') return true;
549 if (ch == '?') return true;
550 return false;
554 private bool isValidEmailHostChar (char ch) pure nothrow @safe @nogc {
555 pragma(inline, true);
556 if (ch <= 32 || ch >= 127) return false;
557 if (ch >= '0' && ch <= '9') return true;
558 if (ch >= 'a' && ch <= 'z') ch -= 32; // poor man's tolower
559 if (ch >= 'A' && ch <= 'Z') return true;
560 if (ch == '-' || ch == '.') return true;
561 return false;
565 bool isGoodEmail (const(char)[] s) pure nothrow @trusted @nogc {
566 if (s.length == 0 || s.ptr[0] == '@') return false;
567 // parse part until '@'
568 while (s.length) {
569 char ch = s.ptr[0];
570 if (ch == '@') break;
571 if (!isValidEmailNameChar(ch)) return false;
572 s = s[1..$];
574 if (!s.length) return false; // no doggy
575 assert(s.ptr[0] == '@');
576 s = s[1..$];
577 if (s.length == 0) return false;
578 while (s.length) {
579 char ch = s.ptr[0];
580 if (!isValidEmailHostChar(ch)) return false;
581 s = s[1..$];
583 return true;
587 /// backslash in ranges is used to escaping; '<' and '>' matching word start and end
588 bool globmatch(bool casesens=true) (const(char)[] str, const(char)[] pat) pure nothrow @trusted @nogc {
589 static bool globIsWordChar (const char ch) pure nothrow @safe @nogc {
590 pragma(inline, true);
591 return
592 (ch >= 'A' && ch <= 'Z') ||
593 (ch >= 'a' && ch <= 'z') ||
594 (ch >= '0' && ch <= '9') ||
595 ch == '_' || ch >= 128;
598 // empty pattern cannot match non-empty string
599 if (pat.length == 0) return (str.length == 0);
600 // start matching
601 const(char)* realstart = str.ptr;
602 bool star = false;
603 usize patpos = void;
604 loopStart:
605 patpos = 0;
606 foreach (usize i; 0..str.length) {
607 static if (casesens) {
608 immutable char sch = str.ptr[i];
609 } else {
610 immutable char sch = tolower(str.ptr[i]);
612 matchAgain:
613 if (patpos >= pat.length) goto starCheck;
614 switch (pat.ptr[patpos++]) {
615 case '?': // match anything
616 break;
617 case '*':
618 star = true;
619 str = str[i..$];
620 pat = pat[patpos..$];
621 // skip excessive stars
622 while (pat.length && pat.ptr[0] == '*') pat = pat[1..$];
623 if (pat.length == 0) return true;
624 goto loopStart;
625 case '[':
627 bool hasMatch = false;
628 bool inverted = (pat.ptr[patpos] == '^');
629 if (inverted) ++patpos;
630 if (patpos >= pat.length) return false; // malformed pattern
631 do {
632 char c0 = pat.ptr[patpos++];
633 if (c0 == '\\' && patpos < pat.length) c0 = pat.ptr[patpos++];
634 if (patpos >= pat.length) return false; // malformed pattern
635 static if (!casesens) c0 = tolower(c0);
636 char c1 = c0;
637 if (pat.ptr[patpos] == '-') {
638 // char range
639 ++patpos; // skip '-'
640 if (patpos >= pat.length) return false; // malformed pattern
641 c1 = pat.ptr[patpos++];
642 if (c1 == '\\' && patpos < pat.length) c1 = pat.ptr[patpos++];
643 static if (!casesens) c1 = tolower(c1);
644 if (patpos >= pat.length) return false; // malformed pattern
646 hasMatch = (!hasMatch && sch >= c0 && sch <= c1);
647 } while (patpos < pat.length && pat.ptr[patpos] != ']');
648 if (patpos >= pat.length || pat.ptr[patpos] != ']') return false; // malformed pattern
649 ++patpos;
650 if (inverted) hasMatch = !hasMatch;
651 if (!hasMatch) goto starCheck;
652 break;
654 case '<': // word boundary (start)
655 // current char must be a word char
656 if (!globIsWordChar(sch)) goto starCheck;
658 const usize realpos = cast(ptrdiff_t)(str.ptr-realstart)+i;
659 // previous char must not be a word char
660 if (realpos > 0 && globIsWordChar(realstart[realpos-1u])) goto starCheck;
662 goto matchAgain;
663 case '>': // word boundary (end)
664 // current char must not be a word char
665 if (globIsWordChar(sch)) goto starCheck;
667 const usize realpos = cast(ptrdiff_t)(str.ptr-realstart)+i;
668 // previous char must be a word char
669 if (realpos > 0 && !globIsWordChar(realstart[realpos-1u])) goto starCheck;
671 goto matchAgain;
672 case '\\':
673 ++patpos;
674 if (patpos >= pat.length) return false; // malformed pattern
675 goto default;
676 default:
677 static if (casesens) {
678 if (sch != pat.ptr[patpos-1]) goto starCheck;
679 } else {
680 if (sch != tolower(pat.ptr[patpos-1])) goto starCheck;
682 break;
685 pat = pat[patpos..$];
686 // pattern may end with stars, skip them
687 // also skip word boundaries check (they are always true here)
688 while (pat.length && (pat.ptr[0] == '*' || pat.ptr[0] == '<' || pat.ptr[0] == '>')) pat = pat[1..$];
689 return (pat.length == 0);
691 starCheck:
692 if (!star) return false;
693 if (str.length) str = str[1..$];
694 goto loopStart;
697 /// ditto.
698 alias globmatchCI = globmatch!false;
701 pure nothrow @system @nogc:
702 version(linux) {
703 extern(C) inout(void)* memmem (inout(void)* haystack, size_t haystacklen, inout(void)* needle, size_t needlelen);
704 extern(C) inout(void)* memrchr (inout(void)* s, int ch, size_t slen);
705 } else {
706 inout(void)* memmem (inout(void)* haystack, size_t haystacklen, inout(void)* needle, size_t needlelen) {
707 // size_t is unsigned
708 if (needlelen > haystacklen || needlelen == 0) return null;
709 auto h = cast(const(ubyte)*)haystack;
710 auto n = cast(const(ubyte)*)needle;
711 foreach (immutable i; 0..haystacklen-needlelen+1) {
712 import core.stdc.string : memcmp;
713 if (memcmp(h+i, n, needlelen) == 0) return cast(typeof(return))(h+i);
715 return null;
718 inout(void)* memrchr (inout(void)* haystack, int ch, size_t haystacklen) {
719 // size_t is unsigned
720 if (haystacklen == 0) return null;
721 auto h = cast(const(ubyte)*)haystack;
722 ch &= 0xff;
723 foreach_reverse (immutable idx, ubyte v; h[0..haystacklen]) {
724 if (v == ch) return cast(typeof(return))(h+idx);
726 return null;
730 inout(void)* memrmem (inout(void)* haystack, size_t haystacklen, inout(void)* needle, size_t needlelen) {
731 if (needlelen > haystacklen) return null;
732 auto h = cast(const(ubyte)*)haystack;
733 const(ubyte)* res = null;
734 // size_t is unsigned
735 if (needlelen > haystacklen || needlelen == 0) return null;
736 version(none) {
737 size_t pos = 0;
738 while (pos < haystacklen-needlelen+1) {
739 auto ff = memmem(haystack+pos, haystacklen-pos, needle, needlelen);
740 if (ff is null) break;
741 res = cast(const(ubyte)*)ff;
742 pos = cast(size_t)(res-haystack)+1;
744 return cast(void*)res;
745 } else {
746 auto n = cast(const(ubyte)*)needle;
747 size_t len = haystacklen-needlelen+1;
748 while (len > 0) {
749 import core.stdc.string : memcmp;
750 auto ff = cast(const(ubyte)*)memrchr(haystack, *n, len);
751 if (ff is null) break;
752 if (memcmp(ff, needle, needlelen) == 0) return cast(void*)ff;
753 //if (ff is h) break;
754 len = cast(size_t)(ff-cast(ubyte*)haystack);
756 return null;