1 // Written in the D programming language.
4 String handling functions.
6 $(SCRIPT inhibitQuickIndex = 1;)
10 $(TR $(TH Category) $(TH Functions) )
11 $(TR $(TDNW Searching)
16 $(MYREF indexOfNeither)
18 $(MYREF lastIndexOfAny)
19 $(MYREF lastIndexOfNeither)
22 $(TR $(TDNW Comparison)
32 $(TR $(TDNW Pruning and Filling)
51 $(TR $(TDNW Substitution)
61 $(TR $(TDNW Miscellaneous)
66 $(MYREF representation)
72 Objects of types $(D _string), $(D wstring), and $(D dstring) are value types
73 and cannot be mutated element-by-element. For using mutation during building
74 strings, use $(D char[]), $(D wchar[]), or $(D dchar[]). The $(D xxxstring)
75 types are preferable because they don't exhibit undesired aliasing, thus
76 making code more robust.
78 The following functions are publicly imported:
81 $(TR $(TH Module) $(TH Functions) )
82 $(LEADINGROW Publicly imported functions)
83 $(TR $(TD std.algorithm)
85 $(REF_SHORT cmp, std,algorithm,comparison)
86 $(REF_SHORT count, std,algorithm,searching)
87 $(REF_SHORT endsWith, std,algorithm,searching)
88 $(REF_SHORT startsWith, std,algorithm,searching)
92 $(REF_SHORT join, std,array)
93 $(REF_SHORT replace, std,array)
94 $(REF_SHORT replaceInPlace, std,array)
95 $(REF_SHORT split, std,array)
96 $(REF_SHORT empty, std,array)
100 $(REF_SHORT format, std,format)
101 $(REF_SHORT sformat, std,format)
105 $(REF_SHORT icmp, std,uni)
106 $(REF_SHORT toLower, std,uni)
107 $(REF_SHORT toLowerInPlace, std,uni)
108 $(REF_SHORT toUpper, std,uni)
109 $(REF_SHORT toUpperInPlace, std,uni)
113 There is a rich set of functions for _string handling defined in other modules.
114 Functions related to Unicode and ASCII are found in $(MREF std, uni)
115 and $(MREF std, ascii), respectively. Other functions that have a
116 wider generality than just strings can be found in $(MREF std, algorithm)
117 and $(MREF std, range).
121 $(MREF std, algorithm) and
123 for generic range algorithms
126 for functions that work with ASCII strings
129 for functions that work with unicode strings
132 Copyright: Copyright Digital Mars 2007-.
134 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
136 Authors: $(HTTP digitalmars.com, Walter Bright),
137 $(HTTP erdani.org, Andrei Alexandrescu),
139 and David L. 'SpottedTiger' Davis
141 Source: $(PHOBOSSRC std/_string.d)
149 struct TestAliasedString
151 string get() @safe @nogc pure nothrow { return _s; }
157 bool testAliasedString(alias func, Args...)(string s, Args args)
159 import std.algorithm.comparison : equal;
160 auto a = func(TestAliasedString(s), args);
161 auto b = func(s, args);
162 static if (is(typeof(equal(a, b))))
164 // For ranges, compare contents instead of object identity.
174 public import std.format : format, sformat;
175 import std.typecons : Flag, Yes, No;
176 public import std.uni : icmp, toLower, toLowerInPlace, toUpper, toUpperInPlace;
178 import std.meta; // AliasSeq, staticIndexOf
179 import std.range.primitives; // back, ElementEncodingType, ElementType, front,
180 // hasLength, hasSlicing, isBidirectionalRange, isForwardRange, isInfinite,
181 // isInputRange, isOutputRange, isRandomAccessRange, popBack, popFront, put,
183 import std.traits; // isConvertibleToString, isNarrowString, isSomeChar,
184 // isSomeString, StringTypeOf, Unqual
186 //public imports for backward compatibility
187 public import std.algorithm.comparison : cmp;
188 public import std.algorithm.searching : startsWith, endsWith, count;
189 public import std.array : join, replace, replaceInPlace, split, empty;
191 /* ************* Exceptions *************** */
194 Exception thrown on errors in std.string functions.
196 class StringException : Exception
198 import std.exception : basicExceptionCtors;
201 mixin basicExceptionCtors;
207 cString = A null-terminated c-style string.
209 Returns: A D-style array of $(D char) referencing the same string. The
210 returned array will retain the same type qualifiers as the input.
212 $(RED Important Note:) The returned array is a slice of the original buffer.
213 The original data is not changed and not copied.
216 inout(char)[] fromStringz(inout(char)* cString) @nogc @system pure nothrow {
217 import core.stdc.string : strlen;
218 return cString ? cString[0 .. strlen(cString)] : null;
222 @system pure unittest
224 assert(fromStringz(null) == null);
225 assert(fromStringz("foo") == "foo");
230 s = A D-style string.
232 Returns: A C-style null-terminated string equivalent to $(D s). $(D s)
233 must not contain embedded $(D '\0')'s as any C function will treat the
234 first $(D '\0') that it sees as the end of the string. If $(D s.empty) is
235 $(D true), then a string containing only $(D '\0') is returned.
237 $(RED Important Note:) When passing a $(D char*) to a C function, and the C
238 function keeps it around for any reason, make sure that you keep a
239 reference to it in your D code. Otherwise, it may become invalid during a
240 garbage collection cycle and cause a nasty bug when the C code tries to use
243 immutable(char)* toStringz(const(char)[] s) @trusted pure nothrow
246 import core.stdc.string : strlen, memcmp;
249 auto slen = s.length;
250 while (slen > 0 && s[slen-1] == 0) --slen;
251 assert(strlen(result) == slen);
252 assert(result[0 .. slen] == s[0 .. slen]);
257 import std.exception : assumeUnique;
258 /+ Unfortunately, this isn't reliable.
259 We could make this work if string literals are put
260 in read-only memory and we test if s[] is pointing into
263 /* Peek past end of s[], if it's 0, no conversion necessary.
264 * Note that the compiler will put a 0 past the end of static
265 * strings, and the storage allocator will put a 0 past the end
266 * of newly allocated char[]'s.
268 char* p = &s[0] + s.length;
273 // Need to make a copy
274 auto copy = new char[s.length + 1];
275 copy[0 .. s.length] = s[];
278 return &assumeUnique(copy)[0];
282 immutable(char)* toStringz(in string s) @trusted pure nothrow
284 if (s.empty) return "".ptr;
285 /* Peek past end of s[], if it's 0, no conversion necessary.
286 * Note that the compiler will put a 0 past the end of static
287 * strings, and the storage allocator will put a 0 past the end
288 * of newly allocated char[]'s.
290 immutable p = s.ptr + s.length;
291 // Is p dereferenceable? A simple test: if the p points to an
292 // address multiple of 4, then conservatively assume the pointer
293 // might be pointing to a new block of memory, which might be
294 // unreadable. Otherwise, it's definitely pointing to valid
296 if ((cast(size_t) p & 3) && *p == 0)
298 return toStringz(cast(const char[]) s);
302 pure nothrow @system unittest
304 import core.stdc.string : strlen;
305 import std.conv : to;
307 auto p = toStringz("foo");
308 assert(strlen(p) == 3);
309 const(char)[] foo = "abbzxyzzy";
310 p = toStringz(foo[3 .. 5]);
311 assert(strlen(p) == 2);
323 assert(p[0] == 'f' && p[1] == 'o' && p[2] == 'o' && p[3] == 0);
325 const string test2 = "";
326 p = toStringz(test2);
332 Flag indicating whether a search is case-sensitive.
334 alias CaseSensitive = Flag!"caseSensitive";
337 Searches for character in range.
340 s = string or InputRange of characters to search in correct UTF format
341 c = character to search for
342 startIdx = starting index to a well-formed code point
343 cs = $(D Yes.caseSensitive) or $(D No.caseSensitive)
346 the index of the first occurrence of $(D c) in $(D s) with
347 respect to the start index $(D startIdx). If $(D c)
348 is not found, then $(D -1) is returned.
349 If $(D c) is found the value of the returned index is at least
351 If the parameters are not valid UTF, the result will still
352 be in the range [-1 .. s.length], but will not be reliable otherwise.
355 If the sequence starting at $(D startIdx) does not represent a well
356 formed codepoint, then a $(REF UTFException, std,utf) may be thrown.
358 See_Also: $(REF countUntil, std,algorithm,searching)
360 ptrdiff_t indexOf(Range)(Range s, in dchar c,
361 in CaseSensitive cs = Yes.caseSensitive)
362 if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) &&
363 !isConvertibleToString!Range)
365 static import std.ascii;
366 static import std.uni;
367 import std.utf : byDchar, byCodeUnit, UTFException, codeLength;
368 alias Char = Unqual!(ElementEncodingType!Range);
370 if (cs == Yes.caseSensitive)
372 static if (Char.sizeof == 1 && isSomeString!Range)
374 if (std.ascii.isASCII(c) && !__ctfe)
376 static ptrdiff_t trustedmemchr(Range s, char c) @trusted
378 import core.stdc.string : memchr;
379 const p = cast(const(Char)*)memchr(s.ptr, c, s.length);
380 return p ? p - s.ptr : -1;
383 return trustedmemchr(s, cast(char) c);
387 static if (Char.sizeof == 1)
392 foreach (const c2; s)
402 foreach (const c2; s.byDchar())
406 i += codeLength!Char(c2);
410 else static if (Char.sizeof == 2)
415 foreach (const c2; s)
422 else if (c <= 0x10FFFF)
424 // Encode UTF-16 surrogate pair
425 const wchar c1 = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
426 const wchar c2 = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00);
428 for (auto r = s.byCodeUnit(); !r.empty; r.popFront())
433 if (r.empty) // invalid UTF - missing second of pair
443 else static if (Char.sizeof == 4)
446 foreach (const c2; s)
459 if (std.ascii.isASCII(c))
461 immutable c1 = cast(char) std.ascii.toLower(c);
464 foreach (const c2; s.byCodeUnit())
466 if (c1 == std.ascii.toLower(c2))
472 { // c is a universal character
473 immutable c1 = std.uni.toLower(c);
476 foreach (const c2; s.byDchar())
478 if (c1 == std.uni.toLower(c2))
480 i += codeLength!Char(c2);
488 ptrdiff_t indexOf(Range)(Range s, in dchar c, in size_t startIdx,
489 in CaseSensitive cs = Yes.caseSensitive)
490 if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) &&
491 !isConvertibleToString!Range)
493 static if (isSomeString!(typeof(s)) ||
494 (hasSlicing!(typeof(s)) && hasLength!(typeof(s))))
496 if (startIdx < s.length)
498 ptrdiff_t foundIdx = indexOf(s[startIdx .. $], c, cs);
501 return foundIdx + cast(ptrdiff_t) startIdx;
507 foreach (i; 0 .. startIdx)
513 ptrdiff_t foundIdx = indexOf(s, c, cs);
516 return foundIdx + cast(ptrdiff_t) startIdx;
525 import std.typecons : No;
527 string s = "Hello World";
528 assert(indexOf(s, 'W') == 6);
529 assert(indexOf(s, 'Z') == -1);
530 assert(indexOf(s, 'w', No.caseSensitive) == 6);
536 import std.typecons : No;
538 string s = "Hello World";
539 assert(indexOf(s, 'W', 4) == 6);
540 assert(indexOf(s, 'Z', 100) == -1);
541 assert(indexOf(s, 'w', 3, No.caseSensitive) == 6);
544 ptrdiff_t indexOf(Range)(auto ref Range s, in dchar c,
545 in CaseSensitive cs = Yes.caseSensitive)
546 if (isConvertibleToString!Range)
548 return indexOf!(StringTypeOf!Range)(s, c, cs);
551 ptrdiff_t indexOf(Range)(auto ref Range s, in dchar c, in size_t startIdx,
552 in CaseSensitive cs = Yes.caseSensitive)
553 if (isConvertibleToString!Range)
555 return indexOf!(StringTypeOf!Range)(s, c, startIdx, cs);
560 assert(testAliasedString!indexOf("std/string.d", '/'));
565 import std.conv : to;
566 import std.exception : assertCTFEable;
567 import std.traits : EnumMembers;
568 import std.utf : byChar, byWchar, byDchar;
572 foreach (S; AliasSeq!(string, wstring, dstring))
574 assert(indexOf(cast(S) null, cast(dchar)'a') == -1);
575 assert(indexOf(to!S("def"), cast(dchar)'a') == -1);
576 assert(indexOf(to!S("abba"), cast(dchar)'a') == 0);
577 assert(indexOf(to!S("def"), cast(dchar)'f') == 2);
579 assert(indexOf(to!S("def"), cast(dchar)'a', No.caseSensitive) == -1);
580 assert(indexOf(to!S("def"), cast(dchar)'a', No.caseSensitive) == -1);
581 assert(indexOf(to!S("Abba"), cast(dchar)'a', No.caseSensitive) == 0);
582 assert(indexOf(to!S("def"), cast(dchar)'F', No.caseSensitive) == 2);
583 assert(indexOf(to!S("ödef"), 'ö', No.caseSensitive) == 0);
585 S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
586 assert(indexOf("def", cast(char)'f', No.caseSensitive) == 2);
587 assert(indexOf(sPlts, cast(char)'P', No.caseSensitive) == 23);
588 assert(indexOf(sPlts, cast(char)'R', No.caseSensitive) == 2);
591 foreach (cs; EnumMembers!CaseSensitive)
593 assert(indexOf("hello\U00010143\u0100\U00010143", '\u0100', cs) == 9);
594 assert(indexOf("hello\U00010143\u0100\U00010143"w, '\u0100', cs) == 7);
595 assert(indexOf("hello\U00010143\u0100\U00010143"d, '\u0100', cs) == 6);
597 assert(indexOf("hello\U00010143\u0100\U00010143".byChar, '\u0100', cs) == 9);
598 assert(indexOf("hello\U00010143\u0100\U00010143".byWchar, '\u0100', cs) == 7);
599 assert(indexOf("hello\U00010143\u0100\U00010143".byDchar, '\u0100', cs) == 6);
601 assert(indexOf("hello\U000007FF\u0100\U00010143".byChar, 'l', cs) == 2);
602 assert(indexOf("hello\U000007FF\u0100\U00010143".byChar, '\u0100', cs) == 7);
603 assert(indexOf("hello\U0000EFFF\u0100\U00010143".byChar, '\u0100', cs) == 8);
605 assert(indexOf("hello\U00010100".byWchar, '\U00010100', cs) == 5);
606 assert(indexOf("hello\U00010100".byWchar, '\U00010101', cs) == -1);
609 char[10] fixedSizeArray = "0123456789";
610 assert(indexOf(fixedSizeArray, '2') == 2);
616 assert(testAliasedString!indexOf("std/string.d", '/', 3));
621 import std.conv : to;
622 import std.traits : EnumMembers;
623 import std.utf : byCodeUnit, byChar, byWchar;
625 assert("hello".byCodeUnit.indexOf(cast(dchar)'l', 1) == 2);
626 assert("hello".byWchar.indexOf(cast(dchar)'l', 1) == 2);
627 assert("hello".byWchar.indexOf(cast(dchar)'l', 6) == -1);
629 foreach (S; AliasSeq!(string, wstring, dstring))
631 assert(indexOf(cast(S) null, cast(dchar)'a', 1) == -1);
632 assert(indexOf(to!S("def"), cast(dchar)'a', 1) == -1);
633 assert(indexOf(to!S("abba"), cast(dchar)'a', 1) == 3);
634 assert(indexOf(to!S("def"), cast(dchar)'f', 1) == 2);
636 assert((to!S("def")).indexOf(cast(dchar)'a', 1,
637 No.caseSensitive) == -1);
638 assert(indexOf(to!S("def"), cast(dchar)'a', 1,
639 No.caseSensitive) == -1);
640 assert(indexOf(to!S("def"), cast(dchar)'a', 12,
641 No.caseSensitive) == -1);
642 assert(indexOf(to!S("AbbA"), cast(dchar)'a', 2,
643 No.caseSensitive) == 3);
644 assert(indexOf(to!S("def"), cast(dchar)'F', 2, No.caseSensitive) == 2);
646 S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
647 assert(indexOf("def", cast(char)'f', cast(uint) 2,
648 No.caseSensitive) == 2);
649 assert(indexOf(sPlts, cast(char)'P', 12, No.caseSensitive) == 23);
650 assert(indexOf(sPlts, cast(char)'R', cast(ulong) 1,
651 No.caseSensitive) == 2);
654 foreach (cs; EnumMembers!CaseSensitive)
656 assert(indexOf("hello\U00010143\u0100\U00010143", '\u0100', 2, cs)
658 assert(indexOf("hello\U00010143\u0100\U00010143"w, '\u0100', 3, cs)
660 assert(indexOf("hello\U00010143\u0100\U00010143"d, '\u0100', 6, cs)
666 Searches for substring in $(D s).
669 s = string or ForwardRange of characters to search in correct UTF format
670 sub = substring to search for
671 startIdx = the index into s to start searching from
672 cs = $(D Yes.caseSensitive) or $(D No.caseSensitive)
675 the index of the first occurrence of $(D sub) in $(D s) with
676 respect to the start index $(D startIdx). If $(D sub) is not found,
677 then $(D -1) is returned.
678 If the arguments are not valid UTF, the result will still
679 be in the range [-1 .. s.length], but will not be reliable otherwise.
680 If $(D sub) is found the value of the returned index is at least
684 If the sequence starting at $(D startIdx) does not represent a well
685 formed codepoint, then a $(REF UTFException, std,utf) may be thrown.
688 Does not work with case insensitive strings where the mapping of
689 tolower and toupper is not 1:1.
691 ptrdiff_t indexOf(Range, Char)(Range s, const(Char)[] sub,
692 in CaseSensitive cs = Yes.caseSensitive)
693 if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
696 alias Char1 = Unqual!(ElementEncodingType!Range);
698 static if (isSomeString!Range)
700 import std.algorithm.searching : find;
702 const(Char1)[] balance;
703 if (cs == Yes.caseSensitive)
705 balance = find(s, sub);
710 ((a, b) => toLower(a) == toLower(b))
713 return () @trusted { return balance.empty ? -1 : balance.ptr - s.ptr; } ();
720 return 0; // degenerate case
722 import std.utf : byDchar, codeLength;
723 auto subr = sub.byDchar; // decode sub[] by dchar's
724 dchar sub0 = subr.front; // cache first character of sub[]
727 // Special case for single character search
729 return indexOf(s, sub0, cs);
731 if (cs == No.caseSensitive)
732 sub0 = toLower(sub0);
734 /* Classic double nested loop search algorithm
736 ptrdiff_t index = 0; // count code unit index into s
737 for (auto sbydchar = s.byDchar(); !sbydchar.empty; sbydchar.popFront())
739 dchar c2 = sbydchar.front;
740 if (cs == No.caseSensitive)
744 auto s2 = sbydchar.save; // why s must be a forward range
745 foreach (c; subr.save)
750 if (cs == Yes.caseSensitive ? c != s2.front
751 : toLower(c) != toLower(s2.front)
758 index += codeLength!Char1(c2);
765 ptrdiff_t indexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub,
766 in size_t startIdx, in CaseSensitive cs = Yes.caseSensitive)
768 if (isSomeChar!Char1 && isSomeChar!Char2)
770 if (startIdx < s.length)
772 ptrdiff_t foundIdx = indexOf(s[startIdx .. $], sub, cs);
775 return foundIdx + cast(ptrdiff_t) startIdx;
784 import std.typecons : No;
786 string s = "Hello World";
787 assert(indexOf(s, "Wo", 4) == 6);
788 assert(indexOf(s, "Zo", 100) == -1);
789 assert(indexOf(s, "wo", 3, No.caseSensitive) == 6);
795 import std.typecons : No;
797 string s = "Hello World";
798 assert(indexOf(s, "Wo") == 6);
799 assert(indexOf(s, "Zo") == -1);
800 assert(indexOf(s, "wO", No.caseSensitive) == 6);
803 ptrdiff_t indexOf(Range, Char)(auto ref Range s, const(Char)[] sub,
804 in CaseSensitive cs = Yes.caseSensitive)
805 if (!(isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
807 is(StringTypeOf!Range))
809 return indexOf!(StringTypeOf!Range)(s, sub, cs);
814 assert(testAliasedString!indexOf("std/string.d", "string"));
819 import std.conv : to;
820 import std.exception : assertCTFEable;
821 import std.traits : EnumMembers;
825 foreach (S; AliasSeq!(string, wstring, dstring))
827 foreach (T; AliasSeq!(string, wstring, dstring))
828 (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
829 assert(indexOf(cast(S) null, to!T("a")) == -1);
830 assert(indexOf(to!S("def"), to!T("a")) == -1);
831 assert(indexOf(to!S("abba"), to!T("a")) == 0);
832 assert(indexOf(to!S("def"), to!T("f")) == 2);
833 assert(indexOf(to!S("dfefffg"), to!T("fff")) == 3);
834 assert(indexOf(to!S("dfeffgfff"), to!T("fff")) == 6);
836 assert(indexOf(to!S("dfeffgfff"), to!T("a"), No.caseSensitive) == -1);
837 assert(indexOf(to!S("def"), to!T("a"), No.caseSensitive) == -1);
838 assert(indexOf(to!S("abba"), to!T("a"), No.caseSensitive) == 0);
839 assert(indexOf(to!S("def"), to!T("f"), No.caseSensitive) == 2);
840 assert(indexOf(to!S("dfefffg"), to!T("fff"), No.caseSensitive) == 3);
841 assert(indexOf(to!S("dfeffgfff"), to!T("fff"), No.caseSensitive) == 6);
843 S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
844 S sMars = "Who\'s \'My Favorite Maritian?\'";
846 assert(indexOf(sMars, to!T("MY fAVe"), No.caseSensitive) == -1);
847 assert(indexOf(sMars, to!T("mY fAVOriTe"), No.caseSensitive) == 7);
848 assert(indexOf(sPlts, to!T("mArS:"), No.caseSensitive) == 0);
849 assert(indexOf(sPlts, to!T("rOcK"), No.caseSensitive) == 17);
850 assert(indexOf(sPlts, to!T("Un."), No.caseSensitive) == 41);
851 assert(indexOf(sPlts, to!T(sPlts), No.caseSensitive) == 0);
853 assert(indexOf("\u0100", to!T("\u0100"), No.caseSensitive) == 0);
855 // Thanks to Carlos Santander B. and zwang
856 assert(indexOf("sus mejores cortesanos. Se embarcaron en el puerto de Dubai y",
857 to!T("page-break-before"), No.caseSensitive) == -1);
860 foreach (cs; EnumMembers!CaseSensitive)
862 assert(indexOf("hello\U00010143\u0100\U00010143", to!S("\u0100"), cs) == 9);
863 assert(indexOf("hello\U00010143\u0100\U00010143"w, to!S("\u0100"), cs) == 7);
864 assert(indexOf("hello\U00010143\u0100\U00010143"d, to!S("\u0100"), cs) == 6);
870 @safe pure @nogc nothrow
873 import std.traits : EnumMembers;
874 import std.utf : byWchar;
876 foreach (cs; EnumMembers!CaseSensitive)
878 assert(indexOf("".byWchar, "", cs) == -1);
879 assert(indexOf("hello".byWchar, "", cs) == 0);
880 assert(indexOf("hello".byWchar, "l", cs) == 2);
881 assert(indexOf("heLLo".byWchar, "LL", cs) == 2);
882 assert(indexOf("hello".byWchar, "lox", cs) == -1);
883 assert(indexOf("hello".byWchar, "betty", cs) == -1);
884 assert(indexOf("hello\U00010143\u0100*\U00010143".byWchar, "\u0100*", cs) == 7);
890 import std.conv : to;
891 import std.traits : EnumMembers;
893 foreach (S; AliasSeq!(string, wstring, dstring))
895 foreach (T; AliasSeq!(string, wstring, dstring))
896 (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
897 assert(indexOf(cast(S) null, to!T("a"), 1337) == -1);
898 assert(indexOf(to!S("def"), to!T("a"), 0) == -1);
899 assert(indexOf(to!S("abba"), to!T("a"), 2) == 3);
900 assert(indexOf(to!S("def"), to!T("f"), 1) == 2);
901 assert(indexOf(to!S("dfefffg"), to!T("fff"), 1) == 3);
902 assert(indexOf(to!S("dfeffgfff"), to!T("fff"), 5) == 6);
904 assert(indexOf(to!S("dfeffgfff"), to!T("a"), 1, No.caseSensitive) == -1);
905 assert(indexOf(to!S("def"), to!T("a"), 2, No.caseSensitive) == -1);
906 assert(indexOf(to!S("abba"), to!T("a"), 3, No.caseSensitive) == 3);
907 assert(indexOf(to!S("def"), to!T("f"), 1, No.caseSensitive) == 2);
908 assert(indexOf(to!S("dfefffg"), to!T("fff"), 2, No.caseSensitive) == 3);
909 assert(indexOf(to!S("dfeffgfff"), to!T("fff"), 4, No.caseSensitive) == 6);
910 assert(indexOf(to!S("dfeffgffföä"), to!T("öä"), 9, No.caseSensitive) == 9,
911 to!string(indexOf(to!S("dfeffgffföä"), to!T("öä"), 9, No.caseSensitive))
912 ~ " " ~ S.stringof ~ " " ~ T.stringof);
914 S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
915 S sMars = "Who\'s \'My Favorite Maritian?\'";
917 assert(indexOf(sMars, to!T("MY fAVe"), 10,
918 No.caseSensitive) == -1);
919 assert(indexOf(sMars, to!T("mY fAVOriTe"), 4, No.caseSensitive) == 7);
920 assert(indexOf(sPlts, to!T("mArS:"), 0, No.caseSensitive) == 0);
921 assert(indexOf(sPlts, to!T("rOcK"), 12, No.caseSensitive) == 17);
922 assert(indexOf(sPlts, to!T("Un."), 32, No.caseSensitive) == 41);
923 assert(indexOf(sPlts, to!T(sPlts), 0, No.caseSensitive) == 0);
925 assert(indexOf("\u0100", to!T("\u0100"), 0, No.caseSensitive) == 0);
927 // Thanks to Carlos Santander B. and zwang
928 assert(indexOf("sus mejores cortesanos. Se embarcaron en el puerto de Dubai y",
929 to!T("page-break-before"), 10, No.caseSensitive) == -1);
931 // In order for indexOf with and without index to be consistent
932 assert(indexOf(to!S(""), to!T("")) == indexOf(to!S(""), to!T(""), 0));
935 foreach (cs; EnumMembers!CaseSensitive)
937 assert(indexOf("hello\U00010143\u0100\U00010143", to!S("\u0100"),
939 assert(indexOf("hello\U00010143\u0100\U00010143"w, to!S("\u0100"),
941 assert(indexOf("hello\U00010143\u0100\U00010143"d, to!S("\u0100"),
950 c = character to search for
951 startIdx = the index into s to start searching from
952 cs = $(D Yes.caseSensitive) or $(D No.caseSensitive)
955 The index of the last occurrence of $(D c) in $(D s). If $(D c) is not
956 found, then $(D -1) is returned. The $(D startIdx) slices $(D s) in
957 the following way $(D s[0 .. startIdx]). $(D startIdx) represents a
958 codeunit index in $(D s).
961 If the sequence ending at $(D startIdx) does not represent a well
962 formed codepoint, then a $(REF UTFException, std,utf) may be thrown.
964 $(D cs) indicates whether the comparisons are case sensitive.
966 ptrdiff_t lastIndexOf(Char)(const(Char)[] s, in dchar c,
967 in CaseSensitive cs = Yes.caseSensitive) @safe pure
970 static import std.ascii, std.uni;
971 import std.utf : canSearchInCodeUnits;
972 if (cs == Yes.caseSensitive)
974 if (canSearchInCodeUnits!Char(c))
976 foreach_reverse (i, it; s)
986 foreach_reverse (i, dchar it; s)
997 if (std.ascii.isASCII(c))
999 immutable c1 = std.ascii.toLower(c);
1001 foreach_reverse (i, it; s)
1003 immutable c2 = std.ascii.toLower(it);
1012 immutable c1 = std.uni.toLower(c);
1014 foreach_reverse (i, dchar it; s)
1016 immutable c2 = std.uni.toLower(it);
1029 ptrdiff_t lastIndexOf(Char)(const(Char)[] s, in dchar c, in size_t startIdx,
1030 in CaseSensitive cs = Yes.caseSensitive) @safe pure
1031 if (isSomeChar!Char)
1033 if (startIdx <= s.length)
1035 return lastIndexOf(s[0u .. startIdx], c, cs);
1044 import std.typecons : No;
1046 string s = "Hello World";
1047 assert(lastIndexOf(s, 'l') == 9);
1048 assert(lastIndexOf(s, 'Z') == -1);
1049 assert(lastIndexOf(s, 'L', No.caseSensitive) == 9);
1055 import std.typecons : No;
1057 string s = "Hello World";
1058 assert(lastIndexOf(s, 'l', 4) == 3);
1059 assert(lastIndexOf(s, 'Z', 1337) == -1);
1060 assert(lastIndexOf(s, 'L', 7, No.caseSensitive) == 3);
1065 import std.conv : to;
1066 import std.exception : assertCTFEable;
1067 import std.traits : EnumMembers;
1071 foreach (S; AliasSeq!(string, wstring, dstring))
1073 assert(lastIndexOf(cast(S) null, 'a') == -1);
1074 assert(lastIndexOf(to!S("def"), 'a') == -1);
1075 assert(lastIndexOf(to!S("abba"), 'a') == 3);
1076 assert(lastIndexOf(to!S("def"), 'f') == 2);
1077 assert(lastIndexOf(to!S("ödef"), 'ö') == 0);
1079 assert(lastIndexOf(cast(S) null, 'a', No.caseSensitive) == -1);
1080 assert(lastIndexOf(to!S("def"), 'a', No.caseSensitive) == -1);
1081 assert(lastIndexOf(to!S("AbbA"), 'a', No.caseSensitive) == 3);
1082 assert(lastIndexOf(to!S("def"), 'F', No.caseSensitive) == 2);
1083 assert(lastIndexOf(to!S("ödef"), 'ö', No.caseSensitive) == 0);
1084 assert(lastIndexOf(to!S("i\u0100def"), to!dchar("\u0100"),
1085 No.caseSensitive) == 1);
1087 S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
1089 assert(lastIndexOf(to!S("def"), 'f', No.caseSensitive) == 2);
1090 assert(lastIndexOf(sPlts, 'M', No.caseSensitive) == 34);
1091 assert(lastIndexOf(sPlts, 'S', No.caseSensitive) == 40);
1094 foreach (cs; EnumMembers!CaseSensitive)
1096 assert(lastIndexOf("\U00010143\u0100\U00010143hello", '\u0100', cs) == 4);
1097 assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, '\u0100', cs) == 2);
1098 assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, '\u0100', cs) == 1);
1105 import std.conv : to;
1106 import std.traits : EnumMembers;
1108 foreach (S; AliasSeq!(string, wstring, dstring))
1110 assert(lastIndexOf(cast(S) null, 'a') == -1);
1111 assert(lastIndexOf(to!S("def"), 'a') == -1);
1112 assert(lastIndexOf(to!S("abba"), 'a', 3) == 0);
1113 assert(lastIndexOf(to!S("deff"), 'f', 3) == 2);
1115 assert(lastIndexOf(cast(S) null, 'a', No.caseSensitive) == -1);
1116 assert(lastIndexOf(to!S("def"), 'a', No.caseSensitive) == -1);
1117 assert(lastIndexOf(to!S("AbbAa"), 'a', to!ushort(4), No.caseSensitive) == 3,
1118 to!string(lastIndexOf(to!S("AbbAa"), 'a', 4, No.caseSensitive)));
1119 assert(lastIndexOf(to!S("def"), 'F', 3, No.caseSensitive) == 2);
1121 S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
1123 assert(lastIndexOf(to!S("def"), 'f', 4, No.caseSensitive) == -1);
1124 assert(lastIndexOf(sPlts, 'M', sPlts.length -2, No.caseSensitive) == 34);
1125 assert(lastIndexOf(sPlts, 'S', sPlts.length -2, No.caseSensitive) == 40);
1128 foreach (cs; EnumMembers!CaseSensitive)
1130 assert(lastIndexOf("\U00010143\u0100\U00010143hello", '\u0100', cs) == 4);
1131 assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, '\u0100', cs) == 2);
1132 assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, '\u0100', cs) == 1);
1138 s = string to search
1139 sub = substring to search for
1140 startIdx = the index into s to start searching from
1141 cs = $(D Yes.caseSensitive) or $(D No.caseSensitive)
1144 the index of the last occurrence of $(D sub) in $(D s). If $(D sub) is
1145 not found, then $(D -1) is returned. The $(D startIdx) slices $(D s)
1146 in the following way $(D s[0 .. startIdx]). $(D startIdx) represents a
1147 codeunit index in $(D s).
1150 If the sequence ending at $(D startIdx) does not represent a well
1151 formed codepoint, then a $(REF UTFException, std,utf) may be thrown.
1153 $(D cs) indicates whether the comparisons are case sensitive.
1155 ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub,
1156 in CaseSensitive cs = Yes.caseSensitive) @safe pure
1157 if (isSomeChar!Char1 && isSomeChar!Char2)
1159 import std.algorithm.searching : endsWith;
1160 import std.conv : to;
1161 import std.range.primitives : walkLength;
1162 static import std.uni;
1163 import std.utf : strideBack;
1167 if (walkLength(sub) == 1)
1168 return lastIndexOf(s, sub.front, cs);
1170 if (cs == Yes.caseSensitive)
1172 static if (is(Unqual!Char1 == Unqual!Char2))
1174 import core.stdc.string : memcmp;
1176 immutable c = sub[0];
1178 for (ptrdiff_t i = s.length - sub.length; i >= 0; --i)
1184 foreach (j; 1 .. sub.length)
1186 if (s[i + j] != sub[j])
1193 auto trustedMemcmp(in void* s1, in void* s2, size_t n) @trusted
1195 return memcmp(s1, s2, n);
1197 if (trustedMemcmp(&s[i + 1], &sub[1],
1198 (sub.length - 1) * Char1.sizeof) == 0)
1206 for (size_t i = s.length; !s.empty;)
1208 if (s.endsWith(sub))
1209 return cast(ptrdiff_t) i - to!(const(Char1)[])(sub).length;
1211 i -= strideBack(s, i);
1218 for (size_t i = s.length; !s.empty;)
1220 if (endsWith!((a, b) => std.uni.toLower(a) == std.uni.toLower(b))
1223 return cast(ptrdiff_t) i - to!(const(Char1)[])(sub).length;
1226 i -= strideBack(s, i);
1235 ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub,
1236 in size_t startIdx, in CaseSensitive cs = Yes.caseSensitive) @safe pure
1237 if (isSomeChar!Char1 && isSomeChar!Char2)
1239 if (startIdx <= s.length)
1241 return lastIndexOf(s[0u .. startIdx], sub, cs);
1250 import std.typecons : No;
1252 string s = "Hello World";
1253 assert(lastIndexOf(s, "ll") == 2);
1254 assert(lastIndexOf(s, "Zo") == -1);
1255 assert(lastIndexOf(s, "lL", No.caseSensitive) == 2);
1261 import std.typecons : No;
1263 string s = "Hello World";
1264 assert(lastIndexOf(s, "ll", 4) == 2);
1265 assert(lastIndexOf(s, "Zo", 128) == -1);
1266 assert(lastIndexOf(s, "lL", 3, No.caseSensitive) == -1);
1271 import std.conv : to;
1273 foreach (S; AliasSeq!(string, wstring, dstring))
1275 auto r = to!S("").lastIndexOf("hello");
1276 assert(r == -1, to!string(r));
1278 r = to!S("hello").lastIndexOf("");
1279 assert(r == -1, to!string(r));
1281 r = to!S("").lastIndexOf("");
1282 assert(r == -1, to!string(r));
1288 import std.conv : to;
1289 import std.exception : assertCTFEable;
1290 import std.traits : EnumMembers;
1294 foreach (S; AliasSeq!(string, wstring, dstring))
1296 foreach (T; AliasSeq!(string, wstring, dstring))
1297 (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
1298 enum typeStr = S.stringof ~ " " ~ T.stringof;
1300 assert(lastIndexOf(cast(S) null, to!T("a")) == -1, typeStr);
1301 assert(lastIndexOf(to!S("abcdefcdef"), to!T("c")) == 6, typeStr);
1302 assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd")) == 6, typeStr);
1303 assert(lastIndexOf(to!S("abcdefcdef"), to!T("ef")) == 8, typeStr);
1304 assert(lastIndexOf(to!S("abcdefCdef"), to!T("c")) == 2, typeStr);
1305 assert(lastIndexOf(to!S("abcdefCdef"), to!T("cd")) == 2, typeStr);
1306 assert(lastIndexOf(to!S("abcdefcdef"), to!T("x")) == -1, typeStr);
1307 assert(lastIndexOf(to!S("abcdefcdef"), to!T("xy")) == -1, typeStr);
1308 assert(lastIndexOf(to!S("abcdefcdef"), to!T("")) == -1, typeStr);
1309 assert(lastIndexOf(to!S("öabcdefcdef"), to!T("ö")) == 0, typeStr);
1311 assert(lastIndexOf(cast(S) null, to!T("a"), No.caseSensitive) == -1, typeStr);
1312 assert(lastIndexOf(to!S("abcdefCdef"), to!T("c"), No.caseSensitive) == 6, typeStr);
1313 assert(lastIndexOf(to!S("abcdefCdef"), to!T("cD"), No.caseSensitive) == 6, typeStr);
1314 assert(lastIndexOf(to!S("abcdefcdef"), to!T("x"), No.caseSensitive) == -1, typeStr);
1315 assert(lastIndexOf(to!S("abcdefcdef"), to!T("xy"), No.caseSensitive) == -1, typeStr);
1316 assert(lastIndexOf(to!S("abcdefcdef"), to!T(""), No.caseSensitive) == -1, typeStr);
1317 assert(lastIndexOf(to!S("öabcdefcdef"), to!T("ö"), No.caseSensitive) == 0, typeStr);
1319 assert(lastIndexOf(to!S("abcdefcdef"), to!T("c"), No.caseSensitive) == 6, typeStr);
1320 assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd"), No.caseSensitive) == 6, typeStr);
1321 assert(lastIndexOf(to!S("abcdefcdef"), to!T("def"), No.caseSensitive) == 7, typeStr);
1323 assert(lastIndexOf(to!S("ödfeffgfff"), to!T("ö"), Yes.caseSensitive) == 0);
1325 S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
1326 S sMars = "Who\'s \'My Favorite Maritian?\'";
1328 assert(lastIndexOf(sMars, to!T("RiTE maR"), No.caseSensitive) == 14, typeStr);
1329 assert(lastIndexOf(sPlts, to!T("FOuRTh"), No.caseSensitive) == 10, typeStr);
1330 assert(lastIndexOf(sMars, to!T("whO\'s \'MY"), No.caseSensitive) == 0, typeStr);
1331 assert(lastIndexOf(sMars, to!T(sMars), No.caseSensitive) == 0, typeStr);
1334 foreach (cs; EnumMembers!CaseSensitive)
1336 enum csString = to!string(cs);
1338 assert(lastIndexOf("\U00010143\u0100\U00010143hello", to!S("\u0100"), cs) == 4, csString);
1339 assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, to!S("\u0100"), cs) == 2, csString);
1340 assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, to!S("\u0100"), cs) == 1, csString);
1346 @safe pure unittest // issue13529
1348 import std.conv : to;
1349 foreach (S; AliasSeq!(string, wstring, dstring))
1351 foreach (T; AliasSeq!(string, wstring, dstring))
1353 enum typeStr = S.stringof ~ " " ~ T.stringof;
1354 auto idx = lastIndexOf(to!T("Hällö Wörldö ö"),to!S("ö ö"));
1355 assert(idx != -1, to!string(idx) ~ " " ~ typeStr);
1357 idx = lastIndexOf(to!T("Hällö Wörldö ö"),to!S("ö öd"));
1358 assert(idx == -1, to!string(idx) ~ " " ~ typeStr);
1365 import std.conv : to;
1366 import std.traits : EnumMembers;
1368 foreach (S; AliasSeq!(string, wstring, dstring))
1370 foreach (T; AliasSeq!(string, wstring, dstring))
1371 (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
1372 enum typeStr = S.stringof ~ " " ~ T.stringof;
1374 assert(lastIndexOf(cast(S) null, to!T("a")) == -1, typeStr);
1375 assert(lastIndexOf(to!S("abcdefcdef"), to!T("c"), 5) == 2, typeStr);
1376 assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd"), 3) == -1, typeStr);
1377 assert(lastIndexOf(to!S("abcdefcdef"), to!T("ef"), 6) == 4, typeStr ~
1378 format(" %u", lastIndexOf(to!S("abcdefcdef"), to!T("ef"), 6)));
1379 assert(lastIndexOf(to!S("abcdefCdef"), to!T("c"), 5) == 2, typeStr);
1380 assert(lastIndexOf(to!S("abcdefCdef"), to!T("cd"), 3) == -1, typeStr);
1381 assert(lastIndexOf(to!S("abcdefcdefx"), to!T("x"), 1) == -1, typeStr);
1382 assert(lastIndexOf(to!S("abcdefcdefxy"), to!T("xy"), 6) == -1, typeStr);
1383 assert(lastIndexOf(to!S("abcdefcdef"), to!T(""), 8) == -1, typeStr);
1384 assert(lastIndexOf(to!S("öafö"), to!T("ö"), 3) == 0, typeStr ~
1385 to!string(lastIndexOf(to!S("öafö"), to!T("ö"), 3))); //BUG 10472
1387 assert(lastIndexOf(cast(S) null, to!T("a"), 1, No.caseSensitive) == -1, typeStr);
1388 assert(lastIndexOf(to!S("abcdefCdef"), to!T("c"), 5, No.caseSensitive) == 2, typeStr);
1389 assert(lastIndexOf(to!S("abcdefCdef"), to!T("cD"), 4, No.caseSensitive) == 2, typeStr ~
1390 " " ~ to!string(lastIndexOf(to!S("abcdefCdef"), to!T("cD"), 3, No.caseSensitive)));
1391 assert(lastIndexOf(to!S("abcdefcdef"), to!T("x"),3 , No.caseSensitive) == -1, typeStr);
1392 assert(lastIndexOf(to!S("abcdefcdefXY"), to!T("xy"), 4, No.caseSensitive) == -1, typeStr);
1393 assert(lastIndexOf(to!S("abcdefcdef"), to!T(""), 7, No.caseSensitive) == -1, typeStr);
1395 assert(lastIndexOf(to!S("abcdefcdef"), to!T("c"), 4, No.caseSensitive) == 2, typeStr);
1396 assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd"), 4, No.caseSensitive) == 2, typeStr);
1397 assert(lastIndexOf(to!S("abcdefcdef"), to!T("def"), 6, No.caseSensitive) == 3, typeStr);
1398 assert(lastIndexOf(to!S(""), to!T(""), 0) == lastIndexOf(to!S(""), to!T("")), typeStr);
1401 foreach (cs; EnumMembers!CaseSensitive)
1403 enum csString = to!string(cs);
1405 assert(lastIndexOf("\U00010143\u0100\U00010143hello", to!S("\u0100"), 6, cs) == 4, csString);
1406 assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, to!S("\u0100"), 6, cs) == 2, csString);
1407 assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, to!S("\u0100"), 3, cs) == 1, csString);
1412 private ptrdiff_t indexOfAnyNeitherImpl(bool forward, bool any, Char, Char2)(
1413 const(Char)[] haystack, const(Char2)[] needles,
1414 in CaseSensitive cs = Yes.caseSensitive) @safe pure
1415 if (isSomeChar!Char && isSomeChar!Char2)
1417 import std.algorithm.searching : canFind, findAmong;
1418 if (cs == Yes.caseSensitive)
1424 size_t n = haystack.findAmong(needles).length;
1425 return n ? haystack.length - n : -1;
1429 foreach (idx, dchar hay; haystack)
1431 if (!canFind(needles, hay))
1442 import std.range : retro;
1443 import std.utf : strideBack;
1444 size_t n = haystack.retro.findAmong(needles).source.length;
1447 return n - haystack.strideBack(n);
1452 foreach_reverse (idx, dchar hay; haystack)
1454 if (!canFind(needles, hay))
1464 import std.range.primitives : walkLength;
1465 if (needles.length <= 16 && needles.walkLength(17))
1468 dchar[16] scratch = void;
1469 foreach ( dchar c; needles)
1471 scratch[si++] = toLower(c);
1476 foreach (i, dchar c; haystack)
1478 if (canFind(scratch[0 .. si], toLower(c)) == any)
1486 foreach_reverse (i, dchar c; haystack)
1488 if (canFind(scratch[0 .. si], toLower(c)) == any)
1497 static bool f(dchar a, dchar b)
1499 return toLower(a) == b;
1504 foreach (i, dchar c; haystack)
1506 if (canFind!f(needles, toLower(c)) == any)
1514 foreach_reverse (i, dchar c; haystack)
1516 if (canFind!f(needles, toLower(c)) == any)
1529 Returns the index of the first occurrence of any of the elements in $(D
1530 needles) in $(D haystack). If no element of $(D needles) is found,
1531 then $(D -1) is returned. The $(D startIdx) slices $(D haystack) in the
1532 following way $(D haystack[startIdx .. $]). $(D startIdx) represents a
1533 codeunit index in $(D haystack). If the sequence ending at $(D startIdx)
1534 does not represent a well formed codepoint, then a $(REF UTFException, std,utf)
1538 haystack = String to search for needles in.
1539 needles = Strings to search for in haystack.
1540 startIdx = slices haystack like this $(D haystack[startIdx .. $]). If
1541 the startIdx is greater equal the length of haystack the functions
1543 cs = Indicates whether the comparisons are case sensitive.
1545 ptrdiff_t indexOfAny(Char,Char2)(const(Char)[] haystack, const(Char2)[] needles,
1546 in CaseSensitive cs = Yes.caseSensitive) @safe pure
1547 if (isSomeChar!Char && isSomeChar!Char2)
1549 return indexOfAnyNeitherImpl!(true, true)(haystack, needles, cs);
1553 ptrdiff_t indexOfAny(Char,Char2)(const(Char)[] haystack, const(Char2)[] needles,
1554 in size_t startIdx, in CaseSensitive cs = Yes.caseSensitive) @safe pure
1555 if (isSomeChar!Char && isSomeChar!Char2)
1557 if (startIdx < haystack.length)
1559 ptrdiff_t foundIdx = indexOfAny(haystack[startIdx .. $], needles, cs);
1562 return foundIdx + cast(ptrdiff_t) startIdx;
1572 import std.conv : to;
1574 ptrdiff_t i = "helloWorld".indexOfAny("Wr");
1576 i = "öällo world".indexOfAny("lo ");
1577 assert(i == 4, to!string(i));
1583 import std.conv : to;
1585 ptrdiff_t i = "helloWorld".indexOfAny("Wr", 4);
1588 i = "Foo öällo world".indexOfAny("lh", 3);
1589 assert(i == 8, to!string(i));
1594 import std.conv : to;
1596 foreach (S; AliasSeq!(string, wstring, dstring))
1598 auto r = to!S("").indexOfAny("hello");
1599 assert(r == -1, to!string(r));
1601 r = to!S("hello").indexOfAny("");
1602 assert(r == -1, to!string(r));
1604 r = to!S("").indexOfAny("");
1605 assert(r == -1, to!string(r));
1611 import std.conv : to;
1612 import std.exception : assertCTFEable;
1616 foreach (S; AliasSeq!(string, wstring, dstring))
1618 foreach (T; AliasSeq!(string, wstring, dstring))
1619 (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
1620 assert(indexOfAny(cast(S) null, to!T("a")) == -1);
1621 assert(indexOfAny(to!S("def"), to!T("rsa")) == -1);
1622 assert(indexOfAny(to!S("abba"), to!T("a")) == 0);
1623 assert(indexOfAny(to!S("def"), to!T("f")) == 2);
1624 assert(indexOfAny(to!S("dfefffg"), to!T("fgh")) == 1);
1625 assert(indexOfAny(to!S("dfeffgfff"), to!T("feg")) == 1);
1627 assert(indexOfAny(to!S("zfeffgfff"), to!T("ACDC"),
1628 No.caseSensitive) == -1);
1629 assert(indexOfAny(to!S("def"), to!T("MI6"),
1630 No.caseSensitive) == -1);
1631 assert(indexOfAny(to!S("abba"), to!T("DEA"),
1632 No.caseSensitive) == 0);
1633 assert(indexOfAny(to!S("def"), to!T("FBI"), No.caseSensitive) == 2);
1634 assert(indexOfAny(to!S("dfefffg"), to!T("NSA"), No.caseSensitive)
1636 assert(indexOfAny(to!S("dfeffgfff"), to!T("BND"),
1637 No.caseSensitive) == 0);
1638 assert(indexOfAny(to!S("dfeffgfff"), to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"),
1639 No.caseSensitive) == 0);
1641 assert(indexOfAny("\u0100", to!T("\u0100"), No.caseSensitive) == 0);
1650 import std.conv : to;
1651 import std.traits : EnumMembers;
1653 foreach (S; AliasSeq!(string, wstring, dstring))
1655 foreach (T; AliasSeq!(string, wstring, dstring))
1656 (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
1657 assert(indexOfAny(cast(S) null, to!T("a"), 1337) == -1);
1658 assert(indexOfAny(to!S("def"), to!T("AaF"), 0) == -1);
1659 assert(indexOfAny(to!S("abba"), to!T("NSa"), 2) == 3);
1660 assert(indexOfAny(to!S("def"), to!T("fbi"), 1) == 2);
1661 assert(indexOfAny(to!S("dfefffg"), to!T("foo"), 2) == 3);
1662 assert(indexOfAny(to!S("dfeffgfff"), to!T("fsb"), 5) == 6);
1664 assert(indexOfAny(to!S("dfeffgfff"), to!T("NDS"), 1,
1665 No.caseSensitive) == -1);
1666 assert(indexOfAny(to!S("def"), to!T("DRS"), 2,
1667 No.caseSensitive) == -1);
1668 assert(indexOfAny(to!S("abba"), to!T("SI"), 3,
1669 No.caseSensitive) == -1);
1670 assert(indexOfAny(to!S("deO"), to!T("ASIO"), 1,
1671 No.caseSensitive) == 2);
1672 assert(indexOfAny(to!S("dfefffg"), to!T("fbh"), 2,
1673 No.caseSensitive) == 3);
1674 assert(indexOfAny(to!S("dfeffgfff"), to!T("fEe"), 4,
1675 No.caseSensitive) == 4);
1676 assert(indexOfAny(to!S("dfeffgffföä"), to!T("föä"), 9,
1677 No.caseSensitive) == 9);
1679 assert(indexOfAny("\u0100", to!T("\u0100"), 0,
1680 No.caseSensitive) == 0);
1683 foreach (cs; EnumMembers!CaseSensitive)
1685 assert(indexOfAny("hello\U00010143\u0100\U00010143",
1686 to!S("e\u0100"), 3, cs) == 9);
1687 assert(indexOfAny("hello\U00010143\u0100\U00010143"w,
1688 to!S("h\u0100"), 3, cs) == 7);
1689 assert(indexOfAny("hello\U00010143\u0100\U00010143"d,
1690 to!S("l\u0100"), 5, cs) == 6);
1696 Returns the index of the last occurrence of any of the elements in $(D
1697 needles) in $(D haystack). If no element of $(D needles) is found,
1698 then $(D -1) is returned. The $(D stopIdx) slices $(D haystack) in the
1699 following way $(D s[0 .. stopIdx]). $(D stopIdx) represents a codeunit
1700 index in $(D haystack). If the sequence ending at $(D startIdx) does not
1701 represent a well formed codepoint, then a $(REF UTFException, std,utf) may be
1705 haystack = String to search for needles in.
1706 needles = Strings to search for in haystack.
1707 stopIdx = slices haystack like this $(D haystack[0 .. stopIdx]). If
1708 the stopIdx is greater equal the length of haystack the functions
1710 cs = Indicates whether the comparisons are case sensitive.
1712 ptrdiff_t lastIndexOfAny(Char,Char2)(const(Char)[] haystack,
1713 const(Char2)[] needles, in CaseSensitive cs = Yes.caseSensitive)
1715 if (isSomeChar!Char && isSomeChar!Char2)
1717 return indexOfAnyNeitherImpl!(false, true)(haystack, needles, cs);
1721 ptrdiff_t lastIndexOfAny(Char,Char2)(const(Char)[] haystack,
1722 const(Char2)[] needles, in size_t stopIdx,
1723 in CaseSensitive cs = Yes.caseSensitive) @safe pure
1724 if (isSomeChar!Char && isSomeChar!Char2)
1726 if (stopIdx <= haystack.length)
1728 return lastIndexOfAny(haystack[0u .. stopIdx], needles, cs);
1737 ptrdiff_t i = "helloWorld".lastIndexOfAny("Wlo");
1740 i = "Foo öäöllo world".lastIndexOfAny("öF");
1747 import std.conv : to;
1749 ptrdiff_t i = "helloWorld".lastIndexOfAny("Wlo", 4);
1752 i = "Foo öäöllo world".lastIndexOfAny("öF", 3);
1758 import std.conv : to;
1760 foreach (S; AliasSeq!(string, wstring, dstring))
1762 auto r = to!S("").lastIndexOfAny("hello");
1763 assert(r == -1, to!string(r));
1765 r = to!S("hello").lastIndexOfAny("");
1766 assert(r == -1, to!string(r));
1768 r = to!S("").lastIndexOfAny("");
1769 assert(r == -1, to!string(r));
1775 import std.conv : to;
1776 import std.exception : assertCTFEable;
1780 foreach (S; AliasSeq!(string, wstring, dstring))
1782 foreach (T; AliasSeq!(string, wstring, dstring))
1783 (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
1784 assert(lastIndexOfAny(cast(S) null, to!T("a")) == -1);
1785 assert(lastIndexOfAny(to!S("def"), to!T("rsa")) == -1);
1786 assert(lastIndexOfAny(to!S("abba"), to!T("a")) == 3);
1787 assert(lastIndexOfAny(to!S("def"), to!T("f")) == 2);
1788 assert(lastIndexOfAny(to!S("dfefffg"), to!T("fgh")) == 6);
1790 ptrdiff_t oeIdx = 9;
1791 if (is(S == wstring) || is(S == dstring))
1796 auto foundOeIdx = lastIndexOfAny(to!S("dfeffgföf"), to!T("feg"));
1797 assert(foundOeIdx == oeIdx, to!string(foundOeIdx));
1799 assert(lastIndexOfAny(to!S("zfeffgfff"), to!T("ACDC"),
1800 No.caseSensitive) == -1);
1801 assert(lastIndexOfAny(to!S("def"), to!T("MI6"),
1802 No.caseSensitive) == -1);
1803 assert(lastIndexOfAny(to!S("abba"), to!T("DEA"),
1804 No.caseSensitive) == 3);
1805 assert(lastIndexOfAny(to!S("def"), to!T("FBI"),
1806 No.caseSensitive) == 2);
1807 assert(lastIndexOfAny(to!S("dfefffg"), to!T("NSA"),
1808 No.caseSensitive) == -1);
1811 if (is(S == wstring) || is(S == dstring))
1815 assert(lastIndexOfAny(to!S("ödfeffgfff"), to!T("BND"),
1816 No.caseSensitive) == oeIdx);
1818 assert(lastIndexOfAny("\u0100", to!T("\u0100"),
1819 No.caseSensitive) == 0);
1828 import std.conv : to;
1829 import std.exception : assertCTFEable;
1833 foreach (S; AliasSeq!(string, wstring, dstring))
1835 foreach (T; AliasSeq!(string, wstring, dstring))
1836 (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
1837 enum typeStr = S.stringof ~ " " ~ T.stringof;
1839 assert(lastIndexOfAny(cast(S) null, to!T("a"), 1337) == -1,
1841 assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("c"), 7) == 6,
1843 assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("cd"), 5) == 3,
1845 assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("ef"), 6) == 5,
1847 assert(lastIndexOfAny(to!S("abcdefCdef"), to!T("c"), 8) == 2,
1849 assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("x"), 7) == -1,
1851 assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("xy"), 4) == -1,
1853 assert(lastIndexOfAny(to!S("öabcdefcdef"), to!T("ö"), 2) == 0,
1856 assert(lastIndexOfAny(cast(S) null, to!T("a"), 1337,
1857 No.caseSensitive) == -1, typeStr);
1858 assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("C"), 7,
1859 No.caseSensitive) == 6, typeStr);
1860 assert(lastIndexOfAny(to!S("ABCDEFCDEF"), to!T("cd"), 5,
1861 No.caseSensitive) == 3, typeStr);
1862 assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("EF"), 6,
1863 No.caseSensitive) == 5, typeStr);
1864 assert(lastIndexOfAny(to!S("ABCDEFcDEF"), to!T("C"), 8,
1865 No.caseSensitive) == 6, typeStr);
1866 assert(lastIndexOfAny(to!S("ABCDEFCDEF"), to!T("x"), 7,
1867 No.caseSensitive) == -1, typeStr);
1868 assert(lastIndexOfAny(to!S("abCdefcdef"), to!T("XY"), 4,
1869 No.caseSensitive) == -1, typeStr);
1870 assert(lastIndexOfAny(to!S("ÖABCDEFCDEF"), to!T("ö"), 2,
1871 No.caseSensitive) == 0, typeStr);
1879 Returns the index of the first occurrence of any character not an elements
1880 in $(D needles) in $(D haystack). If all element of $(D haystack) are
1881 element of $(D needles) $(D -1) is returned.
1884 haystack = String to search for needles in.
1885 needles = Strings to search for in haystack.
1886 startIdx = slices haystack like this $(D haystack[startIdx .. $]). If
1887 the startIdx is greater equal the length of haystack the functions
1889 cs = Indicates whether the comparisons are case sensitive.
1891 ptrdiff_t indexOfNeither(Char,Char2)(const(Char)[] haystack,
1892 const(Char2)[] needles, in CaseSensitive cs = Yes.caseSensitive)
1894 if (isSomeChar!Char && isSomeChar!Char2)
1896 return indexOfAnyNeitherImpl!(true, false)(haystack, needles, cs);
1900 ptrdiff_t indexOfNeither(Char,Char2)(const(Char)[] haystack,
1901 const(Char2)[] needles, in size_t startIdx,
1902 in CaseSensitive cs = Yes.caseSensitive)
1904 if (isSomeChar!Char && isSomeChar!Char2)
1906 if (startIdx < haystack.length)
1908 ptrdiff_t foundIdx = indexOfAnyNeitherImpl!(true, false)(
1909 haystack[startIdx .. $], needles, cs);
1912 return foundIdx + cast(ptrdiff_t) startIdx;
1921 assert(indexOfNeither("abba", "a", 2) == 2);
1922 assert(indexOfNeither("def", "de", 1) == 2);
1923 assert(indexOfNeither("dfefffg", "dfe", 4) == 6);
1929 assert(indexOfNeither("def", "a") == 0);
1930 assert(indexOfNeither("def", "de") == 2);
1931 assert(indexOfNeither("dfefffg", "dfe") == 6);
1936 import std.conv : to;
1938 foreach (S; AliasSeq!(string, wstring, dstring))
1940 auto r = to!S("").indexOfNeither("hello");
1941 assert(r == -1, to!string(r));
1943 r = to!S("hello").indexOfNeither("");
1944 assert(r == 0, to!string(r));
1946 r = to!S("").indexOfNeither("");
1947 assert(r == -1, to!string(r));
1953 import std.conv : to;
1954 import std.exception : assertCTFEable;
1958 foreach (S; AliasSeq!(string, wstring, dstring))
1960 foreach (T; AliasSeq!(string, wstring, dstring))
1961 (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
1962 assert(indexOfNeither(cast(S) null, to!T("a")) == -1);
1963 assert(indexOfNeither("abba", "a") == 1);
1965 assert(indexOfNeither(to!S("dfeffgfff"), to!T("a"),
1966 No.caseSensitive) == 0);
1967 assert(indexOfNeither(to!S("def"), to!T("D"),
1968 No.caseSensitive) == 1);
1969 assert(indexOfNeither(to!S("ABca"), to!T("a"),
1970 No.caseSensitive) == 1);
1971 assert(indexOfNeither(to!S("def"), to!T("f"),
1972 No.caseSensitive) == 0);
1973 assert(indexOfNeither(to!S("DfEfffg"), to!T("dFe"),
1974 No.caseSensitive) == 6);
1975 if (is(S == string))
1977 assert(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"),
1978 No.caseSensitive) == 8,
1979 to!string(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"),
1980 No.caseSensitive)));
1984 assert(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"),
1985 No.caseSensitive) == 7,
1986 to!string(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"),
1987 No.caseSensitive)));
1997 import std.conv : to;
1998 import std.exception : assertCTFEable;
2002 foreach (S; AliasSeq!(string, wstring, dstring))
2004 foreach (T; AliasSeq!(string, wstring, dstring))
2005 (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
2006 assert(indexOfNeither(cast(S) null, to!T("a"), 1) == -1);
2007 assert(indexOfNeither(to!S("def"), to!T("a"), 1) == 1,
2008 to!string(indexOfNeither(to!S("def"), to!T("a"), 1)));
2010 assert(indexOfNeither(to!S("dfeffgfff"), to!T("a"), 4,
2011 No.caseSensitive) == 4);
2012 assert(indexOfNeither(to!S("def"), to!T("D"), 2,
2013 No.caseSensitive) == 2);
2014 assert(indexOfNeither(to!S("ABca"), to!T("a"), 3,
2015 No.caseSensitive) == -1);
2016 assert(indexOfNeither(to!S("def"), to!T("tzf"), 2,
2017 No.caseSensitive) == -1);
2018 assert(indexOfNeither(to!S("DfEfffg"), to!T("dFe"), 5,
2019 No.caseSensitive) == 6);
2020 if (is(S == string))
2022 assert(indexOfNeither(to!S("öDfEfffg"), to!T("äDi"), 2,
2023 No.caseSensitive) == 3, to!string(indexOfNeither(
2024 to!S("öDfEfffg"), to!T("äDi"), 2, No.caseSensitive)));
2028 assert(indexOfNeither(to!S("öDfEfffg"), to!T("äDi"), 2,
2029 No.caseSensitive) == 2, to!string(indexOfNeither(
2030 to!S("öDfEfffg"), to!T("äDi"), 2, No.caseSensitive)));
2039 Returns the last index of the first occurence of any character that is not
2040 an elements in $(D needles) in $(D haystack). If all element of
2041 $(D haystack) are element of $(D needles) $(D -1) is returned.
2044 haystack = String to search for needles in.
2045 needles = Strings to search for in haystack.
2046 stopIdx = slices haystack like this $(D haystack[0 .. stopIdx]) If
2047 the stopIdx is greater equal the length of haystack the functions
2049 cs = Indicates whether the comparisons are case sensitive.
2051 ptrdiff_t lastIndexOfNeither(Char,Char2)(const(Char)[] haystack,
2052 const(Char2)[] needles, in CaseSensitive cs = Yes.caseSensitive)
2054 if (isSomeChar!Char && isSomeChar!Char2)
2056 return indexOfAnyNeitherImpl!(false, false)(haystack, needles, cs);
2060 ptrdiff_t lastIndexOfNeither(Char,Char2)(const(Char)[] haystack,
2061 const(Char2)[] needles, in size_t stopIdx,
2062 in CaseSensitive cs = Yes.caseSensitive)
2064 if (isSomeChar!Char && isSomeChar!Char2)
2066 if (stopIdx < haystack.length)
2068 return indexOfAnyNeitherImpl!(false, false)(haystack[0 .. stopIdx],
2077 assert(lastIndexOfNeither("abba", "a") == 2);
2078 assert(lastIndexOfNeither("def", "f") == 1);
2084 assert(lastIndexOfNeither("def", "rsa", 3) == -1);
2085 assert(lastIndexOfNeither("abba", "a", 2) == 1);
2090 import std.conv : to;
2092 foreach (S; AliasSeq!(string, wstring, dstring))
2094 auto r = to!S("").lastIndexOfNeither("hello");
2095 assert(r == -1, to!string(r));
2097 r = to!S("hello").lastIndexOfNeither("");
2098 assert(r == 4, to!string(r));
2100 r = to!S("").lastIndexOfNeither("");
2101 assert(r == -1, to!string(r));
2107 import std.conv : to;
2108 import std.exception : assertCTFEable;
2112 foreach (S; AliasSeq!(string, wstring, dstring))
2114 foreach (T; AliasSeq!(string, wstring, dstring))
2115 (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
2116 assert(lastIndexOfNeither(cast(S) null, to!T("a")) == -1);
2117 assert(lastIndexOfNeither(to!S("def"), to!T("rsa")) == 2);
2118 assert(lastIndexOfNeither(to!S("dfefffg"), to!T("fgh")) == 2);
2120 ptrdiff_t oeIdx = 8;
2121 if (is(S == string))
2126 auto foundOeIdx = lastIndexOfNeither(to!S("ödfefegff"), to!T("zeg"));
2127 assert(foundOeIdx == oeIdx, to!string(foundOeIdx));
2129 assert(lastIndexOfNeither(to!S("zfeffgfsb"), to!T("FSB"),
2130 No.caseSensitive) == 5);
2131 assert(lastIndexOfNeither(to!S("def"), to!T("MI6"),
2132 No.caseSensitive) == 2, to!string(lastIndexOfNeither(to!S("def"),
2133 to!T("MI6"), No.caseSensitive)));
2134 assert(lastIndexOfNeither(to!S("abbadeafsb"), to!T("fSb"),
2135 No.caseSensitive) == 6, to!string(lastIndexOfNeither(
2136 to!S("abbadeafsb"), to!T("fSb"), No.caseSensitive)));
2137 assert(lastIndexOfNeither(to!S("defbi"), to!T("FBI"),
2138 No.caseSensitive) == 1);
2139 assert(lastIndexOfNeither(to!S("dfefffg"), to!T("NSA"),
2140 No.caseSensitive) == 6);
2141 assert(lastIndexOfNeither(to!S("dfeffgfffö"), to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"),
2142 No.caseSensitive) == 8, to!string(lastIndexOfNeither(to!S("dfeffgfffö"),
2143 to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"), No.caseSensitive)));
2152 import std.conv : to;
2153 import std.exception : assertCTFEable;
2157 foreach (S; AliasSeq!(string, wstring, dstring))
2159 foreach (T; AliasSeq!(string, wstring, dstring))
2160 (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
2161 assert(lastIndexOfNeither(cast(S) null, to!T("a"), 1337) == -1);
2162 assert(lastIndexOfNeither(to!S("def"), to!T("f")) == 1);
2163 assert(lastIndexOfNeither(to!S("dfefffg"), to!T("fgh")) == 2);
2165 ptrdiff_t oeIdx = 4;
2166 if (is(S == string))
2171 auto foundOeIdx = lastIndexOfNeither(to!S("ödfefegff"), to!T("zeg"),
2173 assert(foundOeIdx == oeIdx, to!string(foundOeIdx));
2175 assert(lastIndexOfNeither(to!S("zfeffgfsb"), to!T("FSB"), 6,
2176 No.caseSensitive) == 5);
2177 assert(lastIndexOfNeither(to!S("def"), to!T("MI6"), 2,
2178 No.caseSensitive) == 1, to!string(lastIndexOfNeither(to!S("def"),
2179 to!T("MI6"), 2, No.caseSensitive)));
2180 assert(lastIndexOfNeither(to!S("abbadeafsb"), to!T("fSb"), 6,
2181 No.caseSensitive) == 5, to!string(lastIndexOfNeither(
2182 to!S("abbadeafsb"), to!T("fSb"), 6, No.caseSensitive)));
2183 assert(lastIndexOfNeither(to!S("defbi"), to!T("FBI"), 3,
2184 No.caseSensitive) == 1);
2185 assert(lastIndexOfNeither(to!S("dfefffg"), to!T("NSA"), 2,
2186 No.caseSensitive) == 1, to!string(lastIndexOfNeither(
2187 to!S("dfefffg"), to!T("NSA"), 2, No.caseSensitive)));
2195 * Returns the _representation of a string, which has the same type
2196 * as the string except the character type is replaced by $(D ubyte),
2197 * $(D ushort), or $(D uint) depending on the character width.
2200 * s = The string to return the _representation of.
2203 * The _representation of the passed string.
2205 auto representation(Char)(Char[] s) @safe pure nothrow @nogc
2206 if (isSomeChar!Char)
2208 import std.traits : ModifyTypePreservingTQ;
2209 alias ToRepType(T) = AliasSeq!(ubyte, ushort, uint)[T.sizeof / 2];
2210 return cast(ModifyTypePreservingTQ!(ToRepType, Char)[])s;
2217 static assert(is(typeof(representation(s)) == immutable(ubyte)[]));
2218 assert(representation(s) is cast(immutable(ubyte)[]) s);
2219 assert(representation(s) == [0x68, 0x65, 0x6c, 0x6c, 0x6f]);
2222 @system pure unittest
2224 import std.exception : assertCTFEable;
2225 import std.traits : Fields;
2226 import std.typecons : Tuple;
2230 void test(Char, T)(Char[] str)
2232 static assert(is(typeof(representation(str)) == T[]));
2233 assert(representation(str) is cast(T[]) str);
2236 foreach (Type; AliasSeq!(Tuple!(char , ubyte ),
2237 Tuple!(wchar, ushort),
2238 Tuple!(dchar, uint )))
2240 alias Char = Fields!Type[0];
2241 alias Int = Fields!Type[1];
2242 enum immutable(Char)[] hello = "hello";
2244 test!( immutable Char, immutable Int)(hello);
2245 test!( const Char, const Int)(hello);
2246 test!( Char, Int)(hello.dup);
2247 test!( shared Char, shared Int)(cast(shared) hello.dup);
2248 test!(const shared Char, const shared Int)(hello);
2255 * Capitalize the first character of $(D s) and convert the rest of $(D s) to
2259 * input = The string to _capitalize.
2262 * The capitalized string.
2265 * $(REF asCapitalized, std,uni) for a lazy range version that doesn't allocate memory
2267 S capitalize(S)(S input) @trusted pure
2270 import std.array : array;
2271 import std.uni : asCapitalized;
2272 import std.utf : byUTF;
2274 return input.asCapitalized.byUTF!(ElementEncodingType!(S)).array;
2280 assert(capitalize("hello") == "Hello");
2281 assert(capitalize("World") == "World");
2284 auto capitalize(S)(auto ref S s)
2285 if (!isSomeString!S && is(StringTypeOf!S))
2287 return capitalize!(StringTypeOf!S)(s);
2292 assert(testAliasedString!capitalize("hello"));
2297 import std.algorithm.comparison : cmp;
2298 import std.conv : to;
2299 import std.exception : assertCTFEable;
2303 foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[]))
2308 s2 = capitalize(s1);
2309 assert(cmp(s2, "Fol") == 0);
2312 s2 = capitalize(s1[0 .. 2]);
2313 assert(cmp(s2, "Fo") == 0);
2316 s2 = capitalize(s1);
2317 assert(cmp(s2, "Fol") == 0);
2319 s1 = to!S("\u0131 \u0130");
2320 s2 = capitalize(s1);
2321 assert(cmp(s2, "\u0049 i\u0307") == 0);
2324 s1 = to!S("\u017F \u0049");
2325 s2 = capitalize(s1);
2326 assert(cmp(s2, "\u0053 \u0069") == 0);
2333 Split $(D s) into an array of lines according to the unicode standard using
2334 $(D '\r'), $(D '\n'), $(D "\r\n"), $(REF lineSep, std,uni),
2335 $(REF paraSep, std,uni), $(D U+0085) (NEL), $(D '\v') and $(D '\f')
2336 as delimiters. If $(D keepTerm) is set to $(D KeepTerminator.yes), then the
2337 delimiter is included in the strings returned.
2339 Does not throw on invalid UTF; such is simply passed unchanged
2342 Allocates memory; use $(LREF lineSplitter) for an alternative that
2345 Adheres to $(HTTP www.unicode.org/versions/Unicode7.0.0/ch05.pdf, Unicode 7.0).
2348 s = a string of $(D chars), $(D wchars), or $(D dchars), or any custom
2349 type that casts to a $(D string) type
2350 keepTerm = whether delimiter is included or not in the results
2352 array of strings, each element is a line that is a slice of $(D s)
2354 $(LREF lineSplitter)
2355 $(REF splitter, std,algorithm)
2356 $(REF splitter, std,regex)
2358 alias KeepTerminator = Flag!"keepTerminator";
2361 S[] splitLines(S)(S s, in KeepTerminator keepTerm = No.keepTerminator) @safe pure
2364 import std.array : appender;
2365 import std.uni : lineSep, paraSep;
2368 auto retval = appender!(S[])();
2370 for (size_t i; i < s.length; ++i)
2374 case '\v', '\f', '\n':
2375 retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator)]);
2380 if (i + 1 < s.length && s[i + 1] == '\n')
2382 retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator) * 2]);
2392 static if (s[i].sizeof == 1)
2395 * lineSep is E2 80 A8
2396 * paraSep is E2 80 A9
2399 if (i + 2 < s.length &&
2401 (s[i + 2] == 0xA8 || s[i + 2] == 0xA9)
2404 retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator) * 3]);
2415 if (i + 1 < s.length && s[i + 1] == 0x85)
2417 retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator) * 2]);
2438 if (iStart != s.length)
2439 retval.put(s[iStart .. $]);
2445 @safe pure nothrow unittest
2447 string s = "Hello\nmy\rname\nis";
2448 assert(splitLines(s) == ["Hello", "my", "name", "is"]);
2451 @safe pure nothrow unittest
2453 string s = "a\xC2\x86b";
2454 assert(splitLines(s) == [s]);
2457 auto splitLines(S)(auto ref S s, in KeepTerminator keepTerm = No.keepTerminator)
2458 if (!isSomeString!S && is(StringTypeOf!S))
2460 return splitLines!(StringTypeOf!S)(s, keepTerm);
2463 @safe pure nothrow unittest
2465 assert(testAliasedString!splitLines("hello\nworld"));
2470 import std.conv : to;
2471 import std.exception : assertCTFEable;
2475 foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
2478 "\rpeter\n\rpaul\r\njerry\u2028ice\u2029cream\n\nsunday\n" ~
2479 "mon\u2030day\nschadenfreude\vkindergarten\f\vcookies\u0085"
2481 auto lines = splitLines(s);
2482 assert(lines.length == 14);
2483 assert(lines[0] == "");
2484 assert(lines[1] == "peter");
2485 assert(lines[2] == "");
2486 assert(lines[3] == "paul");
2487 assert(lines[4] == "jerry");
2488 assert(lines[5] == "ice");
2489 assert(lines[6] == "cream");
2490 assert(lines[7] == "");
2491 assert(lines[8] == "sunday");
2492 assert(lines[9] == "mon\u2030day");
2493 assert(lines[10] == "schadenfreude");
2494 assert(lines[11] == "kindergarten");
2495 assert(lines[12] == "");
2496 assert(lines[13] == "cookies");
2499 ubyte[] u = ['a', 0xFF, 0x12, 'b']; // invalid UTF
2500 auto ulines = splitLines(cast(char[]) u);
2501 assert(cast(ubyte[])(ulines[0]) == u);
2503 lines = splitLines(s, Yes.keepTerminator);
2504 assert(lines.length == 14);
2505 assert(lines[0] == "\r");
2506 assert(lines[1] == "peter\n");
2507 assert(lines[2] == "\r");
2508 assert(lines[3] == "paul\r\n");
2509 assert(lines[4] == "jerry\u2028");
2510 assert(lines[5] == "ice\u2029");
2511 assert(lines[6] == "cream\n");
2512 assert(lines[7] == "\n");
2513 assert(lines[8] == "sunday\n");
2514 assert(lines[9] == "mon\u2030day\n");
2515 assert(lines[10] == "schadenfreude\v");
2516 assert(lines[11] == "kindergarten\f");
2517 assert(lines[12] == "\v");
2518 assert(lines[13] == "cookies\u0085");
2520 s.popBack(); // Lop-off trailing \n
2521 lines = splitLines(s);
2522 assert(lines.length == 14);
2523 assert(lines[9] == "mon\u2030day");
2525 lines = splitLines(s, Yes.keepTerminator);
2526 assert(lines.length == 14);
2527 assert(lines[13] == "cookies");
2532 private struct LineSplitter(KeepTerminator keepTerm = No.keepTerminator, Range)
2534 import std.conv : unsigned;
2535 import std.uni : lineSep, paraSep;
2539 alias IndexType = typeof(unsigned(_input.length));
2540 enum IndexType _unComputed = IndexType.max;
2541 IndexType iStart = _unComputed;
2543 IndexType iNext = 0;
2551 static if (isInfinite!Range)
2553 enum bool empty = false;
2557 @property bool empty()
2559 return iStart == _unComputed && iNext == _input.length;
2563 @property typeof(_input) front()
2565 if (iStart == _unComputed)
2569 for (IndexType i = iNext; ; ++i)
2571 if (i == _input.length)
2579 case '\v', '\f', '\n':
2580 iEnd = i + (keepTerm == Yes.keepTerminator);
2585 if (i + 1 < _input.length && _input[i + 1] == '\n')
2587 iEnd = i + (keepTerm == Yes.keepTerminator) * 2;
2596 static if (_input[i].sizeof == 1)
2599 * lineSep is E2 80 A8
2600 * paraSep is E2 80 A9
2603 if (i + 2 < _input.length &&
2604 _input[i + 1] == 0x80 &&
2605 (_input[i + 2] == 0xA8 || _input[i + 2] == 0xA9)
2608 iEnd = i + (keepTerm == Yes.keepTerminator) * 3;
2618 if (i + 1 < _input.length && _input[i + 1] == 0x85)
2620 iEnd = i + (keepTerm == Yes.keepTerminator) * 2;
2640 return _input[iStart .. iEnd];
2645 if (iStart == _unComputed)
2650 iStart = _unComputed;
2653 static if (isForwardRange!Range)
2655 @property typeof(this) save()
2658 ret._input = _input.save;
2664 /***********************************
2665 * Split an array or slicable range of characters into a range of lines
2666 using $(D '\r'), $(D '\n'), $(D '\v'), $(D '\f'), $(D "\r\n"),
2667 $(REF lineSep, std,uni), $(REF paraSep, std,uni) and $(D '\u0085') (NEL)
2668 as delimiters. If $(D keepTerm) is set to $(D Yes.keepTerminator), then the
2669 delimiter is included in the slices returned.
2671 Does not throw on invalid UTF; such is simply passed unchanged
2674 Adheres to $(HTTP www.unicode.org/versions/Unicode7.0.0/ch05.pdf, Unicode 7.0).
2676 Does not allocate memory.
2679 r = array of $(D chars), $(D wchars), or $(D dchars) or a slicable range
2680 keepTerm = whether delimiter is included or not in the results
2682 range of slices of the input range $(D r)
2686 $(REF splitter, std,algorithm)
2687 $(REF splitter, std,regex)
2689 auto lineSplitter(KeepTerminator keepTerm = No.keepTerminator, Range)(Range r)
2690 if ((hasSlicing!Range && hasLength!Range && isSomeChar!(ElementType!Range) ||
2691 isSomeString!Range) &&
2692 !isConvertibleToString!Range)
2694 return LineSplitter!(keepTerm, Range)(r);
2700 import std.array : array;
2702 string s = "Hello\nmy\rname\nis";
2704 /* notice the call to 'array' to turn the lazy range created by
2705 lineSplitter comparable to the string[] created by splitLines.
2707 assert(lineSplitter(s).array == splitLines(s));
2710 auto lineSplitter(KeepTerminator keepTerm = No.keepTerminator, Range)(auto ref Range r)
2711 if (isConvertibleToString!Range)
2713 return LineSplitter!(keepTerm, StringTypeOf!Range)(r);
2718 import std.array : array;
2719 import std.conv : to;
2720 import std.exception : assertCTFEable;
2724 foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
2727 "\rpeter\n\rpaul\r\njerry\u2028ice\u2029cream\n\n" ~
2728 "sunday\nmon\u2030day\nschadenfreude\vkindergarten\f\vcookies\u0085"
2731 auto lines = lineSplitter(s).array;
2732 assert(lines.length == 14);
2733 assert(lines[0] == "");
2734 assert(lines[1] == "peter");
2735 assert(lines[2] == "");
2736 assert(lines[3] == "paul");
2737 assert(lines[4] == "jerry");
2738 assert(lines[5] == "ice");
2739 assert(lines[6] == "cream");
2740 assert(lines[7] == "");
2741 assert(lines[8] == "sunday");
2742 assert(lines[9] == "mon\u2030day");
2743 assert(lines[10] == "schadenfreude");
2744 assert(lines[11] == "kindergarten");
2745 assert(lines[12] == "");
2746 assert(lines[13] == "cookies");
2749 ubyte[] u = ['a', 0xFF, 0x12, 'b']; // invalid UTF
2750 auto ulines = lineSplitter(cast(char[]) u).array;
2751 assert(cast(ubyte[])(ulines[0]) == u);
2753 lines = lineSplitter!(Yes.keepTerminator)(s).array;
2754 assert(lines.length == 14);
2755 assert(lines[0] == "\r");
2756 assert(lines[1] == "peter\n");
2757 assert(lines[2] == "\r");
2758 assert(lines[3] == "paul\r\n");
2759 assert(lines[4] == "jerry\u2028");
2760 assert(lines[5] == "ice\u2029");
2761 assert(lines[6] == "cream\n");
2762 assert(lines[7] == "\n");
2763 assert(lines[8] == "sunday\n");
2764 assert(lines[9] == "mon\u2030day\n");
2765 assert(lines[10] == "schadenfreude\v");
2766 assert(lines[11] == "kindergarten\f");
2767 assert(lines[12] == "\v");
2768 assert(lines[13] == "cookies\u0085");
2770 s.popBack(); // Lop-off trailing \n
2771 lines = lineSplitter(s).array;
2772 assert(lines.length == 14);
2773 assert(lines[9] == "mon\u2030day");
2775 lines = lineSplitter!(Yes.keepTerminator)(s).array;
2776 assert(lines.length == 14);
2777 assert(lines[13] == "cookies");
2783 @nogc @safe pure unittest
2785 auto s = "\rpeter\n\rpaul\r\njerry\u2028ice\u2029cream\n\nsunday\nmon\u2030day\n";
2786 auto lines = s.lineSplitter();
2787 static immutable witness = ["", "peter", "", "paul", "jerry", "ice", "cream", "", "sunday", "mon\u2030day"];
2789 foreach (line; lines)
2791 assert(line == witness[i++]);
2793 assert(i == witness.length);
2796 @nogc @safe pure unittest
2798 import std.algorithm.comparison : equal;
2799 auto s = "std/string.d";
2800 auto as = TestAliasedString(s);
2801 assert(equal(s.lineSplitter(), as.lineSplitter()));
2806 auto s = "line1\nline2";
2807 auto spl0 = s.lineSplitter!(Yes.keepTerminator);
2808 auto spl1 = spl0.save;
2810 assert(spl1.front ~ spl0.front == s);
2811 string r = "a\xC2\x86b";
2812 assert(r.lineSplitter.front == r);
2816 Strips leading whitespace (as defined by $(REF isWhite, std,uni)).
2819 input = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
2822 Returns: $(D input) stripped of leading whitespace.
2824 Postconditions: $(D input) and the returned value
2825 will share the same tail (see $(REF sameTail, std,array)).
2828 Generic stripping on ranges: $(REF _stripLeft, std, algorithm, mutation)
2830 auto stripLeft(Range)(Range input)
2831 if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
2832 !isInfinite!Range && !isConvertibleToString!Range)
2834 static import std.ascii;
2835 static import std.uni;
2836 import std.utf : decodeFront;
2838 while (!input.empty)
2840 auto c = input.front;
2841 if (std.ascii.isASCII(c))
2843 if (!std.ascii.isWhite(c))
2849 auto save = input.save;
2850 auto dc = decodeFront(input);
2851 if (!std.uni.isWhite(dc))
2861 import std.uni : lineSep, paraSep;
2862 assert(stripLeft(" hello world ") ==
2864 assert(stripLeft("\n\t\v\rhello world\n\t\v\r") ==
2865 "hello world\n\t\v\r");
2866 assert(stripLeft("hello world") ==
2868 assert(stripLeft([lineSep] ~ "hello world" ~ lineSep) ==
2869 "hello world" ~ [lineSep]);
2870 assert(stripLeft([paraSep] ~ "hello world" ~ paraSep) ==
2871 "hello world" ~ [paraSep]);
2873 import std.array : array;
2874 import std.utf : byChar;
2875 assert(stripLeft(" hello world "w.byChar).array ==
2879 auto stripLeft(Range)(auto ref Range str)
2880 if (isConvertibleToString!Range)
2882 return stripLeft!(StringTypeOf!Range)(str);
2887 assert(testAliasedString!stripLeft(" hello"));
2891 Strips trailing whitespace (as defined by $(REF isWhite, std,uni)).
2894 str = string or random access range of characters
2897 slice of $(D str) stripped of trailing whitespace.
2900 Generic stripping on ranges: $(REF _stripRight, std, algorithm, mutation)
2902 auto stripRight(Range)(Range str)
2903 if (isSomeString!Range ||
2904 isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range &&
2905 !isConvertibleToString!Range &&
2906 isSomeChar!(ElementEncodingType!Range))
2908 import std.uni : isWhite;
2909 alias C = Unqual!(ElementEncodingType!(typeof(str)));
2911 static if (isSomeString!(typeof(str)))
2913 import std.utf : codeLength;
2915 foreach_reverse (i, dchar c; str)
2918 return str[0 .. i + codeLength!C(c)];
2925 size_t i = str.length;
2928 static if (C.sizeof == 4)
2930 if (isWhite(str[i]))
2934 else static if (C.sizeof == 2)
2937 if (c2 < 0xD800 || c2 >= 0xE000)
2942 else if (c2 >= 0xDC00)
2946 immutable c1 = str[i - 1];
2947 if (c1 >= 0xD800 && c1 < 0xDC00)
2949 immutable dchar c = ((c1 - 0xD7C0) << 10) + (c2 - 0xDC00);
2960 else static if (C.sizeof == 1)
2962 import std.utf : byDchar;
2978 if (!i || (cx & 0xC0) == 0xC0 || stride == 4)
2986 if (!str[i .. i + stride].byDchar.front.isWhite)
2987 return str[0 .. i + stride];
2994 return str[0 .. i + 1];
3002 import std.uni : lineSep, paraSep;
3003 assert(stripRight(" hello world ") ==
3005 assert(stripRight("\n\t\v\rhello world\n\t\v\r") ==
3006 "\n\t\v\rhello world");
3007 assert(stripRight("hello world") ==
3009 assert(stripRight([lineSep] ~ "hello world" ~ lineSep) ==
3010 [lineSep] ~ "hello world");
3011 assert(stripRight([paraSep] ~ "hello world" ~ paraSep) ==
3012 [paraSep] ~ "hello world");
3015 auto stripRight(Range)(auto ref Range str)
3016 if (isConvertibleToString!Range)
3018 return stripRight!(StringTypeOf!Range)(str);
3023 assert(testAliasedString!stripRight("hello "));
3028 import std.array : array;
3029 import std.uni : lineSep, paraSep;
3030 import std.utf : byChar, byDchar, byUTF, byWchar, invalidUTFstrings;
3031 assert(stripRight(" hello world ".byChar).array == " hello world");
3032 assert(stripRight("\n\t\v\rhello world\n\t\v\r"w.byWchar).array == "\n\t\v\rhello world"w);
3033 assert(stripRight("hello world"d.byDchar).array == "hello world"d);
3034 assert(stripRight("\u2028hello world\u2020\u2028".byChar).array == "\u2028hello world\u2020");
3035 assert(stripRight("hello world\U00010001"w.byWchar).array == "hello world\U00010001"w);
3037 foreach (C; AliasSeq!(char, wchar, dchar))
3039 foreach (s; invalidUTFstrings!C())
3041 cast(void) stripRight(s.byUTF!C).array;
3045 cast(void) stripRight("a\x80".byUTF!char).array;
3046 wstring ws = ['a', cast(wchar) 0xDC00];
3047 cast(void) stripRight(ws.byUTF!wchar).array;
3052 Strips both leading and trailing whitespace (as defined by
3053 $(REF isWhite, std,uni)).
3056 str = string or random access range of characters
3059 slice of $(D str) stripped of leading and trailing whitespace.
3062 Generic stripping on ranges: $(REF _strip, std, algorithm, mutation)
3064 auto strip(Range)(Range str)
3065 if (isSomeString!Range ||
3066 isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range &&
3067 !isConvertibleToString!Range &&
3068 isSomeChar!(ElementEncodingType!Range))
3070 return stripRight(stripLeft(str));
3076 import std.uni : lineSep, paraSep;
3077 assert(strip(" hello world ") ==
3079 assert(strip("\n\t\v\rhello world\n\t\v\r") ==
3081 assert(strip("hello world") ==
3083 assert(strip([lineSep] ~ "hello world" ~ [lineSep]) ==
3085 assert(strip([paraSep] ~ "hello world" ~ [paraSep]) ==
3089 auto strip(Range)(auto ref Range str)
3090 if (isConvertibleToString!Range)
3092 return strip!(StringTypeOf!Range)(str);
3097 assert(testAliasedString!strip(" hello world "));
3102 import std.algorithm.comparison : equal;
3103 import std.conv : to;
3104 import std.exception : assertCTFEable;
3108 foreach (S; AliasSeq!( char[], const char[], string,
3109 wchar[], const wchar[], wstring,
3110 dchar[], const dchar[], dstring))
3112 assert(equal(stripLeft(to!S(" foo\t ")), "foo\t "));
3113 assert(equal(stripLeft(to!S("\u2008 foo\t \u2007")), "foo\t \u2007"));
3114 assert(equal(stripLeft(to!S("\u0085 μ \u0085 \u00BB \r")), "μ \u0085 \u00BB \r"));
3115 assert(equal(stripLeft(to!S("1")), "1"));
3116 assert(equal(stripLeft(to!S("\U0010FFFE")), "\U0010FFFE"));
3117 assert(equal(stripLeft(to!S("")), ""));
3119 assert(equal(stripRight(to!S(" foo\t ")), " foo"));
3120 assert(equal(stripRight(to!S("\u2008 foo\t \u2007")), "\u2008 foo"));
3121 assert(equal(stripRight(to!S("\u0085 μ \u0085 \u00BB \r")), "\u0085 μ \u0085 \u00BB"));
3122 assert(equal(stripRight(to!S("1")), "1"));
3123 assert(equal(stripRight(to!S("\U0010FFFE")), "\U0010FFFE"));
3124 assert(equal(stripRight(to!S("")), ""));
3126 assert(equal(strip(to!S(" foo\t ")), "foo"));
3127 assert(equal(strip(to!S("\u2008 foo\t \u2007")), "foo"));
3128 assert(equal(strip(to!S("\u0085 μ \u0085 \u00BB \r")), "μ \u0085 \u00BB"));
3129 assert(equal(strip(to!S("\U0010FFFE")), "\U0010FFFE"));
3130 assert(equal(strip(to!S("")), ""));
3137 import std.array : sameHead, sameTail;
3138 import std.exception : assertCTFEable;
3142 assert(s.sameTail(s.stripLeft()));
3143 assert(s.sameHead(s.stripRight()));
3149 If $(D str) ends with $(D delimiter), then $(D str) is returned without
3150 $(D delimiter) on its end. If it $(D str) does $(I not) end with
3151 $(D delimiter), then it is returned unchanged.
3153 If no $(D delimiter) is given, then one trailing $(D '\r'), $(D '\n'),
3154 $(D "\r\n"), $(D '\f'), $(D '\v'), $(REF lineSep, std,uni), $(REF paraSep, std,uni), or $(REF nelSep, std,uni)
3155 is removed from the end of $(D str). If $(D str) does not end with any of those characters,
3156 then it is returned unchanged.
3159 str = string or indexable range of characters
3160 delimiter = string of characters to be sliced off end of str[]
3165 Range chomp(Range)(Range str)
3166 if ((isRandomAccessRange!Range && isSomeChar!(ElementEncodingType!Range) ||
3167 isNarrowString!Range) &&
3168 !isConvertibleToString!Range)
3170 import std.uni : lineSep, paraSep, nelSep;
3174 alias C = ElementEncodingType!Range;
3180 if (str.length > 1 && str[$ - 2] == '\r')
3181 return str[0 .. $ - 2];
3184 case '\r', '\v', '\f':
3185 return str[0 .. $ - 1];
3187 // Pop off the last character if lineSep, paraSep, or nelSep
3188 static if (is(C : const char))
3191 * lineSep is E2 80 A8
3192 * paraSep is E2 80 A9
3194 case 0xA8: // Last byte of lineSep
3195 case 0xA9: // Last byte of paraSep
3196 if (str.length > 2 && str[$ - 2] == 0x80 && str[$ - 3] == 0xE2)
3197 return str [0 .. $ - 3];
3204 if (str.length > 1 && str[$ - 2] == 0xC2)
3205 return str [0 .. $ - 2];
3213 return str[0 .. $ - 1];
3221 Range chomp(Range, C2)(Range str, const(C2)[] delimiter)
3222 if ((isBidirectionalRange!Range && isSomeChar!(ElementEncodingType!Range) ||
3223 isNarrowString!Range) &&
3224 !isConvertibleToString!Range &&
3227 if (delimiter.empty)
3230 alias C1 = ElementEncodingType!Range;
3232 static if (is(Unqual!C1 == Unqual!C2) && (isSomeString!Range || (hasSlicing!Range && C2.sizeof == 4)))
3234 import std.algorithm.searching : endsWith;
3235 if (str.endsWith(delimiter))
3236 return str[0 .. $ - delimiter.length];
3241 auto orig = str.save;
3243 static if (isSomeString!Range)
3244 alias C = dchar; // because strings auto-decode
3246 alias C = C1; // and ranges do not
3248 foreach_reverse (C c; delimiter)
3250 if (str.empty || str.back != c)
3264 import std.uni : lineSep, paraSep, nelSep;
3265 import std.utf : decode;
3266 assert(chomp(" hello world \n\r") == " hello world \n");
3267 assert(chomp(" hello world \r\n") == " hello world ");
3268 assert(chomp(" hello world \f") == " hello world ");
3269 assert(chomp(" hello world \v") == " hello world ");
3270 assert(chomp(" hello world \n\n") == " hello world \n");
3271 assert(chomp(" hello world \n\n ") == " hello world \n\n ");
3272 assert(chomp(" hello world \n\n" ~ [lineSep]) == " hello world \n\n");
3273 assert(chomp(" hello world \n\n" ~ [paraSep]) == " hello world \n\n");
3274 assert(chomp(" hello world \n\n" ~ [ nelSep]) == " hello world \n\n");
3275 assert(chomp(" hello world") == " hello world");
3276 assert(chomp("") == "");
3278 assert(chomp(" hello world", "orld") == " hello w");
3279 assert(chomp(" hello world", " he") == " hello world");
3280 assert(chomp("", "hello") == "");
3282 // Don't decode pointlessly
3283 assert(chomp("hello\xFE", "\r") == "hello\xFE");
3286 StringTypeOf!Range chomp(Range)(auto ref Range str)
3287 if (isConvertibleToString!Range)
3289 return chomp!(StringTypeOf!Range)(str);
3292 StringTypeOf!Range chomp(Range, C2)(auto ref Range str, const(C2)[] delimiter)
3293 if (isConvertibleToString!Range)
3295 return chomp!(StringTypeOf!Range, C2)(str, delimiter);
3300 assert(testAliasedString!chomp(" hello world \n\r"));
3301 assert(testAliasedString!chomp(" hello world", "orld"));
3306 import std.conv : to;
3307 import std.exception : assertCTFEable;
3313 foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
3315 // @@@ BUG IN COMPILER, MUST INSERT CAST
3316 assert(chomp(cast(S) null) is null);
3317 assert(chomp(to!S("hello")) == "hello");
3318 assert(chomp(to!S("hello\n")) == "hello");
3319 assert(chomp(to!S("hello\r")) == "hello");
3320 assert(chomp(to!S("hello\r\n")) == "hello");
3321 assert(chomp(to!S("hello\n\r")) == "hello\n");
3322 assert(chomp(to!S("hello\n\n")) == "hello\n");
3323 assert(chomp(to!S("hello\r\r")) == "hello\r");
3324 assert(chomp(to!S("hello\nxxx\n")) == "hello\nxxx");
3325 assert(chomp(to!S("hello\u2028")) == "hello");
3326 assert(chomp(to!S("hello\u2029")) == "hello");
3327 assert(chomp(to!S("hello\u0085")) == "hello");
3328 assert(chomp(to!S("hello\u2028\u2028")) == "hello\u2028");
3329 assert(chomp(to!S("hello\u2029\u2029")) == "hello\u2029");
3330 assert(chomp(to!S("hello\u2029\u2129")) == "hello\u2029\u2129");
3331 assert(chomp(to!S("hello\u2029\u0185")) == "hello\u2029\u0185");
3333 foreach (T; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
3334 (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
3335 // @@@ BUG IN COMPILER, MUST INSERT CAST
3336 assert(chomp(cast(S) null, cast(T) null) is null);
3337 assert(chomp(to!S("hello\n"), cast(T) null) == "hello");
3338 assert(chomp(to!S("hello"), to!T("o")) == "hell");
3339 assert(chomp(to!S("hello"), to!T("p")) == "hello");
3340 // @@@ BUG IN COMPILER, MUST INSERT CAST
3341 assert(chomp(to!S("hello"), cast(T) null) == "hello");
3342 assert(chomp(to!S("hello"), to!T("llo")) == "he");
3343 assert(chomp(to!S("\uFF28ello"), to!T("llo")) == "\uFF28e");
3344 assert(chomp(to!S("\uFF28el\uFF4co"), to!T("l\uFF4co")) == "\uFF28e");
3350 import std.array : array;
3351 import std.utf : byChar, byWchar, byDchar;
3352 assert(chomp("hello world\r\n" .byChar ).array == "hello world");
3353 assert(chomp("hello world\r\n"w.byWchar).array == "hello world"w);
3354 assert(chomp("hello world\r\n"d.byDchar).array == "hello world"d);
3356 assert(chomp("hello world"d.byDchar, "ld").array == "hello wor"d);
3358 assert(chomp("hello\u2020" .byChar , "\u2020").array == "hello");
3359 assert(chomp("hello\u2020"d.byDchar, "\u2020"d).array == "hello"d);
3364 If $(D str) starts with $(D delimiter), then the part of $(D str) following
3365 $(D delimiter) is returned. If $(D str) does $(I not) start with
3367 $(D delimiter), then it is returned unchanged.
3370 str = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
3372 delimiter = string of characters to be sliced off front of str[]
3377 Range chompPrefix(Range, C2)(Range str, const(C2)[] delimiter)
3378 if ((isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) ||
3379 isNarrowString!Range) &&
3380 !isConvertibleToString!Range &&
3383 alias C1 = ElementEncodingType!Range;
3385 static if (is(Unqual!C1 == Unqual!C2) && (isSomeString!Range || (hasSlicing!Range && C2.sizeof == 4)))
3387 import std.algorithm.searching : startsWith;
3388 if (str.startsWith(delimiter))
3389 return str[delimiter.length .. $];
3394 auto orig = str.save;
3396 static if (isSomeString!Range)
3397 alias C = dchar; // because strings auto-decode
3399 alias C = C1; // and ranges do not
3401 foreach (C c; delimiter)
3403 if (str.empty || str.front != c)
3416 assert(chompPrefix("hello world", "he") == "llo world");
3417 assert(chompPrefix("hello world", "hello w") == "orld");
3418 assert(chompPrefix("hello world", " world") == "hello world");
3419 assert(chompPrefix("", "hello") == "");
3422 StringTypeOf!Range chompPrefix(Range, C2)(auto ref Range str, const(C2)[] delimiter)
3423 if (isConvertibleToString!Range)
3425 return chompPrefix!(StringTypeOf!Range, C2)(str, delimiter);
3431 import std.algorithm.comparison : equal;
3432 import std.conv : to;
3433 import std.exception : assertCTFEable;
3436 foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
3438 foreach (T; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
3439 (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
3440 assert(equal(chompPrefix(to!S("abcdefgh"), to!T("abcde")), "fgh"));
3441 assert(equal(chompPrefix(to!S("abcde"), to!T("abcdefgh")), "abcde"));
3442 assert(equal(chompPrefix(to!S("\uFF28el\uFF4co"), to!T("\uFF28el\uFF4co")), ""));
3443 assert(equal(chompPrefix(to!S("\uFF28el\uFF4co"), to!T("\uFF28el")), "\uFF4co"));
3444 assert(equal(chompPrefix(to!S("\uFF28el"), to!T("\uFF28el\uFF4co")), "\uFF28el"));
3450 import std.array : array;
3451 import std.utf : byChar, byWchar, byDchar;
3452 assert(chompPrefix("hello world" .byChar , "hello"d).array == " world");
3453 assert(chompPrefix("hello world"w.byWchar, "hello" ).array == " world"w);
3454 assert(chompPrefix("hello world"d.byDchar, "hello"w).array == " world"d);
3455 assert(chompPrefix("hello world"c.byDchar, "hello"w).array == " world"d);
3457 assert(chompPrefix("hello world"d.byDchar, "lx").array == "hello world"d);
3458 assert(chompPrefix("hello world"d.byDchar, "hello world xx").array == "hello world"d);
3460 assert(chompPrefix("\u2020world" .byChar , "\u2020").array == "world");
3461 assert(chompPrefix("\u2020world"d.byDchar, "\u2020"d).array == "world"d);
3466 assert(testAliasedString!chompPrefix("hello world", "hello"));
3470 Returns $(D str) without its last character, if there is one. If $(D str)
3471 ends with $(D "\r\n"), then both are removed. If $(D str) is empty, then
3472 then it is returned unchanged.
3475 str = string (must be valid UTF)
3480 Range chop(Range)(Range str)
3481 if ((isBidirectionalRange!Range && isSomeChar!(ElementEncodingType!Range) ||
3482 isNarrowString!Range) &&
3483 !isConvertibleToString!Range)
3488 static if (isSomeString!Range)
3490 if (str.length >= 2 && str[$ - 1] == '\n' && str[$ - 2] == '\r')
3491 return str[0 .. $ - 2];
3497 alias C = Unqual!(ElementEncodingType!Range);
3502 if (!str.empty && str.back == '\r')
3506 // Pop back a dchar, not just a code unit
3507 static if (C.sizeof == 1)
3510 while ((c & 0xC0) == 0x80)
3520 else static if (C.sizeof == 2)
3522 if (c >= 0xD800 && c <= 0xDBFF)
3528 else static if (C.sizeof == 4)
3540 assert(chop("hello world") == "hello worl");
3541 assert(chop("hello world\n") == "hello world");
3542 assert(chop("hello world\r") == "hello world");
3543 assert(chop("hello world\n\r") == "hello world\n");
3544 assert(chop("hello world\r\n") == "hello world");
3545 assert(chop("Walter Bright") == "Walter Brigh");
3546 assert(chop("") == "");
3549 StringTypeOf!Range chop(Range)(auto ref Range str)
3550 if (isConvertibleToString!Range)
3552 return chop!(StringTypeOf!Range)(str);
3557 assert(testAliasedString!chop("hello world"));
3562 import std.array : array;
3563 import std.utf : byChar, byWchar, byDchar, byCodeUnit, invalidUTFstrings;
3565 assert(chop("hello world".byChar).array == "hello worl");
3566 assert(chop("hello world\n"w.byWchar).array == "hello world"w);
3567 assert(chop("hello world\r"d.byDchar).array == "hello world"d);
3568 assert(chop("hello world\n\r".byChar).array == "hello world\n");
3569 assert(chop("hello world\r\n"w.byWchar).array == "hello world"w);
3570 assert(chop("Walter Bright"d.byDchar).array == "Walter Brigh"d);
3571 assert(chop("".byChar).array == "");
3573 assert(chop(`ミツバチと科学者` .byCodeUnit).array == "ミツバチと科学");
3574 assert(chop(`ミツバチと科学者`w.byCodeUnit).array == "ミツバチと科学"w);
3575 assert(chop(`ミツバチと科学者`d.byCodeUnit).array == "ミツバチと科学"d);
3577 auto ca = invalidUTFstrings!char();
3580 foreach (c; chop(s.byCodeUnit))
3585 auto wa = invalidUTFstrings!wchar();
3588 foreach (c; chop(s.byCodeUnit))
3596 import std.algorithm.comparison : equal;
3597 import std.conv : to;
3598 import std.exception : assertCTFEable;
3602 foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
3604 assert(chop(cast(S) null) is null);
3605 assert(equal(chop(to!S("hello")), "hell"));
3606 assert(equal(chop(to!S("hello\r\n")), "hello"));
3607 assert(equal(chop(to!S("hello\n\r")), "hello\n"));
3608 assert(equal(chop(to!S("Verité")), "Verit"));
3609 assert(equal(chop(to!S(`さいごの果実`)), "さいごの果"));
3610 assert(equal(chop(to!S(`ミツバチと科学者`)), "ミツバチと科学"));
3617 Left justify $(D s) in a field $(D width) characters wide. $(D fillChar)
3618 is the character that will be used to fill up the space in the field that
3619 $(D s) doesn't fill.
3623 width = minimum field width
3624 fillChar = used to pad end up to $(D width) characters
3630 $(LREF leftJustifier), which does not allocate
3632 S leftJustify(S)(S s, size_t width, dchar fillChar = ' ')
3635 import std.array : array;
3636 return leftJustifier(s, width, fillChar).array;
3642 assert(leftJustify("hello", 7, 'X') == "helloXX");
3643 assert(leftJustify("hello", 2, 'X') == "hello");
3644 assert(leftJustify("hello", 9, 'X') == "helloXXXX");
3648 Left justify $(D s) in a field $(D width) characters wide. $(D fillChar)
3649 is the character that will be used to fill up the space in the field that
3650 $(D s) doesn't fill.
3653 r = string or range of characters
3654 width = minimum field width
3655 fillChar = used to pad end up to $(D width) characters
3658 a lazy range of the left justified result
3661 $(LREF rightJustifier)
3664 auto leftJustifier(Range)(Range r, size_t width, dchar fillChar = ' ')
3665 if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) &&
3666 !isConvertibleToString!Range)
3668 alias C = Unqual!(ElementEncodingType!Range);
3670 static if (C.sizeof == 1)
3672 import std.utf : byDchar, byChar;
3673 return leftJustifier(r.byDchar, width, fillChar).byChar;
3675 else static if (C.sizeof == 2)
3677 import std.utf : byDchar, byWchar;
3678 return leftJustifier(r.byDchar, width, fillChar).byWchar;
3680 else static if (C.sizeof == 4)
3682 static struct Result
3692 @property bool empty()
3694 return len >= _width && _input.empty;
3699 return _input.empty ? _fillChar : _input.front;
3709 static if (isForwardRange!Range)
3711 @property typeof(this) save() return scope
3714 ret._input = _input.save;
3720 return Result(r, width, fillChar);
3727 @safe pure @nogc nothrow
3730 import std.algorithm.comparison : equal;
3731 import std.utf : byChar;
3732 assert(leftJustifier("hello", 2).equal("hello".byChar));
3733 assert(leftJustifier("hello", 7).equal("hello ".byChar));
3734 assert(leftJustifier("hello", 7, 'x').equal("helloxx".byChar));
3737 auto leftJustifier(Range)(auto ref Range r, size_t width, dchar fillChar = ' ')
3738 if (isConvertibleToString!Range)
3740 return leftJustifier!(StringTypeOf!Range)(r, width, fillChar);
3745 auto r = "hello".leftJustifier(8);
3749 assert(r.front == 'l');
3750 assert(save.front == 'e');
3755 assert(testAliasedString!leftJustifier("hello", 2));
3759 Right justify $(D s) in a field $(D width) characters wide. $(D fillChar)
3760 is the character that will be used to fill up the space in the field that
3761 $(D s) doesn't fill.
3765 width = minimum field width
3766 fillChar = used to pad end up to $(D width) characters
3772 $(LREF rightJustifier), which does not allocate
3774 S rightJustify(S)(S s, size_t width, dchar fillChar = ' ')
3777 import std.array : array;
3778 return rightJustifier(s, width, fillChar).array;
3784 assert(rightJustify("hello", 7, 'X') == "XXhello");
3785 assert(rightJustify("hello", 2, 'X') == "hello");
3786 assert(rightJustify("hello", 9, 'X') == "XXXXhello");
3790 Right justify $(D s) in a field $(D width) characters wide. $(D fillChar)
3791 is the character that will be used to fill up the space in the field that
3792 $(D s) doesn't fill.
3795 r = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
3797 width = minimum field width
3798 fillChar = used to pad end up to $(D width) characters
3801 a lazy range of the right justified result
3804 $(LREF leftJustifier)
3807 auto rightJustifier(Range)(Range r, size_t width, dchar fillChar = ' ')
3808 if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
3809 !isConvertibleToString!Range)
3811 alias C = Unqual!(ElementEncodingType!Range);
3813 static if (C.sizeof == 1)
3815 import std.utf : byDchar, byChar;
3816 return rightJustifier(r.byDchar, width, fillChar).byChar;
3818 else static if (C.sizeof == 2)
3820 import std.utf : byDchar, byWchar;
3821 return rightJustifier(r.byDchar, width, fillChar).byWchar;
3823 else static if (C.sizeof == 4)
3825 static struct Result
3830 alias nfill = _width; // number of fill characters to prepend
3834 // Lazy initialization so constructor is trivial and cannot fail
3837 // Replace _width with nfill
3838 // (use alias instead of union because CTFE cannot deal with unions)
3840 static if (hasLength!Range)
3842 immutable len = _input.length;
3843 nfill = (_width > len) ? _width - len : 0;
3847 // Lookahead to see now many fill characters are needed
3848 import std.range : take;
3849 import std.range.primitives : walkLength;
3850 nfill = _width - walkLength(_input.save.take(_width), _width);
3856 this(Range input, size_t width, dchar fillChar) pure nothrow
3859 _fillChar = fillChar;
3863 @property bool empty()
3865 return !nfill && _input.empty;
3871 return _input.front; // fast path
3874 return nfill ? _fillChar : _input.front;
3880 _input.popFront(); // fast path
3892 @property typeof(this) save()
3895 ret._input = _input.save;
3900 return Result(r, width, fillChar);
3907 @safe pure @nogc nothrow
3910 import std.algorithm.comparison : equal;
3911 import std.utf : byChar;
3912 assert(rightJustifier("hello", 2).equal("hello".byChar));
3913 assert(rightJustifier("hello", 7).equal(" hello".byChar));
3914 assert(rightJustifier("hello", 7, 'x').equal("xxhello".byChar));
3917 auto rightJustifier(Range)(auto ref Range r, size_t width, dchar fillChar = ' ')
3918 if (isConvertibleToString!Range)
3920 return rightJustifier!(StringTypeOf!Range)(r, width, fillChar);
3925 assert(testAliasedString!rightJustifier("hello", 2));
3930 auto r = "hello"d.rightJustifier(6);
3934 assert(r.front == 'e');
3935 assert(save.front == 'h');
3937 auto t = "hello".rightJustifier(7);
3939 assert(t.front == ' ');
3941 assert(t.front == 'h');
3943 auto u = "hello"d.rightJustifier(5);
3950 Center $(D s) in a field $(D width) characters wide. $(D fillChar)
3951 is the character that will be used to fill up the space in the field that
3952 $(D s) doesn't fill.
3955 s = The string to center
3956 width = Width of the field to center `s` in
3957 fillChar = The character to use for filling excess space in the field
3960 The resulting _center-justified string. The returned string is
3961 GC-allocated. To avoid GC allocation, use $(LREF centerJustifier)
3964 S center(S)(S s, size_t width, dchar fillChar = ' ')
3967 import std.array : array;
3968 return centerJustifier(s, width, fillChar).array;
3974 assert(center("hello", 7, 'X') == "XhelloX");
3975 assert(center("hello", 2, 'X') == "hello");
3976 assert(center("hello", 9, 'X') == "XXhelloXX");
3982 import std.conv : to;
3983 import std.exception : assertCTFEable;
3987 foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
3989 S s = to!S("hello");
3991 assert(leftJustify(s, 2) == "hello");
3992 assert(rightJustify(s, 2) == "hello");
3993 assert(center(s, 2) == "hello");
3995 assert(leftJustify(s, 7) == "hello ");
3996 assert(rightJustify(s, 7) == " hello");
3997 assert(center(s, 7) == " hello ");
3999 assert(leftJustify(s, 8) == "hello ");
4000 assert(rightJustify(s, 8) == " hello");
4001 assert(center(s, 8) == " hello ");
4003 assert(leftJustify(s, 8, '\u0100') == "hello\u0100\u0100\u0100");
4004 assert(rightJustify(s, 8, '\u0100') == "\u0100\u0100\u0100hello");
4005 assert(center(s, 8, '\u0100') == "\u0100hello\u0100\u0100");
4007 assert(leftJustify(s, 8, 'ö') == "helloööö");
4008 assert(rightJustify(s, 8, 'ö') == "öööhello");
4009 assert(center(s, 8, 'ö') == "öhelloöö");
4015 Center justify $(D r) in a field $(D width) characters wide. $(D fillChar)
4016 is the character that will be used to fill up the space in the field that
4017 $(D r) doesn't fill.
4020 r = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
4022 width = minimum field width
4023 fillChar = used to pad end up to $(D width) characters
4026 a lazy range of the center justified result
4029 $(LREF leftJustifier)
4030 $(LREF rightJustifier)
4033 auto centerJustifier(Range)(Range r, size_t width, dchar fillChar = ' ')
4034 if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
4035 !isConvertibleToString!Range)
4037 alias C = Unqual!(ElementEncodingType!Range);
4039 static if (C.sizeof == 1)
4041 import std.utf : byDchar, byChar;
4042 return centerJustifier(r.byDchar, width, fillChar).byChar;
4044 else static if (C.sizeof == 2)
4046 import std.utf : byDchar, byWchar;
4047 return centerJustifier(r.byDchar, width, fillChar).byWchar;
4049 else static if (C.sizeof == 4)
4051 import std.range : chain, repeat;
4052 import std.range.primitives : walkLength;
4054 auto len = walkLength(r.save, width);
4057 const nleft = (width - len) / 2;
4058 const nright = width - len - nleft;
4059 return chain(repeat(fillChar, nleft), r, repeat(fillChar, nright));
4066 @safe pure @nogc nothrow
4069 import std.algorithm.comparison : equal;
4070 import std.utf : byChar;
4071 assert(centerJustifier("hello", 2).equal("hello".byChar));
4072 assert(centerJustifier("hello", 8).equal(" hello ".byChar));
4073 assert(centerJustifier("hello", 7, 'x').equal("xhellox".byChar));
4076 auto centerJustifier(Range)(auto ref Range r, size_t width, dchar fillChar = ' ')
4077 if (isConvertibleToString!Range)
4079 return centerJustifier!(StringTypeOf!Range)(r, width, fillChar);
4084 assert(testAliasedString!centerJustifier("hello", 8));
4089 static auto byFwdRange(dstring s)
4091 static struct FRange
4094 this(dstring s) { str = s; }
4095 @property bool empty() { return str.length == 0; }
4096 @property dchar front() { return str[0]; }
4097 void popFront() { str = str[1 .. $]; }
4098 @property FRange save() { return this; }
4103 auto r = centerJustifier(byFwdRange("hello"d), 6);
4107 assert(r.front == 'l');
4108 assert(save.front == 'e');
4110 auto t = "hello".centerJustifier(7);
4112 assert(t.front == 'h');
4114 assert(t.front == 'e');
4116 auto u = byFwdRange("hello"d).centerJustifier(6);
4127 Replace each tab character in $(D s) with the number of spaces necessary
4128 to align the following character at the next tab stop.
4132 tabSize = distance between tab stops
4135 GC allocated string with tabs replaced with spaces
4137 auto detab(Range)(auto ref Range s, size_t tabSize = 8) pure
4138 if ((isForwardRange!Range && isSomeChar!(ElementEncodingType!Range))
4139 || __traits(compiles, StringTypeOf!Range))
4141 import std.array : array;
4142 return detabber(s, tabSize).array;
4146 @system pure unittest
4148 assert(detab(" \n\tx", 9) == " \n x");
4153 static struct TestStruct
4159 static struct TestStruct2
4163 @disable this(this);
4166 string s = " \n\tx";
4167 string cmp = " \n x";
4168 auto t = TestStruct(s);
4169 assert(detab(t, 9) == cmp);
4170 assert(detab(TestStruct(s), 9) == cmp);
4171 assert(detab(TestStruct(s), 9) == detab(TestStruct(s), 9));
4172 assert(detab(TestStruct2(s), 9) == detab(TestStruct2(s), 9));
4173 assert(detab(TestStruct2(s), 9) == cmp);
4177 Replace each tab character in $(D r) with the number of spaces
4178 necessary to align the following character at the next tab stop.
4181 r = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
4182 tabSize = distance between tab stops
4185 lazy forward range with tabs replaced with spaces
4187 auto detabber(Range)(Range r, size_t tabSize = 8)
4188 if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
4189 !isConvertibleToString!Range)
4191 import std.uni : lineSep, paraSep, nelSep;
4192 import std.utf : codeUnitLimit, decodeFront;
4194 assert(tabSize > 0);
4196 alias C = Unqual!(ElementEncodingType!(Range));
4198 static struct Result
4209 this(Range input, size_t tabSize)
4215 static if (isInfinite!(Range))
4217 enum bool empty = false;
4221 @property bool empty()
4223 return _input.empty && nspaces == 0;
4231 static if (isSomeString!(Range))
4238 if (c < codeUnitLimit!(immutable(C)[]))
4245 auto r = _input.save;
4246 dc = decodeFront(r, index); // lookahead to decode
4259 nspaces = _tabSize - (column % _tabSize);
4279 static if (isSomeString!(Range))
4280 _input = _input[1 .. $];
4287 @property typeof(this) save()
4290 ret._input = _input.save;
4295 return Result(r, tabSize);
4299 @system pure unittest
4301 import std.array : array;
4303 assert(detabber(" \n\tx", 9).array == " \n x");
4306 auto detabber(Range)(auto ref Range r, size_t tabSize = 8)
4307 if (isConvertibleToString!Range)
4309 return detabber!(StringTypeOf!Range)(r, tabSize);
4314 assert(testAliasedString!detabber( " ab\t asdf ", 8));
4317 @system pure unittest
4319 import std.algorithm.comparison : cmp;
4320 import std.conv : to;
4321 import std.exception : assertCTFEable;
4325 foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
4327 S s = to!S("This \tis\t a fofof\tof list");
4328 assert(cmp(detab(s), "This is a fofof of list") == 0);
4330 assert(detab(cast(S) null) is null);
4331 assert(detab("").empty);
4332 assert(detab("a") == "a");
4333 assert(detab("\t") == " ");
4334 assert(detab("\t", 3) == " ");
4335 assert(detab("\t", 9) == " ");
4336 assert(detab( " ab\t asdf ") == " ab asdf ");
4337 assert(detab( " \U00010000b\tasdf ") == " \U00010000b asdf ");
4338 assert(detab("\r\t", 9) == "\r ");
4339 assert(detab("\n\t", 9) == "\n ");
4340 assert(detab("\u0085\t", 9) == "\u0085 ");
4341 assert(detab("\u2028\t", 9) == "\u2028 ");
4342 assert(detab(" \u2029\t", 9) == " \u2029 ");
4348 @system pure unittest
4350 import std.array : array;
4351 import std.utf : byChar, byWchar;
4353 assert(detabber(" \u2029\t".byChar, 9).array == " \u2029 ");
4354 auto r = "hel\tx".byWchar.detabber();
4355 assert(r.front == 'h');
4359 assert(r.front == 'l');
4360 assert(s.front == 'h');
4364 Replaces spaces in $(D s) with the optimal number of tabs.
4365 All spaces and tabs at the end of a line are removed.
4368 s = String to convert.
4369 tabSize = Tab columns are $(D tabSize) spaces apart.
4372 GC allocated string with spaces replaced with tabs;
4373 use $(LREF entabber) to not allocate.
4378 auto entab(Range)(Range s, size_t tabSize = 8)
4379 if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range))
4381 import std.array : array;
4382 return entabber(s, tabSize).array;
4388 assert(entab(" x \n") == "\tx\n");
4391 auto entab(Range)(auto ref Range s, size_t tabSize = 8)
4392 if (!(isForwardRange!Range && isSomeChar!(ElementEncodingType!Range)) &&
4393 is(StringTypeOf!Range))
4395 return entab!(StringTypeOf!Range)(s, tabSize);
4400 assert(testAliasedString!entab(" x \n"));
4404 Replaces spaces in range $(D r) with the optimal number of tabs.
4405 All spaces and tabs at the end of a line are removed.
4408 r = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
4409 tabSize = distance between tab stops
4412 lazy forward range with spaces replaced with tabs
4417 auto entabber(Range)(Range r, size_t tabSize = 8)
4418 if (isForwardRange!Range && !isConvertibleToString!Range)
4420 import std.uni : lineSep, paraSep, nelSep;
4421 import std.utf : codeUnitLimit, decodeFront;
4423 assert(tabSize > 0);
4424 alias C = Unqual!(ElementEncodingType!Range);
4426 static struct Result
4436 @property C getFront()
4438 static if (isSomeString!Range)
4439 return _input[0]; // avoid autodecode
4441 return _input.front;
4446 this(Range input, size_t tabSize)
4452 @property bool empty()
4454 if (ntabs || nspaces)
4457 /* Since trailing spaces are removed,
4458 * look ahead for anything that is not a trailing space
4460 static if (isSomeString!Range)
4464 if (c != ' ' && c != '\t')
4473 immutable c = _input.front;
4474 if (c != ' ' && c != '\t')
4476 auto t = _input.save;
4480 if (c2 != ' ' && c2 != '\t')
4489 //writefln(" front(): ntabs = %s nspaces = %s index = %s front = '%s'", ntabs, nspaces, index, getFront);
4498 if (c < codeUnitLimit!(immutable(C)[]))
4502 if (c == ' ' || c == '\t')
4504 // Consume input until a non-blank is encountered
4505 immutable startcol = column;
4507 static if (isSomeString!Range)
4511 assert(_input.length);
4515 else if (cx == '\t')
4516 column += _tabSize - (column % _tabSize);
4519 _input = _input[1 .. $];
4526 assert(!_input.empty);
4530 else if (cx == '\t')
4531 column += _tabSize - (column % _tabSize);
4537 // Compute ntabs+nspaces to get from startcol to column
4538 immutable n = column - startcol;
4545 ntabs = column / _tabSize - startcol / _tabSize;
4547 nspaces = column - startcol;
4549 nspaces = column % _tabSize;
4551 //writefln("\tstartcol = %s, column = %s, _tabSize = %s", startcol, column, _tabSize);
4552 //writefln("\tntabs = %s, nspaces = %s", ntabs, nspaces);
4553 if (cx < codeUnitLimit!(immutable(C)[]))
4560 auto r = _input.save;
4561 dc = decodeFront(r, index); // lookahead to decode
4571 // Spaces followed by newline are ignored
4580 return ntabs ? '\t' : ' ';
4585 auto r = _input.save;
4586 dc = decodeFront(r, index); // lookahead to decode
4588 //writefln("dc = x%x", dc);
4608 //writefln("popFront(): ntabs = %s nspaces = %s index = %s front = '%s'", ntabs, nspaces, index, getFront);
4615 else if (!ntabs && !nspaces)
4617 static if (isSomeString!Range)
4618 _input = _input[1 .. $];
4625 @property typeof(this) save()
4628 ret._input = _input.save;
4633 return Result(r, tabSize);
4639 import std.array : array;
4640 assert(entabber(" x \n").array == "\tx\n");
4643 auto entabber(Range)(auto ref Range r, size_t tabSize = 8)
4644 if (isConvertibleToString!Range)
4646 return entabber!(StringTypeOf!Range)(r, tabSize);
4651 assert(testAliasedString!entabber(" ab asdf ", 8));
4657 import std.conv : to;
4658 import std.exception : assertCTFEable;
4662 assert(entab(cast(string) null) is null);
4663 assert(entab("").empty);
4664 assert(entab("a") == "a");
4665 assert(entab(" ") == "");
4666 assert(entab(" x") == "\tx");
4667 assert(entab(" ab asdf ") == " ab\tasdf");
4668 assert(entab(" ab asdf ") == " ab\t asdf");
4669 assert(entab(" ab \t asdf ") == " ab\t asdf");
4670 assert(entab("1234567 \ta") == "1234567\t\ta");
4671 assert(entab("1234567 \ta") == "1234567\t\ta");
4672 assert(entab("1234567 \ta") == "1234567\t\ta");
4673 assert(entab("1234567 \ta") == "1234567\t\ta");
4674 assert(entab("1234567 \ta") == "1234567\t\ta");
4675 assert(entab("1234567 \ta") == "1234567\t\ta");
4676 assert(entab("1234567 \ta") == "1234567\t\ta");
4677 assert(entab("1234567 \ta") == "1234567\t\ta");
4678 assert(entab("1234567 \ta") == "1234567\t\t\ta");
4680 assert(entab("a ") == "a");
4681 assert(entab("a\v") == "a\v");
4682 assert(entab("a\f") == "a\f");
4683 assert(entab("a\n") == "a\n");
4684 assert(entab("a\n\r") == "a\n\r");
4685 assert(entab("a\r\n") == "a\r\n");
4686 assert(entab("a\u2028") == "a\u2028");
4687 assert(entab("a\u2029") == "a\u2029");
4688 assert(entab("a\u0085") == "a\u0085");
4689 assert(entab("a ") == "a");
4690 assert(entab("a\t") == "a");
4691 assert(entab("\uFF28\uFF45\uFF4C\uFF4C567 \t\uFF4F \t") ==
4692 "\uFF28\uFF45\uFF4C\uFF4C567\t\t\uFF4F");
4693 assert(entab(" \naa") == "\naa");
4694 assert(entab(" \r aa") == "\r aa");
4695 assert(entab(" \u2028 aa") == "\u2028 aa");
4696 assert(entab(" \u2029 aa") == "\u2029 aa");
4697 assert(entab(" \u0085 aa") == "\u0085 aa");
4704 import std.array : array;
4705 import std.utf : byChar;
4706 assert(entabber(" \u0085 aa".byChar).array == "\u0085 aa");
4707 assert(entabber(" \u2028\t aa \t".byChar).array == "\u2028\t aa");
4709 auto r = entabber("1234", 4);
4711 auto rsave = r.save;
4713 assert(r.front == '3');
4714 assert(rsave.front == '2');
4719 Replaces the characters in $(D str) which are keys in $(D transTable) with
4720 their corresponding values in $(D transTable). $(D transTable) is an AA
4721 where its keys are $(D dchar) and its values are either $(D dchar) or some
4722 type of string. Also, if $(D toRemove) is given, the characters in it are
4723 removed from $(D str) prior to translation. $(D str) itself is unaltered.
4724 A copy with the changes is returned.
4728 $(REF replace, std,array)
4731 str = The original string.
4732 transTable = The AA indicating which characters to replace and what to
4734 toRemove = The characters to remove from the string.
4736 C1[] translate(C1, C2 = immutable char)(C1[] str,
4737 in dchar[dchar] transTable,
4738 const(C2)[] toRemove = null) @safe pure
4739 if (isSomeChar!C1 && isSomeChar!C2)
4741 import std.array : appender;
4742 auto buffer = appender!(C1[])();
4743 translateImpl(str, transTable, toRemove, buffer);
4750 dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q'];
4751 assert(translate("hello world", transTable1) == "h5ll7 w7rld");
4753 assert(translate("hello world", transTable1, "low") == "h5 rd");
4755 string[dchar] transTable2 = ['e' : "5", 'o' : "orange"];
4756 assert(translate("hello world", transTable2) == "h5llorange worangerld");
4759 @safe pure unittest // issue 13018
4761 immutable dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q'];
4762 assert(translate("hello world", transTable1) == "h5ll7 w7rld");
4764 assert(translate("hello world", transTable1, "low") == "h5 rd");
4766 immutable string[dchar] transTable2 = ['e' : "5", 'o' : "orange"];
4767 assert(translate("hello world", transTable2) == "h5llorange worangerld");
4770 @system pure unittest
4772 import std.conv : to;
4773 import std.exception : assertCTFEable;
4777 foreach (S; AliasSeq!( char[], const( char)[], immutable( char)[],
4778 wchar[], const(wchar)[], immutable(wchar)[],
4779 dchar[], const(dchar)[], immutable(dchar)[]))
4781 assert(translate(to!S("hello world"), cast(dchar[dchar])['h' : 'q', 'l' : '5']) ==
4782 to!S("qe55o wor5d"));
4783 assert(translate(to!S("hello world"), cast(dchar[dchar])['o' : 'l', 'l' : '\U00010143']) ==
4784 to!S("he\U00010143\U00010143l wlr\U00010143d"));
4785 assert(translate(to!S("hello \U00010143 world"), cast(dchar[dchar])['h' : 'q', 'l': '5']) ==
4786 to!S("qe55o \U00010143 wor5d"));
4787 assert(translate(to!S("hello \U00010143 world"), cast(dchar[dchar])['o' : '0', '\U00010143' : 'o']) ==
4788 to!S("hell0 o w0rld"));
4789 assert(translate(to!S("hello world"), cast(dchar[dchar]) null) == to!S("hello world"));
4791 foreach (T; AliasSeq!( char[], const( char)[], immutable( char)[],
4792 wchar[], const(wchar)[], immutable(wchar)[],
4793 dchar[], const(dchar)[], immutable(dchar)[]))
4794 (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
4795 foreach (R; AliasSeq!(dchar[dchar], const dchar[dchar],
4796 immutable dchar[dchar]))
4798 R tt = ['h' : 'q', 'l' : '5'];
4799 assert(translate(to!S("hello world"), tt, to!T("r"))
4800 == to!S("qe55o wo5d"));
4801 assert(translate(to!S("hello world"), tt, to!T("helo"))
4803 assert(translate(to!S("hello world"), tt, to!T("q5"))
4804 == to!S("qe55o wor5d"));
4808 auto s = to!S("hello world");
4809 dchar[dchar] transTable = ['h' : 'q', 'l' : '5'];
4810 static assert(is(typeof(s) == typeof(translate(s, transTable))));
4816 C1[] translate(C1, S, C2 = immutable char)(C1[] str,
4817 in S[dchar] transTable,
4818 const(C2)[] toRemove = null) @safe pure
4819 if (isSomeChar!C1 && isSomeString!S && isSomeChar!C2)
4821 import std.array : appender;
4822 auto buffer = appender!(C1[])();
4823 translateImpl(str, transTable, toRemove, buffer);
4827 @system pure unittest
4829 import std.conv : to;
4830 import std.exception : assertCTFEable;
4834 foreach (S; AliasSeq!( char[], const( char)[], immutable( char)[],
4835 wchar[], const(wchar)[], immutable(wchar)[],
4836 dchar[], const(dchar)[], immutable(dchar)[]))
4838 assert(translate(to!S("hello world"), ['h' : "yellow", 'l' : "42"]) ==
4839 to!S("yellowe4242o wor42d"));
4840 assert(translate(to!S("hello world"), ['o' : "owl", 'l' : "\U00010143\U00010143"]) ==
4841 to!S("he\U00010143\U00010143\U00010143\U00010143owl wowlr\U00010143\U00010143d"));
4842 assert(translate(to!S("hello \U00010143 world"), ['h' : "yellow", 'l' : "42"]) ==
4843 to!S("yellowe4242o \U00010143 wor42d"));
4844 assert(translate(to!S("hello \U00010143 world"), ['o' : "owl", 'l' : "\U00010143\U00010143"]) ==
4845 to!S("he\U00010143\U00010143\U00010143\U00010143owl \U00010143 wowlr\U00010143\U00010143d"));
4846 assert(translate(to!S("hello \U00010143 world"), ['h' : ""]) ==
4847 to!S("ello \U00010143 world"));
4848 assert(translate(to!S("hello \U00010143 world"), ['\U00010143' : ""]) ==
4849 to!S("hello world"));
4850 assert(translate(to!S("hello world"), cast(string[dchar]) null) == to!S("hello world"));
4852 foreach (T; AliasSeq!( char[], const( char)[], immutable( char)[],
4853 wchar[], const(wchar)[], immutable(wchar)[],
4854 dchar[], const(dchar)[], immutable(dchar)[]))
4855 (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
4857 foreach (R; AliasSeq!(string[dchar], const string[dchar],
4858 immutable string[dchar]))
4860 R tt = ['h' : "yellow", 'l' : "42"];
4861 assert(translate(to!S("hello world"), tt, to!T("r")) ==
4862 to!S("yellowe4242o wo42d"));
4863 assert(translate(to!S("hello world"), tt, to!T("helo")) ==
4865 assert(translate(to!S("hello world"), tt, to!T("y42")) ==
4866 to!S("yellowe4242o wor42d"));
4867 assert(translate(to!S("hello world"), tt, to!T("hello world")) ==
4869 assert(translate(to!S("hello world"), tt, to!T("42")) ==
4870 to!S("yellowe4242o wor42d"));
4874 auto s = to!S("hello world");
4875 string[dchar] transTable = ['h' : "silly", 'l' : "putty"];
4876 static assert(is(typeof(s) == typeof(translate(s, transTable))));
4882 This is an overload of $(D translate) which takes an existing buffer to write the contents to.
4885 str = The original string.
4886 transTable = The AA indicating which characters to replace and what to
4888 toRemove = The characters to remove from the string.
4889 buffer = An output range to write the contents to.
4891 void translate(C1, C2 = immutable char, Buffer)(C1[] str,
4892 in dchar[dchar] transTable,
4893 const(C2)[] toRemove,
4895 if (isSomeChar!C1 && isSomeChar!C2 && isOutputRange!(Buffer, C1))
4897 translateImpl(str, transTable, toRemove, buffer);
4903 import std.array : appender;
4904 dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q'];
4905 auto buffer = appender!(dchar[])();
4906 translate("hello world", transTable1, null, buffer);
4907 assert(buffer.data == "h5ll7 w7rld");
4910 translate("hello world", transTable1, "low", buffer);
4911 assert(buffer.data == "h5 rd");
4914 string[dchar] transTable2 = ['e' : "5", 'o' : "orange"];
4915 translate("hello world", transTable2, null, buffer);
4916 assert(buffer.data == "h5llorange worangerld");
4919 @safe pure unittest // issue 13018
4921 import std.array : appender;
4922 immutable dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q'];
4923 auto buffer = appender!(dchar[])();
4924 translate("hello world", transTable1, null, buffer);
4925 assert(buffer.data == "h5ll7 w7rld");
4928 translate("hello world", transTable1, "low", buffer);
4929 assert(buffer.data == "h5 rd");
4932 immutable string[dchar] transTable2 = ['e' : "5", 'o' : "orange"];
4933 translate("hello world", transTable2, null, buffer);
4934 assert(buffer.data == "h5llorange worangerld");
4938 void translate(C1, S, C2 = immutable char, Buffer)(C1[] str,
4939 in S[dchar] transTable,
4940 const(C2)[] toRemove,
4942 if (isSomeChar!C1 && isSomeString!S && isSomeChar!C2 && isOutputRange!(Buffer, S))
4944 translateImpl(str, transTable, toRemove, buffer);
4947 private void translateImpl(C1, T, C2, Buffer)(C1[] str,
4949 const(C2)[] toRemove,
4952 bool[dchar] removeTable;
4954 foreach (dchar c; toRemove)
4955 removeTable[c] = true;
4957 foreach (dchar c; str)
4959 if (c in removeTable)
4962 auto newC = c in transTable;
4972 This is an $(I $(RED ASCII-only)) overload of $(LREF _translate). It
4973 will $(I not) work with Unicode. It exists as an optimization for the
4974 cases where Unicode processing is not necessary.
4976 Unlike the other overloads of $(LREF _translate), this one does not take
4977 an AA. Rather, it takes a $(D string) generated by $(LREF makeTransTable).
4979 The array generated by $(D makeTransTable) is $(D 256) elements long such that
4980 the index is equal to the ASCII character being replaced and the value is
4981 equal to the character that it's being replaced with. Note that translate
4982 does not decode any of the characters, so you can actually pass it Extended
4983 ASCII characters if you want to (ASCII only actually uses $(D 128)
4984 characters), but be warned that Extended ASCII characters are not valid
4985 Unicode and therefore will result in a $(D UTFException) being thrown from
4986 most other Phobos functions.
4988 Also, because no decoding occurs, it is possible to use this overload to
4989 translate ASCII characters within a proper UTF-8 string without altering the
4990 other, non-ASCII characters. It's replacing any code unit greater than
4991 $(D 127) with another code unit or replacing any code unit with another code
4992 unit greater than $(D 127) which will cause UTF validation issues.
4996 $(REF replace, std,array)
4999 str = The original string.
5000 transTable = The string indicating which characters to replace and what
5001 to replace them with. It is generated by $(LREF makeTransTable).
5002 toRemove = The characters to remove from the string.
5004 C[] translate(C = immutable char)(in char[] str, in char[] transTable, in char[] toRemove = null) @trusted pure nothrow
5005 if (is(Unqual!C == char))
5008 assert(transTable.length == 256);
5012 bool[256] remTable = false;
5014 foreach (char c; toRemove)
5018 foreach (char c; str)
5024 auto buffer = new char[count];
5027 foreach (char c; str)
5030 buffer[i++] = transTable[c];
5033 return cast(C[])(buffer);
5038 * Do same thing as $(LREF makeTransTable) but allocate the translation table
5041 * Use $(LREF makeTransTable) instead.
5043 string makeTrans(in char[] from, in char[] to) @trusted pure nothrow
5045 return makeTransTable(from, to)[].idup;
5049 @safe pure nothrow unittest
5051 auto transTable1 = makeTrans("eo5", "57q");
5052 assert(translate("hello world", transTable1) == "h5ll7 w7rld");
5054 assert(translate("hello world", transTable1, "low") == "h5 rd");
5058 * Construct 256 character translation table, where characters in from[] are replaced
5059 * by corresponding characters in to[].
5062 * from = array of chars, less than or equal to 256 in length
5063 * to = corresponding array of chars to translate to
5068 char[256] makeTransTable(in char[] from, in char[] to) @safe pure nothrow @nogc
5071 import std.ascii : isASCII;
5072 assert(from.length == to.length);
5073 assert(from.length <= 256);
5074 foreach (char c; from)
5076 foreach (char c; to)
5081 char[256] result = void;
5083 foreach (i; 0 .. result.length)
5084 result[i] = cast(char) i;
5085 foreach (i, c; from)
5092 import std.conv : to;
5093 import std.exception : assertCTFEable;
5097 foreach (C; AliasSeq!(char, const char, immutable char))
5099 assert(translate!C("hello world", makeTransTable("hl", "q5")) == to!(C[])("qe55o wor5d"));
5101 auto s = to!(C[])("hello world");
5102 auto transTable = makeTransTable("hl", "q5");
5103 static assert(is(typeof(s) == typeof(translate!C(s, transTable))));
5106 foreach (S; AliasSeq!(char[], const(char)[], immutable(char)[]))
5108 assert(translate(to!S("hello world"), makeTransTable("hl", "q5")) == to!S("qe55o wor5d"));
5109 assert(translate(to!S("hello \U00010143 world"), makeTransTable("hl", "q5")) ==
5110 to!S("qe55o \U00010143 wor5d"));
5111 assert(translate(to!S("hello world"), makeTransTable("ol", "1o")) == to!S("heoo1 w1rod"));
5112 assert(translate(to!S("hello world"), makeTransTable("", "")) == to!S("hello world"));
5113 assert(translate(to!S("hello world"), makeTransTable("12345", "67890")) == to!S("hello world"));
5114 assert(translate(to!S("hello \U00010143 world"), makeTransTable("12345", "67890")) ==
5115 to!S("hello \U00010143 world"));
5117 foreach (T; AliasSeq!(char[], const(char)[], immutable(char)[]))
5118 (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396
5119 assert(translate(to!S("hello world"), makeTransTable("hl", "q5"), to!T("r")) ==
5120 to!S("qe55o wo5d"));
5121 assert(translate(to!S("hello \U00010143 world"), makeTransTable("hl", "q5"), to!T("r")) ==
5122 to!S("qe55o \U00010143 wo5d"));
5123 assert(translate(to!S("hello world"), makeTransTable("hl", "q5"), to!T("helo")) ==
5125 assert(translate(to!S("hello world"), makeTransTable("hl", "q5"), to!T("q5")) ==
5126 to!S("qe55o wor5d"));
5133 This is an $(I $(RED ASCII-only)) overload of $(D translate) which takes an existing buffer to write the contents to.
5136 str = The original string.
5137 transTable = The string indicating which characters to replace and what
5138 to replace them with. It is generated by $(LREF makeTransTable).
5139 toRemove = The characters to remove from the string.
5140 buffer = An output range to write the contents to.
5142 void translate(C = immutable char, Buffer)(in char[] str, in char[] transTable,
5143 in char[] toRemove, Buffer buffer) @trusted pure
5144 if (is(Unqual!C == char) && isOutputRange!(Buffer, char))
5147 assert(transTable.length == 256);
5151 bool[256] remTable = false;
5153 foreach (char c; toRemove)
5156 foreach (char c; str)
5159 put(buffer, transTable[c]);
5166 import std.array : appender;
5167 auto buffer = appender!(char[])();
5168 auto transTable1 = makeTransTable("eo5", "57q");
5169 translate("hello world", transTable1, null, buffer);
5170 assert(buffer.data == "h5ll7 w7rld");
5173 translate("hello world", transTable1, "low", buffer);
5174 assert(buffer.data == "h5 rd");
5177 //@@@DEPRECATED_2018-05@@@
5178 /***********************************************
5179 * $(RED This function is deprecated and will be removed May 2018.)
5180 * Please use the functions in $(MREF std, regex) and $(MREF std, algorithm)
5181 * instead. If you still need this function, it will be available in
5182 * $(LINK2 https://github.com/dlang/undeaD, undeaD).
5184 * See if character c is in the pattern.
5187 * A $(I pattern) is an array of characters much like a $(I character
5188 * class) in regular expressions. A sequence of characters
5189 * can be given, such as "abcde". The '-' can represent a range
5190 * of characters, as "a-e" represents the same pattern as "abcde".
5191 * "a-fA-F0-9" represents all the hex characters.
5192 * If the first character of a pattern is '^', then the pattern
5193 * is negated, i.e. "^0-9" means any character except a digit.
5194 * The functions inPattern, $(B countchars), $(B removeschars),
5195 * and $(B squeeze) use patterns.
5197 * Note: In the future, the pattern syntax may be improved
5198 * to be more like regular expression character classes.
5200 deprecated("This function is obsolete and will be removed May 2018. See the docs for more details")
5201 bool inPattern(S)(dchar c, in S pattern) @safe pure @nogc
5204 bool result = false;
5208 foreach (size_t i, dchar p; pattern)
5210 if (p == '^' && i == 0)
5213 if (i + 1 == pattern.length)
5214 return (c == p); // or should this be an error?
5219 if (lastc <= c && c <= p || c == p)
5222 else if (p == '-' && i > result && i + 1 < pattern.length)
5236 @safe pure @nogc unittest
5238 import std.conv : to;
5239 import std.exception : assertCTFEable;
5243 assert(inPattern('x', "x") == 1);
5244 assert(inPattern('x', "y") == 0);
5245 assert(inPattern('x', string.init) == 0);
5246 assert(inPattern('x', "^y") == 1);
5247 assert(inPattern('x', "yxxy") == 1);
5248 assert(inPattern('x', "^yxxy") == 0);
5249 assert(inPattern('x', "^abcd") == 1);
5250 assert(inPattern('^', "^^") == 0);
5251 assert(inPattern('^', "^") == 1);
5252 assert(inPattern('^', "a^") == 1);
5253 assert(inPattern('x', "a-z") == 1);
5254 assert(inPattern('x', "A-Z") == 0);
5255 assert(inPattern('x', "^a-z") == 0);
5256 assert(inPattern('x', "^A-Z") == 1);
5257 assert(inPattern('-', "a-") == 1);
5258 assert(inPattern('-', "^A-") == 0);
5259 assert(inPattern('a', "z-a") == 1);
5260 assert(inPattern('z', "z-a") == 1);
5261 assert(inPattern('x', "z-a") == 0);
5265 //@@@DEPRECATED_2018-05@@@
5266 /***********************************************
5267 * $(RED This function is deprecated and will be removed May 2018.)
5268 * Please use the functions in $(MREF std, regex) and $(MREF std, algorithm)
5269 * instead. If you still need this function, it will be available in
5270 * $(LINK2 https://github.com/dlang/undeaD, undeaD).
5272 * See if character c is in the intersection of the patterns.
5274 deprecated("This function is obsolete and will be removed May 2018. See the docs for more details")
5275 bool inPattern(S)(dchar c, S[] patterns) @safe pure @nogc
5278 foreach (string pattern; patterns)
5280 if (!inPattern(c, pattern))
5288 //@@@DEPRECATED_2018-05@@@
5289 /********************************************
5290 * $(RED This function is deprecated and will be removed May 2018.)
5291 * Please use the functions in $(MREF std, regex) and $(MREF std, algorithm)
5292 * instead. If you still need this function, it will be available in
5293 * $(LINK2 https://github.com/dlang/undeaD, undeaD).
5295 * Count characters in s that match pattern.
5297 deprecated("This function is obsolete and will be removed May 2018. See the docs for more details")
5298 size_t countchars(S, S1)(S s, in S1 pattern) @safe pure @nogc
5299 if (isSomeString!S && isSomeString!S1)
5302 foreach (dchar c; s)
5304 count += inPattern(c, pattern);
5310 @safe pure @nogc unittest
5312 import std.conv : to;
5313 import std.exception : assertCTFEable;
5317 assert(countchars("abc", "a-c") == 3);
5318 assert(countchars("hello world", "or") == 3);
5322 //@@@DEPRECATED_2018-05@@@
5323 /********************************************
5324 * $(RED This function is deprecated and will be removed May 2018.)
5325 * Please use the functions in $(MREF std, regex) and $(MREF std, algorithm)
5326 * instead. If you still need this function, it will be available in
5327 * $(LINK2 https://github.com/dlang/undeaD, undeaD).
5329 * Return string that is s with all characters removed that match pattern.
5331 deprecated("This function is obsolete and will be removed May 2018. See the docs for more details")
5332 S removechars(S)(S s, in S pattern) @safe pure
5335 import std.utf : encode;
5337 Unqual!(typeof(s[0]))[] r;
5338 bool changed = false;
5340 foreach (size_t i, dchar c; s)
5342 if (inPattern(c, pattern))
5365 import std.conv : to;
5366 import std.exception : assertCTFEable;
5370 assert(removechars("abc", "a-c").length == 0);
5371 assert(removechars("hello world", "or") == "hell wld");
5372 assert(removechars("hello world", "d") == "hello worl");
5373 assert(removechars("hah", "h") == "a");
5380 assert(removechars("abc", "x") == "abc");
5383 //@@@DEPRECATED_2018-05@@@
5384 /***************************************************
5385 * $(RED This function is deprecated and will be removed May 2018.)
5386 * Please use the functions in $(MREF std, regex) and $(MREF std, algorithm)
5387 * instead. If you still need this function, it will be available in
5388 * $(LINK2 https://github.com/dlang/undeaD, undeaD).
5390 * Return string where sequences of a character in s[] from pattern[]
5391 * are replaced with a single instance of that character.
5392 * If pattern is null, it defaults to all characters.
5394 deprecated("This function is obsolete and will be removed May 2018. See the docs for more details")
5395 S squeeze(S)(S s, in S pattern = null)
5397 import std.utf : encode, stride;
5399 Unqual!(typeof(s[0]))[] r;
5405 foreach (size_t i, dchar c; s)
5407 if (run && lastc == c)
5411 else if (pattern is null || inPattern(c, pattern))
5417 r = s[0 .. lasti].dup;
5421 lasti = i + stride(s, i);
5430 r = s[0 .. lasti].dup;
5435 return changed ? ((r is null) ? s[0 .. lasti] : cast(S) r) : s;
5439 @system pure unittest
5441 import std.conv : to;
5442 import std.exception : assertCTFEable;
5448 assert(squeeze("hello") == "helo");
5451 assert(squeeze(s) is s);
5453 assert(squeeze(s).ptr == s.ptr); // should just be a slice
5455 assert(squeeze("hello goodbyee", "oe") == "hello godbye");
5459 //@@@DEPRECATED_2018-05@@@
5460 /***************************************************************
5461 $(RED This function is deprecated and will be removed May 2018.)
5462 Please use the functions in $(MREF std, regex) and $(MREF std, algorithm)
5463 instead. If you still need this function, it will be available in
5464 $(LINK2 https://github.com/dlang/undeaD, undeaD).
5466 Finds the position $(D_PARAM pos) of the first character in $(D_PARAM
5467 s) that does not match $(D_PARAM pattern) (in the terminology used by
5468 $(REF inPattern, std,string)). Updates $(D_PARAM s =
5469 s[pos..$]). Returns the slice from the beginning of the original
5470 (before update) string up to, and excluding, $(D_PARAM pos).
5472 The $(D_PARAM munch) function is mostly convenient for skipping
5473 certain category of characters (e.g. whitespace) when parsing
5474 strings. (In such cases, the return value is not used.)
5476 deprecated("This function is obsolete and will be removed May 2018. See the docs for more details")
5477 S1 munch(S1, S2)(ref S1 s, S2 pattern) @safe pure @nogc
5479 size_t j = s.length;
5480 foreach (i, dchar c; s)
5482 if (!inPattern(c, pattern))
5488 scope(exit) s = s[j .. $];
5494 @safe pure @nogc unittest
5496 string s = "123abc";
5497 string t = munch(s, "0123456789");
5498 assert(t == "123" && s == "abc");
5499 t = munch(s, "0123456789");
5500 assert(t == "" && s == "abc");
5504 @safe pure @nogc unittest
5506 string s = "123€abc";
5507 string t = munch(s, "0123456789");
5508 assert(t == "123" && s == "€abc");
5509 t = munch(s, "0123456789");
5510 assert(t == "" && s == "€abc");
5511 t = munch(s, "£$€¥");
5512 assert(t == "€" && s == "abc");
5516 /**********************************************
5517 * Return string that is the 'successor' to s[].
5518 * If the rightmost character is a-zA-Z0-9, it is incremented within
5519 * its case or digits. If it generates a carry, the process is
5520 * repeated with the one to its immediate left.
5523 S succ(S)(S s) @safe pure
5526 import std.ascii : isAlphaNum;
5528 if (s.length && isAlphaNum(s[$ - 1]))
5531 size_t i = r.length - 1;
5549 r[i] = cast(char) c;
5552 auto t = new typeof(r[0])[r.length + 1];
5553 t[0] = cast(char) carry;
5573 assert(succ("1") == "2");
5574 assert(succ("9") == "10");
5575 assert(succ("999") == "1000");
5576 assert(succ("zz99") == "aaa00");
5581 import std.conv : to;
5582 import std.exception : assertCTFEable;
5586 assert(succ(string.init) is null);
5587 assert(succ("!@#$%") == "!@#$%");
5588 assert(succ("1") == "2");
5589 assert(succ("9") == "10");
5590 assert(succ("999") == "1000");
5591 assert(succ("zz99") == "aaa00");
5597 Replaces the characters in $(D str) which are in $(D from) with the
5598 the corresponding characters in $(D to) and returns the resulting string.
5601 $(HTTP pubs.opengroup.org/onlinepubs/9699919799/utilities/_tr.html, Posix's tr),
5602 though it doesn't do everything that the Posix utility does.
5605 str = The original string.
5606 from = The characters to replace.
5607 to = The characters to replace with.
5608 modifiers = String containing modifiers.
5612 $(TR $(TD Modifier) $(TD Description))
5613 $(TR $(TD $(D 'c')) $(TD Complement the list of characters in $(D from)))
5614 $(TR $(TD $(D 'd')) $(TD Removes matching characters with no corresponding
5615 replacement in $(D to)))
5616 $(TR $(TD $(D 's')) $(TD Removes adjacent duplicates in the replaced
5620 If the modifier $(D 'd') is present, then the number of characters in
5621 $(D to) may be only $(D 0) or $(D 1).
5623 If the modifier $(D 'd') is $(I not) present, and $(D to) is empty, then
5624 $(D to) is taken to be the same as $(D from).
5626 If the modifier $(D 'd') is $(I not) present, and $(D to) is shorter than
5627 $(D from), then $(D to) is extended by replicating the last character in
5630 Both $(D from) and $(D to) may contain ranges using the $(D '-') character
5631 (e.g. $(D "a-d") is synonymous with $(D "abcd").) Neither accept a leading
5632 $(D '^') as meaning the complement of the string (use the $(D 'c') modifier
5635 C1[] tr(C1, C2, C3, C4 = immutable char)
5636 (C1[] str, const(C2)[] from, const(C3)[] to, const(C4)[] modifiers = null)
5638 import std.array : appender;
5639 import std.conv : conv_to = to;
5640 import std.utf : decode;
5646 foreach (char c; modifiers)
5650 case 'c': mod_c = 1; break; // complement
5651 case 'd': mod_d = 1; break; // delete unreplaced chars
5652 case 's': mod_s = 1; break; // squeeze duplicated replaced chars
5657 if (to.empty && !mod_d)
5658 to = conv_to!(typeof(to))(from);
5660 auto result = appender!(C1[])();
5664 foreach (dchar c; str)
5671 for (size_t i = 0; i < from.length; )
5673 immutable f = decode(from, i);
5674 if (f == '-' && lastf != dchar.init && i < from.length)
5676 immutable nextf = decode(from, i);
5677 if (lastf <= c && c <= nextf)
5699 n = 0; // consider it 'found' at position 0
5703 // Find the nth character in to[]
5705 for (size_t i = 0; i < to.length; )
5707 immutable t = decode(to, i);
5708 if (t == '-' && lastt != dchar.init && i < to.length)
5710 nextt = decode(to, i);
5714 newc = nextt + n + 1;
5733 if (mod_s && modified && newc == lastc)
5736 assert(newc != dchar.init);
5752 import std.algorithm.comparison : equal;
5753 import std.conv : to;
5754 import std.exception : assertCTFEable;
5756 // Complete list of test types; too slow to test'em all
5757 // alias TestTypes = AliasSeq!(
5758 // char[], const( char)[], immutable( char)[],
5759 // wchar[], const(wchar)[], immutable(wchar)[],
5760 // dchar[], const(dchar)[], immutable(dchar)[]);
5762 // Reduced list of test types
5763 alias TestTypes = AliasSeq!(char[], const(wchar)[], immutable(dchar)[]);
5767 foreach (S; TestTypes)
5769 foreach (T; TestTypes)
5771 foreach (U; TestTypes)
5773 assert(equal(tr(to!S("abcdef"), to!T("cd"), to!U("CD")), "abCDef"));
5774 assert(equal(tr(to!S("abcdef"), to!T("b-d"), to!U("B-D")), "aBCDef"));
5775 assert(equal(tr(to!S("abcdefgh"), to!T("b-dh"), to!U("B-Dx")), "aBCDefgx"));
5776 assert(equal(tr(to!S("abcdefgh"), to!T("b-dh"), to!U("B-CDx")), "aBCDefgx"));
5777 assert(equal(tr(to!S("abcdefgh"), to!T("b-dh"), to!U("B-BCDx")), "aBCDefgx"));
5778 assert(equal(tr(to!S("abcdef"), to!T("ef"), to!U("*"), to!S("c")), "****ef"));
5779 assert(equal(tr(to!S("abcdef"), to!T("ef"), to!U(""), to!T("d")), "abcd"));
5780 assert(equal(tr(to!S("hello goodbye"), to!T("lo"), to!U(""), to!U("s")), "helo godbye"));
5781 assert(equal(tr(to!S("hello goodbye"), to!T("lo"), to!U("x"), "s"), "hex gxdbye"));
5782 assert(equal(tr(to!S("14-Jul-87"), to!T("a-zA-Z"), to!U(" "), "cs"), " Jul "));
5783 assert(equal(tr(to!S("Abc"), to!T("AAA"), to!U("XYZ")), "Xbc"));
5787 auto s = to!S("hello world");
5788 static assert(is(typeof(s) == typeof(tr(s, "he", "if"))));
5793 @system pure unittest
5795 import core.exception : AssertError;
5796 import std.exception : assertThrown;
5797 assertThrown!AssertError(tr("abcdef", "cd", "CD", "X"));
5801 * Takes a string $(D s) and determines if it represents a number. This function
5802 * also takes an optional parameter, $(D bAllowSep), which will accept the
5803 * separator characters $(D ',') and $(D '__') within the string. But these
5804 * characters should be stripped from the string before using any
5805 * of the conversion functions like $(D to!int()), $(D to!float()), and etc
5806 * else an error will occur.
5808 * Also please note, that no spaces are allowed within the string
5809 * anywhere whether it's a leading, trailing, or embedded space(s),
5810 * thus they too must be stripped from the string before using this
5811 * function, or any of the conversion functions.
5814 * s = the string or random access range to check
5815 * bAllowSep = accept separator characters or not
5820 bool isNumeric(S)(S s, bool bAllowSep = false)
5821 if (isSomeString!S ||
5822 (isRandomAccessRange!S &&
5824 isSomeChar!(ElementType!S) &&
5827 import std.algorithm.comparison : among;
5828 import std.ascii : isASCII;
5830 // ASCII only case insensitive comparison with two ranges
5831 static bool asciiCmp(S1)(S1 a, string b)
5833 import std.algorithm.comparison : equal;
5834 import std.algorithm.iteration : map;
5835 import std.ascii : toLower;
5836 import std.utf : byChar;
5837 return a.map!toLower.equal(b.byChar.map!toLower);
5840 // auto-decoding special case, we're only comparing characters
5841 // in the ASCII range so there's no reason to decode
5842 static if (isSomeString!S)
5844 import std.utf : byCodeUnit;
5845 auto codeUnits = s.byCodeUnit;
5849 alias codeUnits = s;
5852 if (codeUnits.empty)
5855 // Check for NaN (Not a Number) and for Infinity
5856 if (codeUnits.among!((a, b) => asciiCmp(a.save, b))
5857 ("nan", "nani", "nan+nani", "inf", "-inf"))
5860 immutable frontResult = codeUnits.front;
5861 if (frontResult == '-' || frontResult == '+')
5864 immutable iLen = codeUnits.length;
5865 bool bDecimalPoint, bExponent, bComplex, sawDigits;
5867 for (size_t i = 0; i < iLen; i++)
5869 immutable c = codeUnits[i];
5874 // Digits are good, skip to the next character
5875 if (c >= '0' && c <= '9')
5881 // Check for the complex type, and if found
5882 // reset the flags for checking the 2nd number.
5887 bDecimalPoint = false;
5894 // Allow only one exponent per number
5895 if (c == 'e' || c == 'E')
5897 // A 2nd exponent found, return not a number
5898 if (bExponent || i + 1 >= iLen)
5900 // Look forward for the sign, and if
5901 // missing then this is not a number.
5902 if (codeUnits[i + 1] != '-' && codeUnits[i + 1] != '+')
5908 // Allow only one decimal point per number to be used
5911 // A 2nd decimal point found, return not a number
5914 bDecimalPoint = true;
5917 // Check for ending literal characters: "f,u,l,i,ul,fi,li",
5918 // and whether they're being used with the correct datatype.
5923 // Integer Whole Number
5924 if (asciiCmp(codeUnits[i .. iLen], "ul") &&
5925 (!bDecimalPoint && !bExponent && !bComplex))
5927 // Floating-Point Number
5928 if (codeUnits[i .. iLen].among!((a, b) => asciiCmp(a, b))("fi", "li") &&
5929 (bDecimalPoint || bExponent || bComplex))
5931 if (asciiCmp(codeUnits[i .. iLen], "ul") &&
5932 (bDecimalPoint || bExponent || bComplex))
5934 // Could be a Integer or a Float, thus
5935 // all these suffixes are valid for both
5936 return codeUnits[i .. iLen].among!((a, b) => asciiCmp(a, b))
5937 ("ul", "fi", "li") != 0;
5943 // Integer Whole Number
5944 if (c.among!('u', 'l', 'U', 'L')() &&
5945 (!bDecimalPoint && !bExponent && !bComplex))
5947 // Check to see if the last character in the string
5948 // is the required 'i' character
5950 return c.among!('i', 'I')() != 0;
5951 // Floating-Point Number
5952 return c.among!('l', 'L', 'f', 'F', 'i', 'I')() != 0;
5954 // Check if separators are allowed to be in the numeric string
5955 if (!bAllowSep || !c.among!('_', ',')())
5963 * Integer Whole Number: (byte, ubyte, short, ushort, int, uint, long, and ulong)
5964 * ['+'|'-']digit(s)[U|L|UL]
5966 @safe @nogc pure nothrow unittest
5968 assert(isNumeric("123"));
5969 assert(isNumeric("123UL"));
5970 assert(isNumeric("123L"));
5971 assert(isNumeric("+123U"));
5972 assert(isNumeric("-123L"));
5976 * Floating-Point Number: (float, double, real, ifloat, idouble, and ireal)
5977 * ['+'|'-']digit(s)[.][digit(s)][[e-|e+]digit(s)][i|f|L|Li|fi]]
5978 * or [nan|nani|inf|-inf]
5980 @safe @nogc pure nothrow unittest
5982 assert(isNumeric("+123"));
5983 assert(isNumeric("-123.01"));
5984 assert(isNumeric("123.3e-10f"));
5985 assert(isNumeric("123.3e-10fi"));
5986 assert(isNumeric("123.3e-10L"));
5988 assert(isNumeric("nan"));
5989 assert(isNumeric("nani"));
5990 assert(isNumeric("-inf"));
5994 * Floating-Point Number: (cfloat, cdouble, and creal)
5995 * ['+'|'-']digit(s)[.][digit(s)][[e-|e+]digit(s)][+]
5996 * [digit(s)[.][digit(s)][[e-|e+]digit(s)][i|f|L|Li|fi]]
5997 * or [nan|nani|nan+nani|inf|-inf]
5999 @safe @nogc pure nothrow unittest
6001 assert(isNumeric("-123e-1+456.9e-10Li"));
6002 assert(isNumeric("+123e+10+456i"));
6003 assert(isNumeric("123+456"));
6006 @safe @nogc pure nothrow unittest
6008 assert(!isNumeric("F"));
6009 assert(!isNumeric("L"));
6010 assert(!isNumeric("U"));
6011 assert(!isNumeric("i"));
6012 assert(!isNumeric("fi"));
6013 assert(!isNumeric("ul"));
6014 assert(!isNumeric("li"));
6015 assert(!isNumeric("."));
6016 assert(!isNumeric("-"));
6017 assert(!isNumeric("+"));
6018 assert(!isNumeric("e-"));
6019 assert(!isNumeric("e+"));
6020 assert(!isNumeric(".f"));
6021 assert(!isNumeric("e+f"));
6022 assert(!isNumeric("++1"));
6023 assert(!isNumeric(""));
6024 assert(!isNumeric("1E+1E+1"));
6025 assert(!isNumeric("1E1"));
6026 assert(!isNumeric("\x81"));
6029 // Test string types
6032 import std.conv : to;
6034 foreach (T; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[]))
6036 assert("123".to!T.isNumeric());
6037 assert("123UL".to!T.isNumeric());
6038 assert("123fi".to!T.isNumeric());
6039 assert("123li".to!T.isNumeric());
6040 assert(!"--123L".to!T.isNumeric());
6045 @system pure unittest
6047 import std.range : refRange;
6048 import std.utf : byCodeUnit;
6050 assert("123".byCodeUnit.isNumeric());
6051 assert("123UL".byCodeUnit.isNumeric());
6052 assert("123fi".byCodeUnit.isNumeric());
6053 assert("123li".byCodeUnit.isNumeric());
6054 assert(!"--123L".byCodeUnit.isNumeric());
6057 assert(isNumeric(refRange(&z)));
6059 dstring nani = "nani";
6060 assert(isNumeric(refRange(&nani)));
6063 /// isNumeric works with CTFE
6066 enum a = isNumeric("123.00E-5+1234.45E-12Li");
6067 enum b = isNumeric("12345xxxx890");
6075 import std.conv : to;
6076 import std.exception : assertCTFEable;
6080 // Test the isNumeric(in string) function
6081 assert(isNumeric("1") == true );
6082 assert(isNumeric("1.0") == true );
6083 assert(isNumeric("1e-1") == true );
6084 assert(isNumeric("12345xxxx890") == false );
6085 assert(isNumeric("567L") == true );
6086 assert(isNumeric("23UL") == true );
6087 assert(isNumeric("-123..56f") == false );
6088 assert(isNumeric("12.3.5.6") == false );
6089 assert(isNumeric(" 12.356") == false );
6090 assert(isNumeric("123 5.6") == false );
6091 assert(isNumeric("1233E-1+1.0e-1i") == true );
6093 assert(isNumeric("123.00E-5+1234.45E-12Li") == true);
6094 assert(isNumeric("123.00e-5+1234.45E-12iL") == false);
6095 assert(isNumeric("123.00e-5+1234.45e-12uL") == false);
6096 assert(isNumeric("123.00E-5+1234.45e-12lu") == false);
6098 assert(isNumeric("123fi") == true);
6099 assert(isNumeric("123li") == true);
6100 assert(isNumeric("--123L") == false);
6101 assert(isNumeric("+123.5UL") == false);
6102 assert(isNumeric("123f") == true);
6103 assert(isNumeric("123.u") == false);
6105 // @@@BUG@@ to!string(float) is not CTFEable.
6106 // Related: formatValue(T) if (is(FloatingPointTypeOf!T))
6109 assert(isNumeric(to!string(real.nan)) == true);
6110 assert(isNumeric(to!string(-real.infinity)) == true);
6111 assert(isNumeric(to!string(123e+2+1234.78Li)) == true);
6114 string s = "$250.99-";
6115 assert(isNumeric(s[1 .. s.length - 2]) == true);
6116 assert(isNumeric(s) == false);
6117 assert(isNumeric(s[0 .. s.length - 1]) == false);
6120 assert(!isNumeric("-"));
6121 assert(!isNumeric("+"));
6124 /*****************************
6125 * Soundex algorithm.
6127 * The Soundex algorithm converts a word into 4 characters
6128 * based on how the word sounds phonetically. The idea is that
6129 * two spellings that sound alike will have the same Soundex
6130 * value, which means that Soundex can be used for fuzzy matching
6134 * str = String or InputRange to convert to Soundex representation.
6137 * The four character array with the Soundex result in it.
6138 * The array has zero's in it if there is no Soundex representation for the string.
6141 * $(LINK2 http://en.wikipedia.org/wiki/Soundex, Wikipedia),
6142 * $(LUCKY The Soundex Indexing System)
6146 * Only works well with English names.
6147 * There are other arguably better Soundex algorithms,
6148 * but this one is the standard one.
6150 char[4] soundexer(Range)(Range str)
6151 if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) &&
6152 !isConvertibleToString!Range)
6154 alias C = Unqual!(ElementEncodingType!Range);
6156 static immutable dex =
6157 // ABCDEFGHIJKLMNOPQRSTUVWXYZ
6158 "01230120022455012623010202";
6160 char[4] result = void;
6165 if (c >= 'a' && c <= 'z')
6167 else if (c >= 'A' && c <= 'Z')
6177 result[0] = cast(char) c;
6179 lastc = dex[c - 'A'];
6183 if (c == 'H' || c == 'W')
6185 if (c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U')
6188 if (c != '0' && c != lastc)
6190 result[b] = cast(char) c;
6201 result[b .. 4] = '0';
6206 char[4] soundexer(Range)(auto ref Range str)
6207 if (isConvertibleToString!Range)
6209 return soundexer!(StringTypeOf!Range)(str);
6212 /*****************************
6213 * Like $(LREF soundexer), but with different parameters
6217 * str = String to convert to Soundex representation.
6218 * buffer = Optional 4 char array to put the resulting Soundex
6219 * characters into. If null, the return value
6220 * buffer will be allocated on the heap.
6222 * The four character array with the Soundex result in it.
6223 * Returns null if there is no Soundex representation for the string.
6227 char[] soundex(const(char)[] str, char[] buffer = null)
6231 assert(buffer is null || buffer.length >= 4);
6235 if (result !is null)
6237 assert(result.length == 4);
6238 assert(result[0] >= 'A' && result[0] <= 'Z');
6239 foreach (char c; result[1 .. 4])
6240 assert(c >= '0' && c <= '6');
6245 char[4] result = soundexer(str);
6249 buffer = new char[4];
6250 buffer[] = result[];
6255 @safe pure nothrow unittest
6257 import std.exception : assertCTFEable;
6262 assert(soundex(null) == null);
6263 assert(soundex("") == null);
6264 assert(soundex("0123^&^^**&^") == null);
6265 assert(soundex("Euler") == "E460");
6266 assert(soundex(" Ellery ") == "E460");
6267 assert(soundex("Gauss") == "G200");
6268 assert(soundex("Ghosh") == "G200");
6269 assert(soundex("Hilbert") == "H416");
6270 assert(soundex("Heilbronn") == "H416");
6271 assert(soundex("Knuth") == "K530");
6272 assert(soundex("Kant", buffer) == "K530");
6273 assert(soundex("Lloyd") == "L300");
6274 assert(soundex("Ladd") == "L300");
6275 assert(soundex("Lukasiewicz", buffer) == "L222");
6276 assert(soundex("Lissajous") == "L222");
6277 assert(soundex("Robert") == "R163");
6278 assert(soundex("Rupert") == "R163");
6279 assert(soundex("Rubin") == "R150");
6280 assert(soundex("Washington") == "W252");
6281 assert(soundex("Lee") == "L000");
6282 assert(soundex("Gutierrez") == "G362");
6283 assert(soundex("Pfister") == "P236");
6284 assert(soundex("Jackson") == "J250");
6285 assert(soundex("Tymczak") == "T522");
6286 assert(soundex("Ashcraft") == "A261");
6288 assert(soundex("Woo") == "W000");
6289 assert(soundex("Pilgrim") == "P426");
6290 assert(soundex("Flingjingwaller") == "F452");
6291 assert(soundex("PEARSE") == "P620");
6292 assert(soundex("PIERCE") == "P620");
6293 assert(soundex("Price") == "P620");
6294 assert(soundex("CATHY") == "C300");
6295 assert(soundex("KATHY") == "K300");
6296 assert(soundex("Jones") == "J520");
6297 assert(soundex("johnsons") == "J525");
6298 assert(soundex("Hardin") == "H635");
6299 assert(soundex("Martinez") == "M635");
6301 import std.utf : byChar, byDchar, byWchar;
6302 assert(soundexer("Martinez".byChar ) == "M635");
6303 assert(soundexer("Martinez".byWchar) == "M635");
6304 assert(soundexer("Martinez".byDchar) == "M635");
6310 assert(testAliasedString!soundexer("Martinez"));
6314 /***************************************************
6315 * Construct an associative array consisting of all
6316 * abbreviations that uniquely map to the strings in values.
6318 * This is useful in cases where the user is expected to type
6319 * in one of a known set of strings, and the program will helpfully
6320 * auto-complete the string once sufficient characters have been
6321 * entered that uniquely identify it.
6324 string[string] abbrev(string[] values) @safe pure
6326 import std.algorithm.sorting : sort;
6328 string[string] result;
6330 // Make a copy when sorting so we follow COW principles.
6331 values = values.dup;
6334 size_t values_length = values.length;
6335 size_t lasti = values_length;
6341 for (size_t i = 0; i < values_length; i = nexti)
6343 string value = values[i];
6346 for (nexti = i + 1; nexti < values_length; nexti++)
6349 if (value != values[nexti])
6353 import std.utf : stride;
6355 for (size_t j = 0; j < value.length; j += stride(value, j))
6357 string v = value[0 .. j];
6359 if ((nexti == values_length || j > nv.length || v != nv[0 .. j]) &&
6360 (lasti == values_length || j > lv.length || v != lv[0 .. j]))
6365 result[value] = value;
6378 static string[] list = [ "food", "foxy" ];
6379 auto abbrevs = abbrev(list);
6380 assert(abbrevs == ["fox": "foxy", "food": "food",
6381 "foxy": "foxy", "foo": "food"]);
6385 @system pure unittest
6387 import std.algorithm.sorting : sort;
6388 import std.conv : to;
6389 import std.exception : assertCTFEable;
6401 auto keys = r.keys.dup;
6404 assert(keys.length == 4);
6405 assert(keys[0] == "he");
6406 assert(keys[1] == "hel");
6407 assert(keys[2] == "hell");
6408 assert(keys[3] == "hello");
6410 assert(r[keys[0]] == "he");
6411 assert(r[keys[1]] == "hello");
6412 assert(r[keys[2]] == "hello");
6413 assert(r[keys[3]] == "hello");
6418 /******************************************
6419 * Compute _column number at the end of the printed form of the string,
6420 * assuming the string starts in the leftmost _column, which is numbered
6423 * Tab characters are expanded into enough spaces to bring the _column number
6424 * to the next multiple of tabsize.
6425 * If there are multiple lines in the string, the _column number of the last
6429 * str = string or InputRange to be analyzed
6430 * tabsize = number of columns a tab character represents
6436 size_t column(Range)(Range str, in size_t tabsize = 8)
6437 if ((isInputRange!Range && isSomeChar!(Unqual!(ElementEncodingType!Range)) ||
6438 isNarrowString!Range) &&
6439 !isConvertibleToString!Range)
6441 static if (is(Unqual!(ElementEncodingType!Range) == char))
6443 // decoding needed for chars
6444 import std.utf : byDchar;
6446 return str.byDchar.column(tabsize);
6450 // decoding not needed for wchars and dchars
6451 import std.uni : lineSep, paraSep, nelSep;
6455 foreach (const c; str)
6460 column = (column + tabsize) / tabsize * tabsize;
6483 import std.utf : byChar, byWchar, byDchar;
6485 assert(column("1234 ") == 5);
6486 assert(column("1234 "w) == 5);
6487 assert(column("1234 "d) == 5);
6489 assert(column("1234 ".byChar()) == 5);
6490 assert(column("1234 "w.byWchar()) == 5);
6491 assert(column("1234 "d.byDchar()) == 5);
6493 // Tab stops are set at 8 spaces by default; tab characters insert enough
6494 // spaces to bring the column position to the next multiple of 8.
6495 assert(column("\t") == 8);
6496 assert(column("1\t") == 8);
6497 assert(column("\t1") == 9);
6498 assert(column("123\t") == 8);
6500 // Other tab widths are possible by specifying it explicitly:
6501 assert(column("\t", 4) == 4);
6502 assert(column("1\t", 4) == 4);
6503 assert(column("\t1", 4) == 5);
6504 assert(column("123\t", 4) == 4);
6506 // New lines reset the column number.
6507 assert(column("abc\n") == 0);
6508 assert(column("abc\n1") == 1);
6509 assert(column("abcdefg\r1234") == 4);
6510 assert(column("abc\u20281") == 1);
6511 assert(column("abc\u20291") == 1);
6512 assert(column("abc\u00851") == 1);
6513 assert(column("abc\u00861") == 5);
6516 size_t column(Range)(auto ref Range str, in size_t tabsize = 8)
6517 if (isConvertibleToString!Range)
6519 return column!(StringTypeOf!Range)(str, tabsize);
6524 assert(testAliasedString!column("abc\u00861"));
6527 @safe @nogc unittest
6529 import std.conv : to;
6530 import std.exception : assertCTFEable;
6534 assert(column(string.init) == 0);
6535 assert(column("") == 0);
6536 assert(column("\t") == 8);
6537 assert(column("abc\t") == 8);
6538 assert(column("12345678\t") == 16);
6542 /******************************************
6543 * Wrap text into a paragraph.
6545 * The input text string s is formed into a paragraph
6546 * by breaking it up into a sequence of lines, delineated
6547 * by \n, such that the number of columns is not exceeded
6549 * The last line is terminated with a \n.
6551 * s = text string to be wrapped
6552 * columns = maximum number of _columns in the paragraph
6553 * firstindent = string used to _indent first line of the paragraph
6554 * indent = string to use to _indent following lines of the paragraph
6555 * tabsize = column spacing of tabs in firstindent[] and indent[]
6557 * resulting paragraph as an allocated string
6560 S wrap(S)(S s, in size_t columns = 80, S firstindent = null,
6561 S indent = null, in size_t tabsize = 8)
6564 import std.uni : isWhite;
6565 typeof(s.dup) result;
6570 const indentcol = column(indent, tabsize);
6572 result.length = firstindent.length + s.length;
6573 result.length = firstindent.length;
6574 result[] = firstindent[];
6575 auto col = column(firstindent, tabsize);
6576 foreach (size_t i, dchar c; s)
6585 else if (col + 1 + (i - wordstart) > columns)
6596 result ~= s[wordstart .. i];
6597 col += i - wordstart;
6614 if (col + 1 + (s.length - wordstart) >= columns)
6619 else if (result.length != firstindent.length)
6621 result ~= s[wordstart .. s.length];
6631 assert(wrap("a short string", 7) == "a short\nstring\n");
6633 // wrap will not break inside of a word, but at the next space
6634 assert(wrap("a short string", 4) == "a\nshort\nstring\n");
6636 assert(wrap("a short string", 7, "\t") == "\ta\nshort\nstring\n");
6637 assert(wrap("a short string", 7, "\t", " ") == "\ta\n short\n string\n");
6642 import std.conv : to;
6643 import std.exception : assertCTFEable;
6647 assert(wrap(string.init) == "\n");
6648 assert(wrap(" a b df ") == "a b df\n");
6649 assert(wrap(" a b df ", 3) == "a b\ndf\n");
6650 assert(wrap(" a bc df ", 3) == "a\nbc\ndf\n");
6651 assert(wrap(" abcd df ", 3) == "abcd\ndf\n");
6652 assert(wrap("x") == "x\n");
6653 assert(wrap("u u") == "u u\n");
6654 assert(wrap("abcd", 3) == "\nabcd\n");
6655 assert(wrap("a de", 10, "\t", " ", 8) == "\ta\n de\n");
6659 /******************************************
6660 * Removes one level of indentation from a multi-line string.
6662 * This uniformly outdents the text as much as possible.
6663 * Whitespace-only lines are always converted to blank lines.
6665 * Does not allocate memory if it does not throw.
6668 * str = multi-line string
6674 * StringException if indentation is done with different sequences
6675 * of whitespace characters.
6677 S outdent(S)(S str) @safe pure
6680 return str.splitLines(Yes.keepTerminator).outdent().join();
6700 assert(pretty == ugly);
6704 /******************************************
6705 * Removes one level of indentation from an array of single-line strings.
6707 * This uniformly outdents the text as much as possible.
6708 * Whitespace-only lines are always converted to blank lines.
6711 * lines = array of single-line strings
6714 * lines[] is rewritten in place with outdented lines
6717 * StringException if indentation is done with different sequences
6718 * of whitespace characters.
6720 S[] outdent(S)(S[] lines) @safe pure
6723 import std.algorithm.searching : startsWith;
6730 static S leadingWhiteOf(S str)
6732 return str[ 0 .. $ - stripLeft(str).length ];
6736 foreach (ref line; lines)
6738 const stripped = line.stripLeft();
6742 line = line[line.chomp().length .. $];
6746 const indent = leadingWhiteOf(line);
6748 // Comparing number of code units instead of code points is OK here
6749 // because this function throws upon inconsistent indentation.
6750 if (shortestIndent is null || indent.length < shortestIndent.length)
6754 shortestIndent = indent;
6759 foreach (ref line; lines)
6761 const stripped = line.stripLeft();
6767 else if (line.startsWith(shortestIndent))
6769 line = line[shortestIndent.length .. $];
6773 throw new StringException("outdent: Inconsistent indentation");
6782 import std.conv : to;
6783 import std.exception : assertCTFEable;
6785 template outdent_testStr(S)
6787 enum S outdent_testStr =
6797 template outdent_expected(S)
6799 enum S outdent_expected =
6812 foreach (S; AliasSeq!(string, wstring, dstring))
6815 assert(blank.outdent() == blank);
6816 static assert(blank.outdent() == blank);
6818 enum S testStr1 = " \n \t\n ";
6819 enum S expected1 = "\n\n";
6820 assert(testStr1.outdent() == expected1);
6821 static assert(testStr1.outdent() == expected1);
6823 assert(testStr1[0..$-1].outdent() == expected1);
6824 static assert(testStr1[0..$-1].outdent() == expected1);
6826 enum S testStr2 = "a\n \t\nb";
6827 assert(testStr2.outdent() == testStr2);
6828 static assert(testStr2.outdent() == testStr2);
6847 assert(testStr3.outdent() == expected3);
6848 static assert(testStr3.outdent() == expected3);
6850 enum testStr4 = " X\r X\n X\r\n X\u2028 X\u2029 X";
6851 enum expected4 = "X\rX\nX\r\nX\u2028X\u2029X";
6852 assert(testStr4.outdent() == expected4);
6853 static assert(testStr4.outdent() == expected4);
6855 enum testStr5 = testStr4[0..$-1];
6856 enum expected5 = expected4[0..$-1];
6857 assert(testStr5.outdent() == expected5);
6858 static assert(testStr5.outdent() == expected5);
6860 enum testStr6 = " \r \n \r\n \u2028 \u2029";
6861 enum expected6 = "\r\n\r\n\u2028\u2029";
6862 assert(testStr6.outdent() == expected6);
6863 static assert(testStr6.outdent() == expected6);
6865 enum testStr7 = " a \n b ";
6866 enum expected7 = "a \nb ";
6867 assert(testStr7.outdent() == expected7);
6868 static assert(testStr7.outdent() == expected7);
6875 import std.exception : assertThrown;
6876 auto bad = " a\n\tb\n c";
6877 assertThrown!StringException(bad.outdent);
6880 /** Assume the given array of integers $(D arr) is a well-formed UTF string and
6881 return it typed as a UTF string.
6883 $(D ubyte) becomes $(D char), $(D ushort) becomes $(D wchar) and $(D uint)
6884 becomes $(D dchar). Type qualifiers are preserved.
6886 When compiled with debug mode, this function performs an extra check to make
6887 sure the return value is a valid Unicode string.
6890 arr = array of bytes, ubytes, shorts, ushorts, ints, or uints
6893 arr retyped as an array of chars, wchars, or dchars
6895 See_Also: $(LREF representation)
6897 auto assumeUTF(T)(T[] arr) pure
6898 if (staticIndexOf!(Unqual!T, ubyte, ushort, uint) != -1)
6900 import std.traits : ModifyTypePreservingTQ;
6901 import std.utf : validate;
6902 alias ToUTFType(U) = AliasSeq!(char, wchar, dchar)[U.sizeof / 2];
6903 auto asUTF = cast(ModifyTypePreservingTQ!(ToUTFType, T)[])arr;
6904 debug validate(asUTF);
6911 string a = "Hölo World";
6912 immutable(ubyte)[] b = a.representation;
6913 string c = b.assumeUTF;
6918 pure @system unittest
6920 import std.algorithm.comparison : equal;
6921 foreach (T; AliasSeq!(char[], wchar[], dchar[]))
6923 immutable T jti = "Hello World";
6926 static if (is(T == char[]))
6928 auto gt = cast(ubyte[]) jt;
6929 auto gtc = cast(const(ubyte)[])jt;
6930 auto gti = cast(immutable(ubyte)[])jt;
6932 else static if (is(T == wchar[]))
6934 auto gt = cast(ushort[]) jt;
6935 auto gtc = cast(const(ushort)[])jt;
6936 auto gti = cast(immutable(ushort)[])jt;
6938 else static if (is(T == dchar[]))
6940 auto gt = cast(uint[]) jt;
6941 auto gtc = cast(const(uint)[])jt;
6942 auto gti = cast(immutable(uint)[])jt;
6945 auto ht = assumeUTF(gt);
6946 auto htc = assumeUTF(gtc);
6947 auto hti = assumeUTF(gti);
6948 assert(equal(jt, ht));
6949 assert(equal(jt, htc));
6950 assert(equal(jt, hti));