doc: Describe limitations re Ada, D, and Go on FreeBSD
[official-gcc.git] / libphobos / src / std / string.d
blobca14c23747e01c6f4272ede26c6210527cff258e
1 // Written in the D programming language.
3 /**
4 String handling functions.
6 $(SCRIPT inhibitQuickIndex = 1;)
8 $(DIVC quickindex,
9 $(BOOKTABLE ,
10 $(TR $(TH Category) $(TH Functions) )
11 $(TR $(TDNW Searching)
12 $(TD
13 $(MYREF column)
14 $(MYREF indexOf)
15 $(MYREF indexOfAny)
16 $(MYREF indexOfNeither)
17 $(MYREF lastIndexOf)
18 $(MYREF lastIndexOfAny)
19 $(MYREF lastIndexOfNeither)
22 $(TR $(TDNW Comparison)
23 $(TD
24 $(MYREF isNumeric)
27 $(TR $(TDNW Mutation)
28 $(TD
29 $(MYREF capitalize)
32 $(TR $(TDNW Pruning and Filling)
33 $(TD
34 $(MYREF center)
35 $(MYREF chomp)
36 $(MYREF chompPrefix)
37 $(MYREF chop)
38 $(MYREF detabber)
39 $(MYREF detab)
40 $(MYREF entab)
41 $(MYREF entabber)
42 $(MYREF leftJustify)
43 $(MYREF outdent)
44 $(MYREF rightJustify)
45 $(MYREF strip)
46 $(MYREF stripLeft)
47 $(MYREF stripRight)
48 $(MYREF wrap)
51 $(TR $(TDNW Substitution)
52 $(TD
53 $(MYREF abbrev)
54 $(MYREF soundex)
55 $(MYREF soundexer)
56 $(MYREF succ)
57 $(MYREF tr)
58 $(MYREF translate)
61 $(TR $(TDNW Miscellaneous)
62 $(TD
63 $(MYREF assumeUTF)
64 $(MYREF fromStringz)
65 $(MYREF lineSplitter)
66 $(MYREF representation)
67 $(MYREF splitLines)
68 $(MYREF toStringz)
70 )))
72 Objects of types `string`, `wstring`, and `dstring` are value types
73 and cannot be mutated element-by-element. For using mutation during building
74 strings, use `char[]`, `wchar[]`, or `dchar[]`. The `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:
80 $(BOOKTABLE ,
81 $(TR $(TH Module) $(TH Functions) )
82 $(LEADINGROW Publicly imported functions)
83 $(TR $(TD std.algorithm)
84 $(TD
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)
90 $(TR $(TD std.array)
91 $(TD
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)
98 $(TR $(TD std.format)
99 $(TD
100 $(REF_SHORT format, std,format)
101 $(REF_SHORT sformat, std,format)
103 $(TR $(TD std.uni)
104 $(TD
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).
119 See_Also:
120 $(LIST
121 $(MREF std, algorithm) and
122 $(MREF std, range)
123 for generic range algorithms
125 $(MREF std, ascii)
126 for functions that work with ASCII strings
128 $(MREF std, uni)
129 for functions that work with unicode strings
132 Copyright: Copyright The D Language Foundation 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),
138 $(HTTP jmdavisprog.com, Jonathan M Davis),
139 and David L. 'SpottedTiger' Davis
141 Source: $(PHOBOSSRC std/string.d)
144 module std.string;
146 version (StdUnittest)
148 private:
149 struct TestAliasedString
151 string get() @safe @nogc pure nothrow return scope { return _s; }
152 alias get this;
153 @disable this(this);
154 string _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.
165 return equal(a, b);
167 else
169 return a == b;
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,
182 save;
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;
205 @safe pure unittest
207 import std.exception : assertThrown;
208 auto bad = " a\n\tb\n c";
209 assertThrown!StringException(bad.outdent);
213 Params:
214 cString = A null-terminated c-style string.
216 Returns: A D-style array of `char`, `wchar` or `dchar` referencing the same
217 string. The returned array will retain the same type qualifiers as the input.
219 $(RED Important Note:) The returned array is a slice of the original buffer.
220 The original data is not changed and not copied.
222 inout(Char)[] fromStringz(Char)(return scope inout(Char)* cString) @nogc @system pure nothrow
223 if (isSomeChar!Char)
225 import core.stdc.stddef : wchar_t;
227 static if (is(immutable Char == immutable char))
228 import core.stdc.string : cstrlen = strlen;
229 else static if (is(immutable Char == immutable wchar_t))
230 import core.stdc.wchar_ : cstrlen = wcslen;
231 else
232 static size_t cstrlen(scope const Char* s)
234 const(Char)* p = s;
235 while (*p)
236 ++p;
237 return p - s;
240 return cString ? cString[0 .. cstrlen(cString)] : null;
243 /// ditto
244 inout(Char)[] fromStringz(Char)(return scope inout(Char)[] cString) @nogc @safe pure nothrow
245 if (isSomeChar!Char)
247 foreach (i; 0 .. cString.length)
248 if (cString[i] == '\0')
249 return cString[0 .. i];
251 return cString;
255 @system pure unittest
257 assert(fromStringz("foo\0"c.ptr) == "foo"c);
258 assert(fromStringz("foo\0"w.ptr) == "foo"w);
259 assert(fromStringz("foo\0"d.ptr) == "foo"d);
261 assert(fromStringz("福\0"c.ptr) == "福"c);
262 assert(fromStringz("福\0"w.ptr) == "福"w);
263 assert(fromStringz("福\0"d.ptr) == "福"d);
267 @nogc @safe pure nothrow unittest
269 struct C
271 char[32] name;
273 assert(C("foo\0"c).name.fromStringz() == "foo"c);
275 struct W
277 wchar[32] name;
279 assert(W("foo\0"w).name.fromStringz() == "foo"w);
281 struct D
283 dchar[32] name;
285 assert(D("foo\0"d).name.fromStringz() == "foo"d);
288 @nogc @safe pure nothrow unittest
290 assert( string.init.fromStringz() == ""c);
291 assert(wstring.init.fromStringz() == ""w);
292 assert(dstring.init.fromStringz() == ""d);
294 immutable char[3] a = "foo"c;
295 assert(a.fromStringz() == "foo"c);
297 immutable wchar[3] b = "foo"w;
298 assert(b.fromStringz() == "foo"w);
300 immutable dchar[3] c = "foo"d;
301 assert(c.fromStringz() == "foo"d);
304 @system pure unittest
306 char* a = null;
307 assert(fromStringz(a) == null);
308 wchar* b = null;
309 assert(fromStringz(b) == null);
310 dchar* c = null;
311 assert(fromStringz(c) == null);
313 const char* d = "foo\0";
314 assert(fromStringz(d) == "foo");
316 immutable char* e = "foo\0";
317 assert(fromStringz(e) == "foo");
319 const wchar* f = "foo\0";
320 assert(fromStringz(f) == "foo");
322 immutable wchar* g = "foo\0";
323 assert(fromStringz(g) == "foo");
325 const dchar* h = "foo\0";
326 assert(fromStringz(h) == "foo");
328 immutable dchar* i = "foo\0";
329 assert(fromStringz(i) == "foo");
331 immutable wchar z = 0x0000;
332 // Test some surrogate pairs
333 // high surrogates are in the range 0xD800 .. 0xDC00
334 // low surrogates are in the range 0xDC00 .. 0xE000
335 // since UTF16 doesn't specify endianness we test both.
336 foreach (wchar[] t; [[0xD800, 0xDC00], [0xD800, 0xE000], [0xDC00, 0xDC00],
337 [0xDC00, 0xE000], [0xDA00, 0xDE00]])
339 immutable hi = t[0], lo = t[1];
340 assert(fromStringz([hi, lo, z].ptr) == [hi, lo]);
341 assert(fromStringz([lo, hi, z].ptr) == [lo, hi]);
346 Params:
347 s = A D-style string.
349 Returns: A C-style null-terminated string equivalent to `s`. `s`
350 must not contain embedded `'\0'`'s as any C function will treat the
351 first `'\0'` that it sees as the end of the string. If `s.empty` is
352 `true`, then a string containing only `'\0'` is returned.
354 $(RED Important Note:) When passing a `char*` to a C function, and the C
355 function keeps it around for any reason, make sure that you keep a
356 reference to it in your D code. Otherwise, it may become invalid during a
357 garbage collection cycle and cause a nasty bug when the C code tries to use
360 immutable(char)* toStringz(scope const(char)[] s) @trusted pure nothrow
361 out (result)
363 import core.stdc.string : strlen, memcmp;
364 if (result)
366 auto slen = s.length;
367 while (slen > 0 && s[slen-1] == 0) --slen;
368 assert(strlen(result) == slen,
369 "The result c string is shorter than the in input string");
370 assert(result[0 .. slen] == s[0 .. slen],
371 "The input and result string are not equal");
376 import std.exception : assumeUnique;
378 if (s.empty) return "".ptr;
380 /+ Unfortunately, this isn't reliable.
381 We could make this work if string literals are put
382 in read-only memory and we test if s[] is pointing into
383 that.
385 /* Peek past end of s[], if it's 0, no conversion necessary.
386 * Note that the compiler will put a 0 past the end of static
387 * strings, and the storage allocator will put a 0 past the end
388 * of newly allocated char[]'s.
390 char* p = &s[0] + s.length;
391 if (*p == 0)
392 return s;
395 // Need to make a copy
396 auto copy = new char[s.length + 1];
397 copy[0 .. s.length] = s[];
398 copy[s.length] = 0;
400 return &assumeUnique(copy)[0];
404 pure nothrow @system unittest
406 import core.stdc.string : strlen;
407 import std.conv : to;
409 auto p = toStringz("foo");
410 assert(strlen(p) == 3);
411 const(char)[] foo = "abbzxyzzy";
412 p = toStringz(foo[3 .. 5]);
413 assert(strlen(p) == 2);
415 string test = "";
416 p = toStringz(test);
417 assert(*p == 0);
419 test = "\0";
420 p = toStringz(test);
421 assert(*p == 0);
423 test = "foo\0";
424 p = toStringz(test);
425 assert(p[0] == 'f' && p[1] == 'o' && p[2] == 'o' && p[3] == 0);
427 const string test2 = "";
428 p = toStringz(test2);
429 assert(*p == 0);
431 assert(toStringz([]) is toStringz(""));
434 pure nothrow @system unittest // https://issues.dlang.org/show_bug.cgi?id=15136
436 static struct S
438 immutable char[5] str;
439 ubyte foo;
440 this(char[5] str) pure nothrow
442 this.str = str;
445 auto s = S("01234");
446 const str = s.str.toStringz;
447 assert(str !is s.str.ptr);
448 assert(*(str + 5) == 0); // Null terminated.
449 s.foo = 42;
450 assert(*(str + 5) == 0); // Still null terminated.
455 Flag indicating whether a search is case-sensitive.
457 alias CaseSensitive = Flag!"caseSensitive";
460 Searches for character in range.
462 Params:
463 s = string or InputRange of characters to search in correct UTF format
464 c = character to search for
465 startIdx = starting index to a well-formed code point
466 cs = `Yes.caseSensitive` or `No.caseSensitive`
468 Returns:
469 the index of the first occurrence of `c` in `s` with
470 respect to the start index `startIdx`. If `c`
471 is not found, then `-1` is returned.
472 If `c` is found the value of the returned index is at least
473 `startIdx`.
474 If the parameters are not valid UTF, the result will still
475 be in the range [-1 .. s.length], but will not be reliable otherwise.
477 Throws:
478 If the sequence starting at `startIdx` does not represent a well
479 formed codepoint, then a $(REF UTFException, std,utf) may be thrown.
481 See_Also: $(REF countUntil, std,algorithm,searching)
483 ptrdiff_t indexOf(Range)(Range s, dchar c, CaseSensitive cs = Yes.caseSensitive)
484 if (isInputRange!Range && isSomeChar!(ElementType!Range) && !isSomeString!Range)
486 return _indexOf(s, c, cs);
489 /// Ditto
490 ptrdiff_t indexOf(C)(scope const(C)[] s, dchar c, CaseSensitive cs = Yes.caseSensitive)
491 if (isSomeChar!C)
493 return _indexOf(s, c, cs);
496 /// Ditto
497 ptrdiff_t indexOf(Range)(Range s, dchar c, size_t startIdx, CaseSensitive cs = Yes.caseSensitive)
498 if (isInputRange!Range && isSomeChar!(ElementType!Range) && !isSomeString!Range)
500 return _indexOf(s, c, startIdx, cs);
503 /// Ditto
504 ptrdiff_t indexOf(C)(scope const(C)[] s, dchar c, size_t startIdx, CaseSensitive cs = Yes.caseSensitive)
505 if (isSomeChar!C)
507 return _indexOf(s, c, startIdx, cs);
511 @safe pure unittest
513 import std.typecons : No;
515 string s = "Hello World";
516 assert(indexOf(s, 'W') == 6);
517 assert(indexOf(s, 'Z') == -1);
518 assert(indexOf(s, 'w', No.caseSensitive) == 6);
522 @safe pure unittest
524 import std.typecons : No;
526 string s = "Hello World";
527 assert(indexOf(s, 'W', 4) == 6);
528 assert(indexOf(s, 'Z', 100) == -1);
529 assert(indexOf(s, 'w', 3, No.caseSensitive) == 6);
532 @safe pure unittest
534 assert(testAliasedString!indexOf("std/string.d", '/'));
536 enum S : string { a = "std/string.d" }
537 assert(S.a.indexOf('/') == 3);
539 char[S.a.length] sa = S.a[];
540 assert(sa.indexOf('/') == 3);
543 @safe pure unittest
545 import std.conv : to;
546 import std.exception : assertCTFEable;
547 import std.traits : EnumMembers;
548 import std.utf : byChar, byWchar, byDchar;
550 assertCTFEable!(
552 static foreach (S; AliasSeq!(string, wstring, dstring))
554 assert(indexOf(cast(S) null, cast(dchar)'a') == -1);
555 assert(indexOf(to!S("def"), cast(dchar)'a') == -1);
556 assert(indexOf(to!S("abba"), cast(dchar)'a') == 0);
557 assert(indexOf(to!S("def"), cast(dchar)'f') == 2);
559 assert(indexOf(to!S("def"), cast(dchar)'a', No.caseSensitive) == -1);
560 assert(indexOf(to!S("def"), cast(dchar)'a', No.caseSensitive) == -1);
561 assert(indexOf(to!S("Abba"), cast(dchar)'a', No.caseSensitive) == 0);
562 assert(indexOf(to!S("def"), cast(dchar)'F', No.caseSensitive) == 2);
563 assert(indexOf(to!S("ödef"), 'ö', No.caseSensitive) == 0);
565 S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
566 assert(indexOf("def", cast(char)'f', No.caseSensitive) == 2);
567 assert(indexOf(sPlts, cast(char)'P', No.caseSensitive) == 23);
568 assert(indexOf(sPlts, cast(char)'R', No.caseSensitive) == 2);
571 foreach (cs; EnumMembers!CaseSensitive)
573 assert(indexOf("hello\U00010143\u0100\U00010143", '\u0100', cs) == 9);
574 assert(indexOf("hello\U00010143\u0100\U00010143"w, '\u0100', cs) == 7);
575 assert(indexOf("hello\U00010143\u0100\U00010143"d, '\u0100', cs) == 6);
577 assert(indexOf("hello\U00010143\u0100\U00010143".byChar, '\u0100', cs) == 9);
578 assert(indexOf("hello\U00010143\u0100\U00010143".byWchar, '\u0100', cs) == 7);
579 assert(indexOf("hello\U00010143\u0100\U00010143".byDchar, '\u0100', cs) == 6);
581 assert(indexOf("hello\U000007FF\u0100\U00010143".byChar, 'l', cs) == 2);
582 assert(indexOf("hello\U000007FF\u0100\U00010143".byChar, '\u0100', cs) == 7);
583 assert(indexOf("hello\U0000EFFF\u0100\U00010143".byChar, '\u0100', cs) == 8);
585 assert(indexOf("hello\U00010100".byWchar, '\U00010100', cs) == 5);
586 assert(indexOf("hello\U00010100".byWchar, '\U00010101', cs) == -1);
589 char[10] fixedSizeArray = "0123456789";
590 assert(indexOf(fixedSizeArray, '2') == 2);
594 @safe pure unittest
596 assert(testAliasedString!indexOf("std/string.d", '/', 0));
597 assert(testAliasedString!indexOf("std/string.d", '/', 1));
598 assert(testAliasedString!indexOf("std/string.d", '/', 4));
600 enum S : string { a = "std/string.d" }
601 assert(S.a.indexOf('/', 0) == 3);
602 assert(S.a.indexOf('/', 1) == 3);
603 assert(S.a.indexOf('/', 4) == -1);
605 char[S.a.length] sa = S.a[];
606 assert(sa.indexOf('/', 0) == 3);
607 assert(sa.indexOf('/', 1) == 3);
608 assert(sa.indexOf('/', 4) == -1);
611 @safe pure unittest
613 import std.conv : to;
614 import std.traits : EnumMembers;
615 import std.utf : byCodeUnit, byChar, byWchar;
617 assert("hello".byCodeUnit.indexOf(cast(dchar)'l', 1) == 2);
618 assert("hello".byWchar.indexOf(cast(dchar)'l', 1) == 2);
619 assert("hello".byWchar.indexOf(cast(dchar)'l', 6) == -1);
621 static foreach (S; AliasSeq!(string, wstring, dstring))
623 assert(indexOf(cast(S) null, cast(dchar)'a', 1) == -1);
624 assert(indexOf(to!S("def"), cast(dchar)'a', 1) == -1);
625 assert(indexOf(to!S("abba"), cast(dchar)'a', 1) == 3);
626 assert(indexOf(to!S("def"), cast(dchar)'f', 1) == 2);
628 assert((to!S("def")).indexOf(cast(dchar)'a', 1,
629 No.caseSensitive) == -1);
630 assert(indexOf(to!S("def"), cast(dchar)'a', 1,
631 No.caseSensitive) == -1);
632 assert(indexOf(to!S("def"), cast(dchar)'a', 12,
633 No.caseSensitive) == -1);
634 assert(indexOf(to!S("AbbA"), cast(dchar)'a', 2,
635 No.caseSensitive) == 3);
636 assert(indexOf(to!S("def"), cast(dchar)'F', 2, No.caseSensitive) == 2);
638 S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
639 assert(indexOf("def", cast(char)'f', cast(uint) 2,
640 No.caseSensitive) == 2);
641 assert(indexOf(sPlts, cast(char)'P', 12, No.caseSensitive) == 23);
642 assert(indexOf(sPlts, cast(char)'R', cast(ulong) 1,
643 No.caseSensitive) == 2);
646 foreach (cs; EnumMembers!CaseSensitive)
648 assert(indexOf("hello\U00010143\u0100\U00010143", '\u0100', 2, cs)
649 == 9);
650 assert(indexOf("hello\U00010143\u0100\U00010143"w, '\u0100', 3, cs)
651 == 7);
652 assert(indexOf("hello\U00010143\u0100\U00010143"d, '\u0100', 6, cs)
653 == 6);
657 private ptrdiff_t _indexOf(Range)(Range s, dchar c, CaseSensitive cs = Yes.caseSensitive)
658 if (isInputRange!Range && isSomeChar!(ElementType!Range))
660 static import std.ascii;
661 static import std.uni;
662 import std.utf : byDchar, byCodeUnit, UTFException, codeLength;
663 alias Char = Unqual!(ElementEncodingType!Range);
665 if (cs == Yes.caseSensitive)
667 static if (Char.sizeof == 1 && isSomeString!Range)
669 if (std.ascii.isASCII(c) && !__ctfe)
670 { // Plain old ASCII
671 static ptrdiff_t trustedmemchr(Range s, char c) @trusted
673 import core.stdc.string : memchr;
674 const p = cast(const(Char)*)memchr(s.ptr, c, s.length);
675 return p ? p - s.ptr : -1;
678 return trustedmemchr(s, cast(char) c);
682 static if (Char.sizeof == 1)
684 if (c <= 0x7F)
686 ptrdiff_t i;
687 foreach (const c2; s)
689 if (c == c2)
690 return i;
691 ++i;
694 else
696 ptrdiff_t i;
697 foreach (const c2; s.byDchar())
699 if (c == c2)
700 return i;
701 i += codeLength!Char(c2);
705 else static if (Char.sizeof == 2)
707 if (c <= 0xFFFF)
709 ptrdiff_t i;
710 foreach (const c2; s)
712 if (c == c2)
713 return i;
714 ++i;
717 else if (c <= 0x10FFFF)
719 // Encode UTF-16 surrogate pair
720 const wchar c1 = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
721 const wchar c2 = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00);
722 ptrdiff_t i;
723 for (auto r = s.byCodeUnit(); !r.empty; r.popFront())
725 if (c1 == r.front)
727 r.popFront();
728 if (r.empty) // invalid UTF - missing second of pair
729 break;
730 if (c2 == r.front)
731 return i;
732 ++i;
734 ++i;
738 else static if (Char.sizeof == 4)
740 ptrdiff_t i;
741 foreach (const c2; s)
743 if (c == c2)
744 return i;
745 ++i;
748 else
749 static assert(0);
750 return -1;
752 else
754 if (std.ascii.isASCII(c))
755 { // Plain old ASCII
756 immutable c1 = cast(char) std.ascii.toLower(c);
758 ptrdiff_t i;
759 foreach (const c2; s.byCodeUnit())
761 if (c1 == std.ascii.toLower(c2))
762 return i;
763 ++i;
766 else
767 { // c is a universal character
768 immutable c1 = std.uni.toLower(c);
770 ptrdiff_t i;
771 foreach (const c2; s.byDchar())
773 if (c1 == std.uni.toLower(c2))
774 return i;
775 i += codeLength!Char(c2);
779 return -1;
782 private ptrdiff_t _indexOf(Range)(Range s, dchar c, size_t startIdx, CaseSensitive cs = Yes.caseSensitive)
783 if (isInputRange!Range && isSomeChar!(ElementType!Range))
785 static if (isSomeString!(typeof(s)) ||
786 (hasSlicing!(typeof(s)) && hasLength!(typeof(s))))
788 if (startIdx < s.length)
790 ptrdiff_t foundIdx = indexOf(s[startIdx .. $], c, cs);
791 if (foundIdx != -1)
793 return foundIdx + cast(ptrdiff_t) startIdx;
797 else
799 foreach (i; 0 .. startIdx)
801 if (s.empty)
802 return -1;
803 s.popFront();
805 ptrdiff_t foundIdx = indexOf(s, c, cs);
806 if (foundIdx != -1)
808 return foundIdx + cast(ptrdiff_t) startIdx;
811 return -1;
814 private template _indexOfStr(CaseSensitive cs)
816 private ptrdiff_t _indexOfStr(Range, Char)(Range s, const(Char)[] sub)
817 if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
818 isSomeChar!Char)
820 alias Char1 = Unqual!(ElementEncodingType!Range);
822 static if (isSomeString!Range)
824 static if (is(Char1 == Char) && cs == Yes.caseSensitive)
826 import std.algorithm.searching : countUntil;
827 return s.representation.countUntil(sub.representation);
829 else
831 import std.algorithm.searching : find;
833 const(Char1)[] balance;
834 static if (cs == Yes.caseSensitive)
836 balance = find(s, sub);
838 else
840 balance = find!
841 ((a, b) => toLower(a) == toLower(b))
842 (s, sub);
844 return () @trusted { return balance.empty ? -1 : balance.ptr - s.ptr; } ();
847 else
849 if (s.empty)
850 return -1;
851 if (sub.empty)
852 return 0; // degenerate case
854 import std.utf : byDchar, codeLength;
855 auto subr = sub.byDchar; // decode sub[] by dchar's
856 dchar sub0 = subr.front; // cache first character of sub[]
857 subr.popFront();
859 // Special case for single character search
860 if (subr.empty)
861 return indexOf(s, sub0, cs);
863 static if (cs == No.caseSensitive)
864 sub0 = toLower(sub0);
866 /* Classic double nested loop search algorithm
868 ptrdiff_t index = 0; // count code unit index into s
869 for (auto sbydchar = s.byDchar(); !sbydchar.empty; sbydchar.popFront())
871 dchar c2 = sbydchar.front;
872 static if (cs == No.caseSensitive)
873 c2 = toLower(c2);
874 if (c2 == sub0)
876 auto s2 = sbydchar.save; // why s must be a forward range
877 foreach (c; subr.save)
879 s2.popFront();
880 if (s2.empty)
881 return -1;
882 static if (cs == Yes.caseSensitive)
884 if (c != s2.front)
885 goto Lnext;
887 else
889 if (toLower(c) != toLower(s2.front))
890 goto Lnext;
893 return index;
895 Lnext:
896 index += codeLength!Char1(c2);
898 return -1;
904 Searches for substring in `s`.
906 Params:
907 s = string or ForwardRange of characters to search in correct UTF format
908 sub = substring to search for
909 startIdx = the index into s to start searching from
910 cs = `Yes.caseSensitive` (default) or `No.caseSensitive`
912 Returns:
913 the index of the first occurrence of `sub` in `s` with
914 respect to the start index `startIdx`. If `sub` is not found,
915 then `-1` is returned.
916 If the arguments are not valid UTF, the result will still
917 be in the range [-1 .. s.length], but will not be reliable otherwise.
918 If `sub` is found the value of the returned index is at least
919 `startIdx`.
921 Throws:
922 If the sequence starting at `startIdx` does not represent a well
923 formed codepoint, then a $(REF UTFException, std,utf) may be thrown.
925 Bugs:
926 Does not work with case insensitive strings where the mapping of
927 tolower and toupper is not 1:1.
929 ptrdiff_t indexOf(Range, Char)(Range s, const(Char)[] sub)
930 if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
931 isSomeChar!Char)
933 return _indexOfStr!(Yes.caseSensitive)(s, sub);
936 /// Ditto
937 ptrdiff_t indexOf(Range, Char)(Range s, const(Char)[] sub, in CaseSensitive cs)
938 if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
939 isSomeChar!Char)
941 if (cs == Yes.caseSensitive)
942 return indexOf(s, sub);
943 else
944 return _indexOfStr!(No.caseSensitive)(s, sub);
947 /// Ditto
948 ptrdiff_t indexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub,
949 in size_t startIdx)
950 @safe
951 if (isSomeChar!Char1 && isSomeChar!Char2)
953 if (startIdx >= s.length)
954 return -1;
955 ptrdiff_t foundIdx = indexOf(s[startIdx .. $], sub);
956 if (foundIdx == -1)
957 return -1;
958 return foundIdx + cast(ptrdiff_t) startIdx;
961 /// Ditto
962 ptrdiff_t indexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub,
963 in size_t startIdx, in CaseSensitive cs)
964 @safe
965 if (isSomeChar!Char1 && isSomeChar!Char2)
967 if (startIdx >= s.length)
968 return -1;
969 ptrdiff_t foundIdx = indexOf(s[startIdx .. $], sub, cs);
970 if (foundIdx == -1)
971 return -1;
972 return foundIdx + cast(ptrdiff_t) startIdx;
976 @safe pure unittest
978 import std.typecons : No;
980 string s = "Hello World";
981 assert(indexOf(s, "Wo", 4) == 6);
982 assert(indexOf(s, "Zo", 100) == -1);
983 assert(indexOf(s, "wo", 3, No.caseSensitive) == 6);
987 @safe pure unittest
989 import std.typecons : No;
991 string s = "Hello World";
992 assert(indexOf(s, "Wo") == 6);
993 assert(indexOf(s, "Zo") == -1);
994 assert(indexOf(s, "wO", No.caseSensitive) == 6);
997 @safe pure nothrow @nogc unittest
999 string s = "Hello World";
1000 assert(indexOf(s, "Wo", 4) == 6);
1001 assert(indexOf(s, "Zo", 100) == -1);
1002 assert(indexOf(s, "Wo") == 6);
1003 assert(indexOf(s, "Zo") == -1);
1006 ptrdiff_t indexOf(Range, Char)(auto ref Range s, const(Char)[] sub)
1007 if (!(isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
1008 isSomeChar!Char) &&
1009 is(StringTypeOf!Range))
1011 return indexOf!(StringTypeOf!Range)(s, sub);
1014 ptrdiff_t indexOf(Range, Char)(auto ref Range s, const(Char)[] sub,
1015 in CaseSensitive cs)
1016 if (!(isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
1017 isSomeChar!Char) &&
1018 is(StringTypeOf!Range))
1020 return indexOf!(StringTypeOf!Range)(s, sub, cs);
1023 @safe pure nothrow @nogc unittest
1025 assert(testAliasedString!indexOf("std/string.d", "string"));
1028 @safe pure unittest
1030 import std.conv : to;
1031 import std.exception : assertCTFEable;
1032 import std.traits : EnumMembers;
1034 assertCTFEable!(
1036 static foreach (S; AliasSeq!(string, wstring, dstring))
1038 static foreach (T; AliasSeq!(string, wstring, dstring))
1040 assert(indexOf(cast(S) null, to!T("a")) == -1);
1041 assert(indexOf(to!S("def"), to!T("a")) == -1);
1042 assert(indexOf(to!S("abba"), to!T("a")) == 0);
1043 assert(indexOf(to!S("def"), to!T("f")) == 2);
1044 assert(indexOf(to!S("dfefffg"), to!T("fff")) == 3);
1045 assert(indexOf(to!S("dfeffgfff"), to!T("fff")) == 6);
1047 assert(indexOf(to!S("dfeffgfff"), to!T("a"), No.caseSensitive) == -1);
1048 assert(indexOf(to!S("def"), to!T("a"), No.caseSensitive) == -1);
1049 assert(indexOf(to!S("abba"), to!T("a"), No.caseSensitive) == 0);
1050 assert(indexOf(to!S("def"), to!T("f"), No.caseSensitive) == 2);
1051 assert(indexOf(to!S("dfefffg"), to!T("fff"), No.caseSensitive) == 3);
1052 assert(indexOf(to!S("dfeffgfff"), to!T("fff"), No.caseSensitive) == 6);
1054 S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
1055 S sMars = "Who\'s \'My Favorite Maritian?\'";
1057 assert(indexOf(sMars, to!T("MY fAVe"), No.caseSensitive) == -1);
1058 assert(indexOf(sMars, to!T("mY fAVOriTe"), No.caseSensitive) == 7);
1059 assert(indexOf(sPlts, to!T("mArS:"), No.caseSensitive) == 0);
1060 assert(indexOf(sPlts, to!T("rOcK"), No.caseSensitive) == 17);
1061 assert(indexOf(sPlts, to!T("Un."), No.caseSensitive) == 41);
1062 assert(indexOf(sPlts, to!T(sPlts), No.caseSensitive) == 0);
1064 assert(indexOf("\u0100", to!T("\u0100"), No.caseSensitive) == 0);
1066 // Thanks to Carlos Santander B. and zwang
1067 assert(indexOf("sus mejores cortesanos. Se embarcaron en el puerto de Dubai y",
1068 to!T("page-break-before"), No.caseSensitive) == -1);
1071 foreach (cs; EnumMembers!CaseSensitive)
1073 assert(indexOf("hello\U00010143\u0100\U00010143", to!S("\u0100"), cs) == 9);
1074 assert(indexOf("hello\U00010143\u0100\U00010143"w, to!S("\u0100"), cs) == 7);
1075 assert(indexOf("hello\U00010143\u0100\U00010143"d, to!S("\u0100"), cs) == 6);
1081 @safe pure @nogc nothrow
1082 unittest
1084 import std.traits : EnumMembers;
1085 import std.utf : byWchar;
1087 foreach (cs; EnumMembers!CaseSensitive)
1089 assert(indexOf("".byWchar, "", cs) == -1);
1090 assert(indexOf("hello".byWchar, "", cs) == 0);
1091 assert(indexOf("hello".byWchar, "l", cs) == 2);
1092 assert(indexOf("heLLo".byWchar, "LL", cs) == 2);
1093 assert(indexOf("hello".byWchar, "lox", cs) == -1);
1094 assert(indexOf("hello".byWchar, "betty", cs) == -1);
1095 assert(indexOf("hello\U00010143\u0100*\U00010143".byWchar, "\u0100*", cs) == 7);
1099 @safe pure unittest
1101 import std.conv : to;
1102 import std.traits : EnumMembers;
1104 static foreach (S; AliasSeq!(string, wstring, dstring))
1106 static foreach (T; AliasSeq!(string, wstring, dstring))
1108 assert(indexOf(cast(S) null, to!T("a"), 1337) == -1);
1109 assert(indexOf(to!S("def"), to!T("a"), 0) == -1);
1110 assert(indexOf(to!S("abba"), to!T("a"), 2) == 3);
1111 assert(indexOf(to!S("def"), to!T("f"), 1) == 2);
1112 assert(indexOf(to!S("dfefffg"), to!T("fff"), 1) == 3);
1113 assert(indexOf(to!S("dfeffgfff"), to!T("fff"), 5) == 6);
1115 assert(indexOf(to!S("dfeffgfff"), to!T("a"), 1, No.caseSensitive) == -1);
1116 assert(indexOf(to!S("def"), to!T("a"), 2, No.caseSensitive) == -1);
1117 assert(indexOf(to!S("abba"), to!T("a"), 3, No.caseSensitive) == 3);
1118 assert(indexOf(to!S("def"), to!T("f"), 1, No.caseSensitive) == 2);
1119 assert(indexOf(to!S("dfefffg"), to!T("fff"), 2, No.caseSensitive) == 3);
1120 assert(indexOf(to!S("dfeffgfff"), to!T("fff"), 4, No.caseSensitive) == 6);
1121 assert(indexOf(to!S("dfeffgffföä"), to!T("öä"), 9, No.caseSensitive) == 9,
1122 to!string(indexOf(to!S("dfeffgffföä"), to!T("öä"), 9, No.caseSensitive))
1123 ~ " " ~ S.stringof ~ " " ~ T.stringof);
1125 S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
1126 S sMars = "Who\'s \'My Favorite Maritian?\'";
1128 assert(indexOf(sMars, to!T("MY fAVe"), 10,
1129 No.caseSensitive) == -1);
1130 assert(indexOf(sMars, to!T("mY fAVOriTe"), 4, No.caseSensitive) == 7);
1131 assert(indexOf(sPlts, to!T("mArS:"), 0, No.caseSensitive) == 0);
1132 assert(indexOf(sPlts, to!T("rOcK"), 12, No.caseSensitive) == 17);
1133 assert(indexOf(sPlts, to!T("Un."), 32, No.caseSensitive) == 41);
1134 assert(indexOf(sPlts, to!T(sPlts), 0, No.caseSensitive) == 0);
1136 assert(indexOf("\u0100", to!T("\u0100"), 0, No.caseSensitive) == 0);
1138 // Thanks to Carlos Santander B. and zwang
1139 assert(indexOf("sus mejores cortesanos. Se embarcaron en el puerto de Dubai y",
1140 to!T("page-break-before"), 10, No.caseSensitive) == -1);
1142 // In order for indexOf with and without index to be consistent
1143 assert(indexOf(to!S(""), to!T("")) == indexOf(to!S(""), to!T(""), 0));
1146 foreach (cs; EnumMembers!CaseSensitive)
1148 assert(indexOf("hello\U00010143\u0100\U00010143", to!S("\u0100"),
1149 3, cs) == 9);
1150 assert(indexOf("hello\U00010143\u0100\U00010143"w, to!S("\u0100"),
1151 3, cs) == 7);
1152 assert(indexOf("hello\U00010143\u0100\U00010143"d, to!S("\u0100"),
1153 3, cs) == 6);
1159 Params:
1160 s = string to search
1161 c = character to search for
1162 startIdx = the index into s to start searching from
1163 cs = `Yes.caseSensitive` or `No.caseSensitive`
1165 Returns:
1166 The index of the last occurrence of `c` in `s`. If `c` is not
1167 found, then `-1` is returned. The `startIdx` slices `s` in
1168 the following way $(D s[0 .. startIdx]). `startIdx` represents a
1169 codeunit index in `s`.
1171 Throws:
1172 If the sequence ending at `startIdx` does not represent a well
1173 formed codepoint, then a $(REF UTFException, std,utf) may be thrown.
1175 `cs` indicates whether the comparisons are case sensitive.
1177 ptrdiff_t lastIndexOf(Char)(const(Char)[] s, in dchar c,
1178 in CaseSensitive cs = Yes.caseSensitive) @safe pure
1179 if (isSomeChar!Char)
1181 static import std.ascii, std.uni;
1182 import std.utf : canSearchInCodeUnits;
1183 if (cs == Yes.caseSensitive)
1185 if (canSearchInCodeUnits!Char(c))
1187 foreach_reverse (i, it; s)
1189 if (it == c)
1191 return i;
1195 else
1197 foreach_reverse (i, dchar it; s)
1199 if (it == c)
1201 return i;
1206 else
1208 if (std.ascii.isASCII(c))
1210 immutable c1 = std.ascii.toLower(c);
1212 foreach_reverse (i, it; s)
1214 immutable c2 = std.ascii.toLower(it);
1215 if (c1 == c2)
1217 return i;
1221 else
1223 immutable c1 = std.uni.toLower(c);
1225 foreach_reverse (i, dchar it; s)
1227 immutable c2 = std.uni.toLower(it);
1228 if (c1 == c2)
1230 return i;
1236 return -1;
1239 /// Ditto
1240 ptrdiff_t lastIndexOf(Char)(const(Char)[] s, in dchar c, in size_t startIdx,
1241 in CaseSensitive cs = Yes.caseSensitive) @safe pure
1242 if (isSomeChar!Char)
1244 if (startIdx <= s.length)
1246 return lastIndexOf(s[0u .. startIdx], c, cs);
1249 return -1;
1253 @safe pure unittest
1255 import std.typecons : No;
1257 string s = "Hello World";
1258 assert(lastIndexOf(s, 'l') == 9);
1259 assert(lastIndexOf(s, 'Z') == -1);
1260 assert(lastIndexOf(s, 'L', No.caseSensitive) == 9);
1264 @safe pure unittest
1266 import std.typecons : No;
1268 string s = "Hello World";
1269 assert(lastIndexOf(s, 'l', 4) == 3);
1270 assert(lastIndexOf(s, 'Z', 1337) == -1);
1271 assert(lastIndexOf(s, 'L', 7, No.caseSensitive) == 3);
1274 @safe pure unittest
1276 import std.conv : to;
1277 import std.exception : assertCTFEable;
1278 import std.traits : EnumMembers;
1280 assertCTFEable!(
1282 static foreach (S; AliasSeq!(string, wstring, dstring))
1284 assert(lastIndexOf(cast(S) null, 'a') == -1);
1285 assert(lastIndexOf(to!S("def"), 'a') == -1);
1286 assert(lastIndexOf(to!S("abba"), 'a') == 3);
1287 assert(lastIndexOf(to!S("def"), 'f') == 2);
1288 assert(lastIndexOf(to!S("ödef"), 'ö') == 0);
1290 assert(lastIndexOf(cast(S) null, 'a', No.caseSensitive) == -1);
1291 assert(lastIndexOf(to!S("def"), 'a', No.caseSensitive) == -1);
1292 assert(lastIndexOf(to!S("AbbA"), 'a', No.caseSensitive) == 3);
1293 assert(lastIndexOf(to!S("def"), 'F', No.caseSensitive) == 2);
1294 assert(lastIndexOf(to!S("ödef"), 'ö', No.caseSensitive) == 0);
1295 assert(lastIndexOf(to!S("i\u0100def"), to!dchar("\u0100"),
1296 No.caseSensitive) == 1);
1298 S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
1300 assert(lastIndexOf(to!S("def"), 'f', No.caseSensitive) == 2);
1301 assert(lastIndexOf(sPlts, 'M', No.caseSensitive) == 34);
1302 assert(lastIndexOf(sPlts, 'S', No.caseSensitive) == 40);
1305 foreach (cs; EnumMembers!CaseSensitive)
1307 assert(lastIndexOf("\U00010143\u0100\U00010143hello", '\u0100', cs) == 4);
1308 assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, '\u0100', cs) == 2);
1309 assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, '\u0100', cs) == 1);
1314 @safe pure unittest
1316 import std.conv : to;
1317 import std.traits : EnumMembers;
1319 static foreach (S; AliasSeq!(string, wstring, dstring))
1321 assert(lastIndexOf(cast(S) null, 'a') == -1);
1322 assert(lastIndexOf(to!S("def"), 'a') == -1);
1323 assert(lastIndexOf(to!S("abba"), 'a', 3) == 0);
1324 assert(lastIndexOf(to!S("deff"), 'f', 3) == 2);
1326 assert(lastIndexOf(cast(S) null, 'a', No.caseSensitive) == -1);
1327 assert(lastIndexOf(to!S("def"), 'a', No.caseSensitive) == -1);
1328 assert(lastIndexOf(to!S("AbbAa"), 'a', to!ushort(4), No.caseSensitive) == 3,
1329 to!string(lastIndexOf(to!S("AbbAa"), 'a', 4, No.caseSensitive)));
1330 assert(lastIndexOf(to!S("def"), 'F', 3, No.caseSensitive) == 2);
1332 S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
1334 assert(lastIndexOf(to!S("def"), 'f', 4, No.caseSensitive) == -1);
1335 assert(lastIndexOf(sPlts, 'M', sPlts.length -2, No.caseSensitive) == 34);
1336 assert(lastIndexOf(sPlts, 'S', sPlts.length -2, No.caseSensitive) == 40);
1339 foreach (cs; EnumMembers!CaseSensitive)
1341 assert(lastIndexOf("\U00010143\u0100\U00010143hello", '\u0100', cs) == 4);
1342 assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, '\u0100', cs) == 2);
1343 assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, '\u0100', cs) == 1);
1348 Params:
1349 s = string to search
1350 sub = substring to search for
1351 startIdx = the index into s to start searching from
1352 cs = `Yes.caseSensitive` or `No.caseSensitive`
1354 Returns:
1355 the index of the last occurrence of `sub` in `s`. If `sub` is
1356 not found, then `-1` is returned. The `startIdx` slices `s`
1357 in the following way $(D s[0 .. startIdx]). `startIdx` represents a
1358 codeunit index in `s`.
1360 Throws:
1361 If the sequence ending at `startIdx` does not represent a well
1362 formed codepoint, then a $(REF UTFException, std,utf) may be thrown.
1364 `cs` indicates whether the comparisons are case sensitive.
1366 ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub,
1367 in CaseSensitive cs = Yes.caseSensitive) @safe pure
1368 if (isSomeChar!Char1 && isSomeChar!Char2)
1370 import std.algorithm.searching : endsWith;
1371 import std.conv : to;
1372 import std.range.primitives : walkLength;
1373 static import std.uni;
1374 import std.utf : strideBack;
1375 if (sub.empty)
1376 return -1;
1378 if (walkLength(sub) == 1)
1379 return lastIndexOf(s, sub.front, cs);
1381 if (cs == Yes.caseSensitive)
1383 static if (is(immutable Char1 == immutable Char2))
1385 import core.stdc.string : memcmp;
1387 immutable c = sub[0];
1389 for (ptrdiff_t i = s.length - sub.length; i >= 0; --i)
1391 if (s[i] == c)
1393 if (__ctfe)
1395 if (s[i + 1 .. i + sub.length] == sub[1 .. $])
1396 return i;
1398 else
1400 auto trustedMemcmp(in void* s1, in void* s2, size_t n) @trusted
1402 return memcmp(s1, s2, n);
1404 if (trustedMemcmp(&s[i + 1], &sub[1],
1405 (sub.length - 1) * Char1.sizeof) == 0)
1406 return i;
1411 else
1413 for (size_t i = s.length; !s.empty;)
1415 if (s.endsWith(sub))
1416 return cast(ptrdiff_t) i - to!(const(Char1)[])(sub).length;
1418 i -= strideBack(s, i);
1419 s = s[0 .. i];
1423 else
1425 for (size_t i = s.length; !s.empty;)
1427 if (endsWith!((a, b) => std.uni.toLower(a) == std.uni.toLower(b))
1428 (s, sub))
1430 return cast(ptrdiff_t) i - to!(const(Char1)[])(sub).length;
1433 i -= strideBack(s, i);
1434 s = s[0 .. i];
1438 return -1;
1441 /// Ditto
1442 ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub,
1443 in size_t startIdx, in CaseSensitive cs = Yes.caseSensitive) @safe pure
1444 if (isSomeChar!Char1 && isSomeChar!Char2)
1446 if (startIdx <= s.length)
1448 return lastIndexOf(s[0u .. startIdx], sub, cs);
1451 return -1;
1455 @safe pure unittest
1457 import std.typecons : No;
1459 string s = "Hello World";
1460 assert(lastIndexOf(s, "ll") == 2);
1461 assert(lastIndexOf(s, "Zo") == -1);
1462 assert(lastIndexOf(s, "lL", No.caseSensitive) == 2);
1466 @safe pure unittest
1468 import std.typecons : No;
1470 string s = "Hello World";
1471 assert(lastIndexOf(s, "ll", 4) == 2);
1472 assert(lastIndexOf(s, "Zo", 128) == -1);
1473 assert(lastIndexOf(s, "lL", 3, No.caseSensitive) == -1);
1476 @safe pure unittest
1478 import std.conv : to;
1480 static foreach (S; AliasSeq!(string, wstring, dstring))
1482 auto r = to!S("").lastIndexOf("hello");
1483 assert(r == -1, to!string(r));
1485 r = to!S("hello").lastIndexOf("");
1486 assert(r == -1, to!string(r));
1488 r = to!S("").lastIndexOf("");
1489 assert(r == -1, to!string(r));
1493 @safe pure unittest
1495 import std.conv : to;
1496 import std.exception : assertCTFEable;
1497 import std.traits : EnumMembers;
1499 assertCTFEable!(
1501 static foreach (S; AliasSeq!(string, wstring, dstring))
1503 static foreach (T; AliasSeq!(string, wstring, dstring))
1505 enum typeStr = S.stringof ~ " " ~ T.stringof;
1507 assert(lastIndexOf(cast(S) null, to!T("a")) == -1, typeStr);
1508 assert(lastIndexOf(to!S("abcdefcdef"), to!T("c")) == 6, typeStr);
1509 assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd")) == 6, typeStr);
1510 assert(lastIndexOf(to!S("abcdefcdef"), to!T("ef")) == 8, typeStr);
1511 assert(lastIndexOf(to!S("abcdefCdef"), to!T("c")) == 2, typeStr);
1512 assert(lastIndexOf(to!S("abcdefCdef"), to!T("cd")) == 2, typeStr);
1513 assert(lastIndexOf(to!S("abcdefcdef"), to!T("x")) == -1, typeStr);
1514 assert(lastIndexOf(to!S("abcdefcdef"), to!T("xy")) == -1, typeStr);
1515 assert(lastIndexOf(to!S("abcdefcdef"), to!T("")) == -1, typeStr);
1516 assert(lastIndexOf(to!S("öabcdefcdef"), to!T("ö")) == 0, typeStr);
1518 assert(lastIndexOf(cast(S) null, to!T("a"), No.caseSensitive) == -1, typeStr);
1519 assert(lastIndexOf(to!S("abcdefCdef"), to!T("c"), No.caseSensitive) == 6, typeStr);
1520 assert(lastIndexOf(to!S("abcdefCdef"), to!T("cD"), No.caseSensitive) == 6, typeStr);
1521 assert(lastIndexOf(to!S("abcdefcdef"), to!T("x"), No.caseSensitive) == -1, typeStr);
1522 assert(lastIndexOf(to!S("abcdefcdef"), to!T("xy"), No.caseSensitive) == -1, typeStr);
1523 assert(lastIndexOf(to!S("abcdefcdef"), to!T(""), No.caseSensitive) == -1, typeStr);
1524 assert(lastIndexOf(to!S("öabcdefcdef"), to!T("ö"), No.caseSensitive) == 0, typeStr);
1526 assert(lastIndexOf(to!S("abcdefcdef"), to!T("c"), No.caseSensitive) == 6, typeStr);
1527 assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd"), No.caseSensitive) == 6, typeStr);
1528 assert(lastIndexOf(to!S("abcdefcdef"), to!T("def"), No.caseSensitive) == 7, typeStr);
1530 assert(lastIndexOf(to!S("ödfeffgfff"), to!T("ö"), Yes.caseSensitive) == 0);
1532 S sPlts = "Mars: the fourth Rock (Planet) from the Sun.";
1533 S sMars = "Who\'s \'My Favorite Maritian?\'";
1535 assert(lastIndexOf(sMars, to!T("RiTE maR"), No.caseSensitive) == 14, typeStr);
1536 assert(lastIndexOf(sPlts, to!T("FOuRTh"), No.caseSensitive) == 10, typeStr);
1537 assert(lastIndexOf(sMars, to!T("whO\'s \'MY"), No.caseSensitive) == 0, typeStr);
1538 assert(lastIndexOf(sMars, to!T(sMars), No.caseSensitive) == 0, typeStr);
1541 foreach (cs; EnumMembers!CaseSensitive)
1543 enum csString = to!string(cs);
1545 assert(lastIndexOf("\U00010143\u0100\U00010143hello", to!S("\u0100"), cs) == 4, csString);
1546 assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, to!S("\u0100"), cs) == 2, csString);
1547 assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, to!S("\u0100"), cs) == 1, csString);
1553 // https://issues.dlang.org/show_bug.cgi?id=13529
1554 @safe pure unittest
1556 import std.conv : to;
1557 static foreach (S; AliasSeq!(string, wstring, dstring))
1559 static foreach (T; AliasSeq!(string, wstring, dstring))
1561 enum typeStr = S.stringof ~ " " ~ T.stringof;
1562 auto idx = lastIndexOf(to!T("Hällö Wörldö ö"),to!S("ö ö"));
1563 assert(idx != -1, to!string(idx) ~ " " ~ typeStr);
1565 idx = lastIndexOf(to!T("Hällö Wörldö ö"),to!S("ö öd"));
1566 assert(idx == -1, to!string(idx) ~ " " ~ typeStr);
1571 @safe pure unittest
1573 import std.conv : to;
1574 import std.traits : EnumMembers;
1576 static foreach (S; AliasSeq!(string, wstring, dstring))
1578 static foreach (T; AliasSeq!(string, wstring, dstring))
1580 enum typeStr = S.stringof ~ " " ~ T.stringof;
1582 assert(lastIndexOf(cast(S) null, to!T("a")) == -1, typeStr);
1583 assert(lastIndexOf(to!S("abcdefcdef"), to!T("c"), 5) == 2, typeStr);
1584 assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd"), 3) == -1, typeStr);
1585 assert(lastIndexOf(to!S("abcdefcdef"), to!T("ef"), 6) == 4, typeStr ~
1586 format(" %u", lastIndexOf(to!S("abcdefcdef"), to!T("ef"), 6)));
1587 assert(lastIndexOf(to!S("abcdefCdef"), to!T("c"), 5) == 2, typeStr);
1588 assert(lastIndexOf(to!S("abcdefCdef"), to!T("cd"), 3) == -1, typeStr);
1589 assert(lastIndexOf(to!S("abcdefcdefx"), to!T("x"), 1) == -1, typeStr);
1590 assert(lastIndexOf(to!S("abcdefcdefxy"), to!T("xy"), 6) == -1, typeStr);
1591 assert(lastIndexOf(to!S("abcdefcdef"), to!T(""), 8) == -1, typeStr);
1592 assert(lastIndexOf(to!S("öafö"), to!T("ö"), 3) == 0, typeStr ~
1593 to!string(lastIndexOf(to!S("öafö"), to!T("ö"), 3))); //BUG 10472
1595 assert(lastIndexOf(cast(S) null, to!T("a"), 1, No.caseSensitive) == -1, typeStr);
1596 assert(lastIndexOf(to!S("abcdefCdef"), to!T("c"), 5, No.caseSensitive) == 2, typeStr);
1597 assert(lastIndexOf(to!S("abcdefCdef"), to!T("cD"), 4, No.caseSensitive) == 2, typeStr ~
1598 " " ~ to!string(lastIndexOf(to!S("abcdefCdef"), to!T("cD"), 3, No.caseSensitive)));
1599 assert(lastIndexOf(to!S("abcdefcdef"), to!T("x"),3 , No.caseSensitive) == -1, typeStr);
1600 assert(lastIndexOf(to!S("abcdefcdefXY"), to!T("xy"), 4, No.caseSensitive) == -1, typeStr);
1601 assert(lastIndexOf(to!S("abcdefcdef"), to!T(""), 7, No.caseSensitive) == -1, typeStr);
1603 assert(lastIndexOf(to!S("abcdefcdef"), to!T("c"), 4, No.caseSensitive) == 2, typeStr);
1604 assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd"), 4, No.caseSensitive) == 2, typeStr);
1605 assert(lastIndexOf(to!S("abcdefcdef"), to!T("def"), 6, No.caseSensitive) == 3, typeStr);
1606 assert(lastIndexOf(to!S(""), to!T(""), 0) == lastIndexOf(to!S(""), to!T("")), typeStr);
1609 foreach (cs; EnumMembers!CaseSensitive)
1611 enum csString = to!string(cs);
1613 assert(lastIndexOf("\U00010143\u0100\U00010143hello", to!S("\u0100"), 6, cs) == 4, csString);
1614 assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, to!S("\u0100"), 6, cs) == 2, csString);
1615 assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, to!S("\u0100"), 3, cs) == 1, csString);
1620 // https://issues.dlang.org/show_bug.cgi?id=20783
1621 @safe pure @nogc unittest
1623 enum lastIndex = "aa".lastIndexOf("ab");
1624 assert(lastIndex == -1);
1627 @safe pure @nogc unittest
1629 enum lastIndex = "hello hello hell h".lastIndexOf("hello");
1630 assert(lastIndex == 6);
1633 private ptrdiff_t indexOfAnyNeitherImpl(bool forward, bool any, Char, Char2)(
1634 const(Char)[] haystack, const(Char2)[] needles,
1635 in CaseSensitive cs = Yes.caseSensitive) @safe pure
1636 if (isSomeChar!Char && isSomeChar!Char2)
1638 import std.algorithm.searching : canFind, findAmong;
1639 if (cs == Yes.caseSensitive)
1641 static if (forward)
1643 static if (any)
1645 size_t n = haystack.findAmong(needles).length;
1646 return n ? haystack.length - n : -1;
1648 else
1650 foreach (idx, dchar hay; haystack)
1652 if (!canFind(needles, hay))
1654 return idx;
1659 else
1661 static if (any)
1663 import std.range : retro;
1664 import std.utf : strideBack;
1665 size_t n = haystack.retro.findAmong(needles).source.length;
1666 if (n)
1668 return n - haystack.strideBack(n);
1671 else
1673 foreach_reverse (idx, dchar hay; haystack)
1675 if (!canFind(needles, hay))
1677 return idx;
1683 else
1685 import std.range.primitives : walkLength;
1686 if (needles.length <= 16 && needles.walkLength(17))
1688 size_t si = 0;
1689 dchar[16] scratch = void;
1690 foreach ( dchar c; needles)
1692 scratch[si++] = toLower(c);
1695 static if (forward)
1697 foreach (i, dchar c; haystack)
1699 if (canFind(scratch[0 .. si], toLower(c)) == any)
1701 return i;
1705 else
1707 foreach_reverse (i, dchar c; haystack)
1709 if (canFind(scratch[0 .. si], toLower(c)) == any)
1711 return i;
1716 else
1718 static bool f(dchar a, dchar b)
1720 return toLower(a) == b;
1723 static if (forward)
1725 foreach (i, dchar c; haystack)
1727 if (canFind!f(needles, toLower(c)) == any)
1729 return i;
1733 else
1735 foreach_reverse (i, dchar c; haystack)
1737 if (canFind!f(needles, toLower(c)) == any)
1739 return i;
1746 return -1;
1750 Returns the index of the first occurrence of any of the elements in $(D
1751 needles) in `haystack`. If no element of `needles` is found,
1752 then `-1` is returned. The `startIdx` slices `haystack` in the
1753 following way $(D haystack[startIdx .. $]). `startIdx` represents a
1754 codeunit index in `haystack`. If the sequence ending at `startIdx`
1755 does not represent a well formed codepoint, then a $(REF UTFException, std,utf)
1756 may be thrown.
1758 Params:
1759 haystack = String to search for needles in.
1760 needles = Strings to search for in haystack.
1761 startIdx = slices haystack like this $(D haystack[startIdx .. $]). If
1762 the startIdx is greater than or equal to the length of haystack the
1763 functions returns `-1`.
1764 cs = Indicates whether the comparisons are case sensitive.
1766 ptrdiff_t indexOfAny(Char,Char2)(const(Char)[] haystack, const(Char2)[] needles,
1767 in CaseSensitive cs = Yes.caseSensitive) @safe pure
1768 if (isSomeChar!Char && isSomeChar!Char2)
1770 return indexOfAnyNeitherImpl!(true, true)(haystack, needles, cs);
1773 /// Ditto
1774 ptrdiff_t indexOfAny(Char,Char2)(const(Char)[] haystack, const(Char2)[] needles,
1775 in size_t startIdx, in CaseSensitive cs = Yes.caseSensitive) @safe pure
1776 if (isSomeChar!Char && isSomeChar!Char2)
1778 if (startIdx < haystack.length)
1780 ptrdiff_t foundIdx = indexOfAny(haystack[startIdx .. $], needles, cs);
1781 if (foundIdx != -1)
1783 return foundIdx + cast(ptrdiff_t) startIdx;
1787 return -1;
1791 @safe pure unittest
1793 import std.conv : to;
1795 ptrdiff_t i = "helloWorld".indexOfAny("Wr");
1796 assert(i == 5);
1797 i = "öällo world".indexOfAny("lo ");
1798 assert(i == 4, to!string(i));
1802 @safe pure unittest
1804 import std.conv : to;
1806 ptrdiff_t i = "helloWorld".indexOfAny("Wr", 4);
1807 assert(i == 5);
1809 i = "Foo öällo world".indexOfAny("lh", 3);
1810 assert(i == 8, to!string(i));
1813 @safe pure unittest
1815 import std.conv : to;
1817 static foreach (S; AliasSeq!(string, wstring, dstring))
1819 auto r = to!S("").indexOfAny("hello");
1820 assert(r == -1, to!string(r));
1822 r = to!S("hello").indexOfAny("");
1823 assert(r == -1, to!string(r));
1825 r = to!S("").indexOfAny("");
1826 assert(r == -1, to!string(r));
1830 @safe pure unittest
1832 import std.conv : to;
1833 import std.exception : assertCTFEable;
1835 assertCTFEable!(
1837 static foreach (S; AliasSeq!(string, wstring, dstring))
1839 static foreach (T; AliasSeq!(string, wstring, dstring))
1841 assert(indexOfAny(cast(S) null, to!T("a")) == -1);
1842 assert(indexOfAny(to!S("def"), to!T("rsa")) == -1);
1843 assert(indexOfAny(to!S("abba"), to!T("a")) == 0);
1844 assert(indexOfAny(to!S("def"), to!T("f")) == 2);
1845 assert(indexOfAny(to!S("dfefffg"), to!T("fgh")) == 1);
1846 assert(indexOfAny(to!S("dfeffgfff"), to!T("feg")) == 1);
1848 assert(indexOfAny(to!S("zfeffgfff"), to!T("ACDC"),
1849 No.caseSensitive) == -1);
1850 assert(indexOfAny(to!S("def"), to!T("MI6"),
1851 No.caseSensitive) == -1);
1852 assert(indexOfAny(to!S("abba"), to!T("DEA"),
1853 No.caseSensitive) == 0);
1854 assert(indexOfAny(to!S("def"), to!T("FBI"), No.caseSensitive) == 2);
1855 assert(indexOfAny(to!S("dfefffg"), to!T("NSA"), No.caseSensitive)
1856 == -1);
1857 assert(indexOfAny(to!S("dfeffgfff"), to!T("BND"),
1858 No.caseSensitive) == 0);
1859 assert(indexOfAny(to!S("dfeffgfff"), to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"),
1860 No.caseSensitive) == 0);
1862 assert(indexOfAny("\u0100", to!T("\u0100"), No.caseSensitive) == 0);
1869 @safe pure unittest
1871 import std.conv : to;
1872 import std.traits : EnumMembers;
1874 static foreach (S; AliasSeq!(string, wstring, dstring))
1876 static foreach (T; AliasSeq!(string, wstring, dstring))
1878 assert(indexOfAny(cast(S) null, to!T("a"), 1337) == -1);
1879 assert(indexOfAny(to!S("def"), to!T("AaF"), 0) == -1);
1880 assert(indexOfAny(to!S("abba"), to!T("NSa"), 2) == 3);
1881 assert(indexOfAny(to!S("def"), to!T("fbi"), 1) == 2);
1882 assert(indexOfAny(to!S("dfefffg"), to!T("foo"), 2) == 3);
1883 assert(indexOfAny(to!S("dfeffgfff"), to!T("fsb"), 5) == 6);
1885 assert(indexOfAny(to!S("dfeffgfff"), to!T("NDS"), 1,
1886 No.caseSensitive) == -1);
1887 assert(indexOfAny(to!S("def"), to!T("DRS"), 2,
1888 No.caseSensitive) == -1);
1889 assert(indexOfAny(to!S("abba"), to!T("SI"), 3,
1890 No.caseSensitive) == -1);
1891 assert(indexOfAny(to!S("deO"), to!T("ASIO"), 1,
1892 No.caseSensitive) == 2);
1893 assert(indexOfAny(to!S("dfefffg"), to!T("fbh"), 2,
1894 No.caseSensitive) == 3);
1895 assert(indexOfAny(to!S("dfeffgfff"), to!T("fEe"), 4,
1896 No.caseSensitive) == 4);
1897 assert(indexOfAny(to!S("dfeffgffföä"), to!T("föä"), 9,
1898 No.caseSensitive) == 9);
1900 assert(indexOfAny("\u0100", to!T("\u0100"), 0,
1901 No.caseSensitive) == 0);
1904 foreach (cs; EnumMembers!CaseSensitive)
1906 assert(indexOfAny("hello\U00010143\u0100\U00010143",
1907 to!S("e\u0100"), 3, cs) == 9);
1908 assert(indexOfAny("hello\U00010143\u0100\U00010143"w,
1909 to!S("h\u0100"), 3, cs) == 7);
1910 assert(indexOfAny("hello\U00010143\u0100\U00010143"d,
1911 to!S("l\u0100"), 5, cs) == 6);
1917 Returns the index of the last occurrence of any of the elements in $(D
1918 needles) in `haystack`. If no element of `needles` is found,
1919 then `-1` is returned. The `stopIdx` slices `haystack` in the
1920 following way $(D s[0 .. stopIdx]). `stopIdx` represents a codeunit
1921 index in `haystack`. If the sequence ending at `startIdx` does not
1922 represent a well formed codepoint, then a $(REF UTFException, std,utf) may be
1923 thrown.
1925 Params:
1926 haystack = String to search for needles in.
1927 needles = Strings to search for in haystack.
1928 stopIdx = slices haystack like this $(D haystack[0 .. stopIdx]). If
1929 the stopIdx is greater than or equal to the length of haystack the
1930 functions returns `-1`.
1931 cs = Indicates whether the comparisons are case sensitive.
1933 ptrdiff_t lastIndexOfAny(Char,Char2)(const(Char)[] haystack,
1934 const(Char2)[] needles, in CaseSensitive cs = Yes.caseSensitive)
1935 @safe pure
1936 if (isSomeChar!Char && isSomeChar!Char2)
1938 return indexOfAnyNeitherImpl!(false, true)(haystack, needles, cs);
1941 /// Ditto
1942 ptrdiff_t lastIndexOfAny(Char,Char2)(const(Char)[] haystack,
1943 const(Char2)[] needles, in size_t stopIdx,
1944 in CaseSensitive cs = Yes.caseSensitive) @safe pure
1945 if (isSomeChar!Char && isSomeChar!Char2)
1947 if (stopIdx <= haystack.length)
1949 return lastIndexOfAny(haystack[0u .. stopIdx], needles, cs);
1952 return -1;
1956 @safe pure unittest
1958 ptrdiff_t i = "helloWorld".lastIndexOfAny("Wlo");
1959 assert(i == 8);
1961 i = "Foo öäöllo world".lastIndexOfAny("öF");
1962 assert(i == 8);
1966 @safe pure unittest
1968 import std.conv : to;
1970 ptrdiff_t i = "helloWorld".lastIndexOfAny("Wlo", 4);
1971 assert(i == 3);
1973 i = "Foo öäöllo world".lastIndexOfAny("öF", 3);
1974 assert(i == 0);
1977 @safe pure unittest
1979 import std.conv : to;
1981 static foreach (S; AliasSeq!(string, wstring, dstring))
1983 auto r = to!S("").lastIndexOfAny("hello");
1984 assert(r == -1, to!string(r));
1986 r = to!S("hello").lastIndexOfAny("");
1987 assert(r == -1, to!string(r));
1989 r = to!S("").lastIndexOfAny("");
1990 assert(r == -1, to!string(r));
1994 @safe pure unittest
1996 import std.conv : to;
1997 import std.exception : assertCTFEable;
1999 assertCTFEable!(
2001 static foreach (S; AliasSeq!(string, wstring, dstring))
2003 static foreach (T; AliasSeq!(string, wstring, dstring))
2005 assert(lastIndexOfAny(cast(S) null, to!T("a")) == -1);
2006 assert(lastIndexOfAny(to!S("def"), to!T("rsa")) == -1);
2007 assert(lastIndexOfAny(to!S("abba"), to!T("a")) == 3);
2008 assert(lastIndexOfAny(to!S("def"), to!T("f")) == 2);
2009 assert(lastIndexOfAny(to!S("dfefffg"), to!T("fgh")) == 6);
2011 ptrdiff_t oeIdx = 9;
2012 if (is(S == wstring) || is(S == dstring))
2014 oeIdx = 8;
2017 auto foundOeIdx = lastIndexOfAny(to!S("dfeffgföf"), to!T("feg"));
2018 assert(foundOeIdx == oeIdx, to!string(foundOeIdx));
2020 assert(lastIndexOfAny(to!S("zfeffgfff"), to!T("ACDC"),
2021 No.caseSensitive) == -1);
2022 assert(lastIndexOfAny(to!S("def"), to!T("MI6"),
2023 No.caseSensitive) == -1);
2024 assert(lastIndexOfAny(to!S("abba"), to!T("DEA"),
2025 No.caseSensitive) == 3);
2026 assert(lastIndexOfAny(to!S("def"), to!T("FBI"),
2027 No.caseSensitive) == 2);
2028 assert(lastIndexOfAny(to!S("dfefffg"), to!T("NSA"),
2029 No.caseSensitive) == -1);
2031 oeIdx = 2;
2032 if (is(S == wstring) || is(S == dstring))
2034 oeIdx = 1;
2036 assert(lastIndexOfAny(to!S("ödfeffgfff"), to!T("BND"),
2037 No.caseSensitive) == oeIdx);
2039 assert(lastIndexOfAny("\u0100", to!T("\u0100"),
2040 No.caseSensitive) == 0);
2047 @safe pure unittest
2049 import std.conv : to;
2050 import std.exception : assertCTFEable;
2052 assertCTFEable!(
2054 static foreach (S; AliasSeq!(string, wstring, dstring))
2056 static foreach (T; AliasSeq!(string, wstring, dstring))
2058 enum typeStr = S.stringof ~ " " ~ T.stringof;
2060 assert(lastIndexOfAny(cast(S) null, to!T("a"), 1337) == -1,
2061 typeStr);
2062 assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("c"), 7) == 6,
2063 typeStr);
2064 assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("cd"), 5) == 3,
2065 typeStr);
2066 assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("ef"), 6) == 5,
2067 typeStr);
2068 assert(lastIndexOfAny(to!S("abcdefCdef"), to!T("c"), 8) == 2,
2069 typeStr);
2070 assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("x"), 7) == -1,
2071 typeStr);
2072 assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("xy"), 4) == -1,
2073 typeStr);
2074 assert(lastIndexOfAny(to!S("öabcdefcdef"), to!T("ö"), 2) == 0,
2075 typeStr);
2077 assert(lastIndexOfAny(cast(S) null, to!T("a"), 1337,
2078 No.caseSensitive) == -1, typeStr);
2079 assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("C"), 7,
2080 No.caseSensitive) == 6, typeStr);
2081 assert(lastIndexOfAny(to!S("ABCDEFCDEF"), to!T("cd"), 5,
2082 No.caseSensitive) == 3, typeStr);
2083 assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("EF"), 6,
2084 No.caseSensitive) == 5, typeStr);
2085 assert(lastIndexOfAny(to!S("ABCDEFcDEF"), to!T("C"), 8,
2086 No.caseSensitive) == 6, typeStr);
2087 assert(lastIndexOfAny(to!S("ABCDEFCDEF"), to!T("x"), 7,
2088 No.caseSensitive) == -1, typeStr);
2089 assert(lastIndexOfAny(to!S("abCdefcdef"), to!T("XY"), 4,
2090 No.caseSensitive) == -1, typeStr);
2091 assert(lastIndexOfAny(to!S("ÖABCDEFCDEF"), to!T("ö"), 2,
2092 No.caseSensitive) == 0, typeStr);
2100 Returns the index of the first occurrence of any character not an elements
2101 in `needles` in `haystack`. If all element of `haystack` are
2102 element of `needles` `-1` is returned.
2104 Params:
2105 haystack = String to search for needles in.
2106 needles = Strings to search for in haystack.
2107 startIdx = slices haystack like this $(D haystack[startIdx .. $]). If
2108 the startIdx is greater than or equal to the length of haystack the
2109 functions returns `-1`.
2110 cs = Indicates whether the comparisons are case sensitive.
2112 ptrdiff_t indexOfNeither(Char,Char2)(const(Char)[] haystack,
2113 const(Char2)[] needles, in CaseSensitive cs = Yes.caseSensitive)
2114 @safe pure
2115 if (isSomeChar!Char && isSomeChar!Char2)
2117 return indexOfAnyNeitherImpl!(true, false)(haystack, needles, cs);
2120 /// Ditto
2121 ptrdiff_t indexOfNeither(Char,Char2)(const(Char)[] haystack,
2122 const(Char2)[] needles, in size_t startIdx,
2123 in CaseSensitive cs = Yes.caseSensitive)
2124 @safe pure
2125 if (isSomeChar!Char && isSomeChar!Char2)
2127 if (startIdx < haystack.length)
2129 ptrdiff_t foundIdx = indexOfAnyNeitherImpl!(true, false)(
2130 haystack[startIdx .. $], needles, cs);
2131 if (foundIdx != -1)
2133 return foundIdx + cast(ptrdiff_t) startIdx;
2136 return -1;
2140 @safe pure unittest
2142 assert(indexOfNeither("abba", "a", 2) == 2);
2143 assert(indexOfNeither("def", "de", 1) == 2);
2144 assert(indexOfNeither("dfefffg", "dfe", 4) == 6);
2148 @safe pure unittest
2150 assert(indexOfNeither("def", "a") == 0);
2151 assert(indexOfNeither("def", "de") == 2);
2152 assert(indexOfNeither("dfefffg", "dfe") == 6);
2155 @safe pure unittest
2157 import std.conv : to;
2159 static foreach (S; AliasSeq!(string, wstring, dstring))
2161 auto r = to!S("").indexOfNeither("hello");
2162 assert(r == -1, to!string(r));
2164 r = to!S("hello").indexOfNeither("");
2165 assert(r == 0, to!string(r));
2167 r = to!S("").indexOfNeither("");
2168 assert(r == -1, to!string(r));
2172 @safe pure unittest
2174 import std.conv : to;
2175 import std.exception : assertCTFEable;
2177 assertCTFEable!(
2179 static foreach (S; AliasSeq!(string, wstring, dstring))
2181 static foreach (T; AliasSeq!(string, wstring, dstring))
2183 assert(indexOfNeither(cast(S) null, to!T("a")) == -1);
2184 assert(indexOfNeither("abba", "a") == 1);
2186 assert(indexOfNeither(to!S("dfeffgfff"), to!T("a"),
2187 No.caseSensitive) == 0);
2188 assert(indexOfNeither(to!S("def"), to!T("D"),
2189 No.caseSensitive) == 1);
2190 assert(indexOfNeither(to!S("ABca"), to!T("a"),
2191 No.caseSensitive) == 1);
2192 assert(indexOfNeither(to!S("def"), to!T("f"),
2193 No.caseSensitive) == 0);
2194 assert(indexOfNeither(to!S("DfEfffg"), to!T("dFe"),
2195 No.caseSensitive) == 6);
2196 if (is(S == string))
2198 assert(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"),
2199 No.caseSensitive) == 8,
2200 to!string(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"),
2201 No.caseSensitive)));
2203 else
2205 assert(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"),
2206 No.caseSensitive) == 7,
2207 to!string(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"),
2208 No.caseSensitive)));
2216 @safe pure unittest
2218 import std.conv : to;
2219 import std.exception : assertCTFEable;
2221 assertCTFEable!(
2223 static foreach (S; AliasSeq!(string, wstring, dstring))
2225 static foreach (T; AliasSeq!(string, wstring, dstring))
2227 assert(indexOfNeither(cast(S) null, to!T("a"), 1) == -1);
2228 assert(indexOfNeither(to!S("def"), to!T("a"), 1) == 1,
2229 to!string(indexOfNeither(to!S("def"), to!T("a"), 1)));
2231 assert(indexOfNeither(to!S("dfeffgfff"), to!T("a"), 4,
2232 No.caseSensitive) == 4);
2233 assert(indexOfNeither(to!S("def"), to!T("D"), 2,
2234 No.caseSensitive) == 2);
2235 assert(indexOfNeither(to!S("ABca"), to!T("a"), 3,
2236 No.caseSensitive) == -1);
2237 assert(indexOfNeither(to!S("def"), to!T("tzf"), 2,
2238 No.caseSensitive) == -1);
2239 assert(indexOfNeither(to!S("DfEfffg"), to!T("dFe"), 5,
2240 No.caseSensitive) == 6);
2241 if (is(S == string))
2243 assert(indexOfNeither(to!S("öDfEfffg"), to!T("äDi"), 2,
2244 No.caseSensitive) == 3, to!string(indexOfNeither(
2245 to!S("öDfEfffg"), to!T("äDi"), 2, No.caseSensitive)));
2247 else
2249 assert(indexOfNeither(to!S("öDfEfffg"), to!T("äDi"), 2,
2250 No.caseSensitive) == 2, to!string(indexOfNeither(
2251 to!S("öDfEfffg"), to!T("äDi"), 2, No.caseSensitive)));
2260 Returns the last index of the first occurence of any character that is not
2261 an elements in `needles` in `haystack`. If all element of
2262 `haystack` are element of `needles` `-1` is returned.
2264 Params:
2265 haystack = String to search for needles in.
2266 needles = Strings to search for in haystack.
2267 stopIdx = slices haystack like this $(D haystack[0 .. stopIdx]) If
2268 the stopIdx is greater than or equal to the length of haystack the
2269 functions returns `-1`.
2270 cs = Indicates whether the comparisons are case sensitive.
2272 ptrdiff_t lastIndexOfNeither(Char,Char2)(const(Char)[] haystack,
2273 const(Char2)[] needles, in CaseSensitive cs = Yes.caseSensitive)
2274 @safe pure
2275 if (isSomeChar!Char && isSomeChar!Char2)
2277 return indexOfAnyNeitherImpl!(false, false)(haystack, needles, cs);
2280 /// Ditto
2281 ptrdiff_t lastIndexOfNeither(Char,Char2)(const(Char)[] haystack,
2282 const(Char2)[] needles, in size_t stopIdx,
2283 in CaseSensitive cs = Yes.caseSensitive)
2284 @safe pure
2285 if (isSomeChar!Char && isSomeChar!Char2)
2287 if (stopIdx < haystack.length)
2289 return indexOfAnyNeitherImpl!(false, false)(haystack[0 .. stopIdx],
2290 needles, cs);
2292 return -1;
2296 @safe pure unittest
2298 assert(lastIndexOfNeither("abba", "a") == 2);
2299 assert(lastIndexOfNeither("def", "f") == 1);
2303 @safe pure unittest
2305 assert(lastIndexOfNeither("def", "rsa", 3) == -1);
2306 assert(lastIndexOfNeither("abba", "a", 2) == 1);
2309 @safe pure unittest
2311 import std.conv : to;
2313 static foreach (S; AliasSeq!(string, wstring, dstring))
2315 auto r = to!S("").lastIndexOfNeither("hello");
2316 assert(r == -1, to!string(r));
2318 r = to!S("hello").lastIndexOfNeither("");
2319 assert(r == 4, to!string(r));
2321 r = to!S("").lastIndexOfNeither("");
2322 assert(r == -1, to!string(r));
2326 @safe pure unittest
2328 import std.conv : to;
2329 import std.exception : assertCTFEable;
2331 assertCTFEable!(
2333 static foreach (S; AliasSeq!(string, wstring, dstring))
2335 static foreach (T; AliasSeq!(string, wstring, dstring))
2337 assert(lastIndexOfNeither(cast(S) null, to!T("a")) == -1);
2338 assert(lastIndexOfNeither(to!S("def"), to!T("rsa")) == 2);
2339 assert(lastIndexOfNeither(to!S("dfefffg"), to!T("fgh")) == 2);
2341 ptrdiff_t oeIdx = 8;
2342 if (is(S == string))
2344 oeIdx = 9;
2347 auto foundOeIdx = lastIndexOfNeither(to!S("ödfefegff"), to!T("zeg"));
2348 assert(foundOeIdx == oeIdx, to!string(foundOeIdx));
2350 assert(lastIndexOfNeither(to!S("zfeffgfsb"), to!T("FSB"),
2351 No.caseSensitive) == 5);
2352 assert(lastIndexOfNeither(to!S("def"), to!T("MI6"),
2353 No.caseSensitive) == 2, to!string(lastIndexOfNeither(to!S("def"),
2354 to!T("MI6"), No.caseSensitive)));
2355 assert(lastIndexOfNeither(to!S("abbadeafsb"), to!T("fSb"),
2356 No.caseSensitive) == 6, to!string(lastIndexOfNeither(
2357 to!S("abbadeafsb"), to!T("fSb"), No.caseSensitive)));
2358 assert(lastIndexOfNeither(to!S("defbi"), to!T("FBI"),
2359 No.caseSensitive) == 1);
2360 assert(lastIndexOfNeither(to!S("dfefffg"), to!T("NSA"),
2361 No.caseSensitive) == 6);
2362 assert(lastIndexOfNeither(to!S("dfeffgfffö"), to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"),
2363 No.caseSensitive) == 8, to!string(lastIndexOfNeither(to!S("dfeffgfffö"),
2364 to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"), No.caseSensitive)));
2371 @safe pure unittest
2373 import std.conv : to;
2374 import std.exception : assertCTFEable;
2376 assertCTFEable!(
2378 static foreach (S; AliasSeq!(string, wstring, dstring))
2380 static foreach (T; AliasSeq!(string, wstring, dstring))
2382 assert(lastIndexOfNeither(cast(S) null, to!T("a"), 1337) == -1);
2383 assert(lastIndexOfNeither(to!S("def"), to!T("f")) == 1);
2384 assert(lastIndexOfNeither(to!S("dfefffg"), to!T("fgh")) == 2);
2386 ptrdiff_t oeIdx = 4;
2387 if (is(S == string))
2389 oeIdx = 5;
2392 auto foundOeIdx = lastIndexOfNeither(to!S("ödfefegff"), to!T("zeg"),
2394 assert(foundOeIdx == oeIdx, to!string(foundOeIdx));
2396 assert(lastIndexOfNeither(to!S("zfeffgfsb"), to!T("FSB"), 6,
2397 No.caseSensitive) == 5);
2398 assert(lastIndexOfNeither(to!S("def"), to!T("MI6"), 2,
2399 No.caseSensitive) == 1, to!string(lastIndexOfNeither(to!S("def"),
2400 to!T("MI6"), 2, No.caseSensitive)));
2401 assert(lastIndexOfNeither(to!S("abbadeafsb"), to!T("fSb"), 6,
2402 No.caseSensitive) == 5, to!string(lastIndexOfNeither(
2403 to!S("abbadeafsb"), to!T("fSb"), 6, No.caseSensitive)));
2404 assert(lastIndexOfNeither(to!S("defbi"), to!T("FBI"), 3,
2405 No.caseSensitive) == 1);
2406 assert(lastIndexOfNeither(to!S("dfefffg"), to!T("NSA"), 2,
2407 No.caseSensitive) == 1, to!string(lastIndexOfNeither(
2408 to!S("dfefffg"), to!T("NSA"), 2, No.caseSensitive)));
2416 * Returns the _representation of a string, which has the same type
2417 * as the string except the character type is replaced by `ubyte`,
2418 * `ushort`, or `uint` depending on the character width.
2420 * Params:
2421 * s = The string to return the _representation of.
2423 * Returns:
2424 * The _representation of the passed string.
2426 auto representation(Char)(Char[] s) @safe pure nothrow @nogc
2427 if (isSomeChar!Char)
2429 import std.traits : ModifyTypePreservingTQ;
2430 alias ToRepType(T) = AliasSeq!(ubyte, ushort, uint)[T.sizeof / 2];
2431 return cast(ModifyTypePreservingTQ!(ToRepType, Char)[])s;
2435 @safe pure unittest
2437 string s = "hello";
2438 static assert(is(typeof(representation(s)) == immutable(ubyte)[]));
2439 assert(representation(s) is cast(immutable(ubyte)[]) s);
2440 assert(representation(s) == [0x68, 0x65, 0x6c, 0x6c, 0x6f]);
2443 @system pure unittest
2445 import std.exception : assertCTFEable;
2446 import std.traits : Fields;
2447 import std.typecons : Tuple;
2449 assertCTFEable!(
2451 void test(Char, T)(Char[] str)
2453 static assert(is(typeof(representation(str)) == T[]));
2454 assert(representation(str) is cast(T[]) str);
2457 static foreach (Type; AliasSeq!(Tuple!(char , ubyte ),
2458 Tuple!(wchar, ushort),
2459 Tuple!(dchar, uint )))
2461 alias Char = Fields!Type[0];
2462 alias Int = Fields!Type[1];
2463 enum immutable(Char)[] hello = "hello";
2465 test!( immutable Char, immutable Int)(hello);
2466 test!( const Char, const Int)(hello);
2467 test!( Char, Int)(hello.dup);
2468 test!( shared Char, shared Int)(cast(shared) hello.dup);
2469 test!(const shared Char, const shared Int)(hello);
2476 * Capitalize the first character of `s` and convert the rest of `s` to
2477 * lowercase.
2479 * Params:
2480 * input = The string to _capitalize.
2482 * Returns:
2483 * The capitalized string.
2485 * See_Also:
2486 * $(REF asCapitalized, std,uni) for a lazy range version that doesn't allocate memory
2488 S capitalize(S)(S input) @trusted pure
2489 if (isSomeString!S)
2491 import std.array : array;
2492 import std.uni : asCapitalized;
2493 import std.utf : byUTF;
2495 return input.asCapitalized.byUTF!(ElementEncodingType!(S)).array;
2499 pure @safe unittest
2501 assert(capitalize("hello") == "Hello");
2502 assert(capitalize("World") == "World");
2505 auto capitalize(S)(auto ref S s)
2506 if (!isSomeString!S && is(StringTypeOf!S))
2508 return capitalize!(StringTypeOf!S)(s);
2511 @safe pure unittest
2513 assert(testAliasedString!capitalize("hello"));
2516 @safe pure unittest
2518 import std.algorithm.comparison : cmp;
2519 import std.conv : to;
2520 import std.exception : assertCTFEable;
2522 assertCTFEable!(
2524 static foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[]))
2526 S s1 = to!S("FoL");
2527 S s2;
2529 s2 = capitalize(s1);
2530 assert(cmp(s2, "Fol") == 0);
2531 assert(s2 !is s1);
2533 s2 = capitalize(s1[0 .. 2]);
2534 assert(cmp(s2, "Fo") == 0);
2536 s1 = to!S("fOl");
2537 s2 = capitalize(s1);
2538 assert(cmp(s2, "Fol") == 0);
2539 assert(s2 !is s1);
2540 s1 = to!S("\u0131 \u0130");
2541 s2 = capitalize(s1);
2542 assert(cmp(s2, "\u0049 i\u0307") == 0);
2543 assert(s2 !is s1);
2545 s1 = to!S("\u017F \u0049");
2546 s2 = capitalize(s1);
2547 assert(cmp(s2, "\u0053 \u0069") == 0);
2548 assert(s2 !is s1);
2554 Split `s` into an array of lines according to the unicode standard using
2555 `'\r'`, `'\n'`, `"\r\n"`, $(REF lineSep, std,uni),
2556 $(REF paraSep, std,uni), `U+0085` (NEL), `'\v'` and `'\f'`
2557 as delimiters. If `keepTerm` is set to `KeepTerminator.yes`, then the
2558 delimiter is included in the strings returned.
2560 Does not throw on invalid UTF; such is simply passed unchanged
2561 to the output.
2563 Allocates memory; use $(LREF lineSplitter) for an alternative that
2564 does not.
2566 Adheres to $(HTTP www.unicode.org/versions/Unicode7.0.0/ch05.pdf, Unicode 7.0).
2568 Params:
2569 s = a string of `chars`, `wchars`, or `dchars`, or any custom
2570 type that casts to a `string` type
2571 keepTerm = whether delimiter is included or not in the results
2572 Returns:
2573 array of strings, each element is a line that is a slice of `s`
2574 See_Also:
2575 $(LREF lineSplitter)
2576 $(REF splitter, std,algorithm)
2577 $(REF splitter, std,regex)
2579 alias KeepTerminator = Flag!"keepTerminator";
2581 /// ditto
2582 C[][] splitLines(C)(C[] s, KeepTerminator keepTerm = No.keepTerminator) @safe pure
2583 if (isSomeChar!C)
2585 import std.array : appender;
2586 import std.uni : lineSep, paraSep;
2588 size_t iStart = 0;
2589 auto retval = appender!(C[][])();
2591 for (size_t i; i < s.length; ++i)
2593 switch (s[i])
2595 case '\v', '\f', '\n':
2596 retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator)]);
2597 iStart = i + 1;
2598 break;
2600 case '\r':
2601 if (i + 1 < s.length && s[i + 1] == '\n')
2603 retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator) * 2]);
2604 iStart = i + 2;
2605 ++i;
2607 else
2609 goto case '\n';
2611 break;
2613 static if (s[i].sizeof == 1)
2615 /* Manually decode:
2616 * lineSep is E2 80 A8
2617 * paraSep is E2 80 A9
2619 case 0xE2:
2620 if (i + 2 < s.length &&
2621 s[i + 1] == 0x80 &&
2622 (s[i + 2] == 0xA8 || s[i + 2] == 0xA9)
2625 retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator) * 3]);
2626 iStart = i + 3;
2627 i += 2;
2629 else
2630 goto default;
2631 break;
2632 /* Manually decode:
2633 * NEL is C2 85
2635 case 0xC2:
2636 if (i + 1 < s.length && s[i + 1] == 0x85)
2638 retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator) * 2]);
2639 iStart = i + 2;
2640 i += 1;
2642 else
2643 goto default;
2644 break;
2646 else
2648 case lineSep:
2649 case paraSep:
2650 case '\u0085':
2651 goto case '\n';
2654 default:
2655 break;
2659 if (iStart != s.length)
2660 retval.put(s[iStart .. $]);
2662 return retval.data;
2666 @safe pure nothrow unittest
2668 string s = "Hello\nmy\rname\nis";
2669 assert(splitLines(s) == ["Hello", "my", "name", "is"]);
2672 @safe pure nothrow unittest
2674 string s = "a\xC2\x86b";
2675 assert(splitLines(s) == [s]);
2678 @safe pure nothrow unittest
2680 assert(testAliasedString!splitLines("hello\nworld"));
2682 enum S : string { a = "hello\nworld" }
2683 assert(S.a.splitLines() == ["hello", "world"]);
2686 @system pure nothrow unittest
2688 // dip1000 cannot express an array of scope arrays, so this is not @safe
2689 char[11] sa = "hello\nworld";
2690 assert(sa.splitLines() == ["hello", "world"]);
2693 @safe pure unittest
2695 import std.conv : to;
2696 import std.exception : assertCTFEable;
2698 assertCTFEable!(
2700 static foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
2702 auto s = to!S(
2703 "\rpeter\n\rpaul\r\njerry\u2028ice\u2029cream\n\nsunday\n" ~
2704 "mon\u2030day\nschadenfreude\vkindergarten\f\vcookies\u0085"
2706 auto lines = splitLines(s);
2707 assert(lines.length == 14);
2708 assert(lines[0] == "");
2709 assert(lines[1] == "peter");
2710 assert(lines[2] == "");
2711 assert(lines[3] == "paul");
2712 assert(lines[4] == "jerry");
2713 assert(lines[5] == "ice");
2714 assert(lines[6] == "cream");
2715 assert(lines[7] == "");
2716 assert(lines[8] == "sunday");
2717 assert(lines[9] == "mon\u2030day");
2718 assert(lines[10] == "schadenfreude");
2719 assert(lines[11] == "kindergarten");
2720 assert(lines[12] == "");
2721 assert(lines[13] == "cookies");
2724 ubyte[] u = ['a', 0xFF, 0x12, 'b']; // invalid UTF
2725 auto ulines = splitLines(cast(char[]) u);
2726 assert(cast(ubyte[])(ulines[0]) == u);
2728 lines = splitLines(s, Yes.keepTerminator);
2729 assert(lines.length == 14);
2730 assert(lines[0] == "\r");
2731 assert(lines[1] == "peter\n");
2732 assert(lines[2] == "\r");
2733 assert(lines[3] == "paul\r\n");
2734 assert(lines[4] == "jerry\u2028");
2735 assert(lines[5] == "ice\u2029");
2736 assert(lines[6] == "cream\n");
2737 assert(lines[7] == "\n");
2738 assert(lines[8] == "sunday\n");
2739 assert(lines[9] == "mon\u2030day\n");
2740 assert(lines[10] == "schadenfreude\v");
2741 assert(lines[11] == "kindergarten\f");
2742 assert(lines[12] == "\v");
2743 assert(lines[13] == "cookies\u0085");
2745 s.popBack(); // Lop-off trailing \n
2746 lines = splitLines(s);
2747 assert(lines.length == 14);
2748 assert(lines[9] == "mon\u2030day");
2750 lines = splitLines(s, Yes.keepTerminator);
2751 assert(lines.length == 14);
2752 assert(lines[13] == "cookies");
2757 private struct LineSplitter(KeepTerminator keepTerm = No.keepTerminator, Range)
2759 import std.conv : unsigned;
2760 import std.uni : lineSep, paraSep;
2761 private:
2762 Range _input;
2764 alias IndexType = typeof(unsigned(_input.length));
2765 enum IndexType _unComputed = IndexType.max;
2766 IndexType iStart = _unComputed;
2767 IndexType iEnd = 0;
2768 IndexType iNext = 0;
2770 public:
2771 this(Range input)
2773 _input = input;
2776 static if (isInfinite!Range)
2778 enum bool empty = false;
2780 else
2782 @property bool empty()
2784 return iStart == _unComputed && iNext == _input.length;
2788 @property typeof(_input) front()
2790 if (iStart == _unComputed)
2792 iStart = iNext;
2793 Loop:
2794 for (IndexType i = iNext; ; ++i)
2796 if (i == _input.length)
2798 iEnd = i;
2799 iNext = i;
2800 break Loop;
2802 switch (_input[i])
2804 case '\v', '\f', '\n':
2805 iEnd = i + (keepTerm == Yes.keepTerminator);
2806 iNext = i + 1;
2807 break Loop;
2809 case '\r':
2810 if (i + 1 < _input.length && _input[i + 1] == '\n')
2812 iEnd = i + (keepTerm == Yes.keepTerminator) * 2;
2813 iNext = i + 2;
2814 break Loop;
2816 else
2818 goto case '\n';
2821 static if (_input[i].sizeof == 1)
2823 /* Manually decode:
2824 * lineSep is E2 80 A8
2825 * paraSep is E2 80 A9
2827 case 0xE2:
2828 if (i + 2 < _input.length &&
2829 _input[i + 1] == 0x80 &&
2830 (_input[i + 2] == 0xA8 || _input[i + 2] == 0xA9)
2833 iEnd = i + (keepTerm == Yes.keepTerminator) * 3;
2834 iNext = i + 3;
2835 break Loop;
2837 else
2838 goto default;
2839 /* Manually decode:
2840 * NEL is C2 85
2842 case 0xC2:
2843 if (i + 1 < _input.length && _input[i + 1] == 0x85)
2845 iEnd = i + (keepTerm == Yes.keepTerminator) * 2;
2846 iNext = i + 2;
2847 break Loop;
2849 else
2850 goto default;
2852 else
2854 case '\u0085':
2855 case lineSep:
2856 case paraSep:
2857 goto case '\n';
2860 default:
2861 break;
2865 return _input[iStart .. iEnd];
2868 void popFront()
2870 if (iStart == _unComputed)
2872 assert(!empty, "Can not popFront an empty range");
2873 front;
2875 iStart = _unComputed;
2878 static if (isForwardRange!Range)
2880 @property typeof(this) save()
2882 auto ret = this;
2883 ret._input = _input.save;
2884 return ret;
2889 /***********************************
2890 * Split an array or slicable range of characters into a range of lines
2891 using `'\r'`, `'\n'`, `'\v'`, `'\f'`, `"\r\n"`,
2892 $(REF lineSep, std,uni), $(REF paraSep, std,uni) and `'\u0085'` (NEL)
2893 as delimiters. If `keepTerm` is set to `Yes.keepTerminator`, then the
2894 delimiter is included in the slices returned.
2896 Does not throw on invalid UTF; such is simply passed unchanged
2897 to the output.
2899 Adheres to $(HTTP www.unicode.org/versions/Unicode7.0.0/ch05.pdf, Unicode 7.0).
2901 Does not allocate memory.
2903 Params:
2904 r = array of `chars`, `wchars`, or `dchars` or a slicable range
2905 keepTerm = whether delimiter is included or not in the results
2906 Returns:
2907 range of slices of the input range `r`
2909 See_Also:
2910 $(LREF splitLines)
2911 $(REF splitter, std,algorithm)
2912 $(REF splitter, std,regex)
2914 auto lineSplitter(KeepTerminator keepTerm = No.keepTerminator, Range)(Range r)
2915 if (hasSlicing!Range && hasLength!Range && isSomeChar!(ElementType!Range) && !isSomeString!Range)
2917 return LineSplitter!(keepTerm, Range)(r);
2920 /// Ditto
2921 auto lineSplitter(KeepTerminator keepTerm = No.keepTerminator, C)(C[] r)
2922 if (isSomeChar!C)
2924 return LineSplitter!(keepTerm, C[])(r);
2928 @safe pure unittest
2930 import std.array : array;
2932 string s = "Hello\nmy\rname\nis";
2934 /* notice the call to 'array' to turn the lazy range created by
2935 lineSplitter comparable to the string[] created by splitLines.
2937 assert(lineSplitter(s).array == splitLines(s));
2940 @safe pure unittest
2942 import std.array : array;
2943 import std.conv : to;
2944 import std.exception : assertCTFEable;
2946 assertCTFEable!(
2948 static foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
2950 auto s = to!S(
2951 "\rpeter\n\rpaul\r\njerry\u2028ice\u2029cream\n\n" ~
2952 "sunday\nmon\u2030day\nschadenfreude\vkindergarten\f\vcookies\u0085"
2955 auto lines = lineSplitter(s).array;
2956 assert(lines.length == 14);
2957 assert(lines[0] == "");
2958 assert(lines[1] == "peter");
2959 assert(lines[2] == "");
2960 assert(lines[3] == "paul");
2961 assert(lines[4] == "jerry");
2962 assert(lines[5] == "ice");
2963 assert(lines[6] == "cream");
2964 assert(lines[7] == "");
2965 assert(lines[8] == "sunday");
2966 assert(lines[9] == "mon\u2030day");
2967 assert(lines[10] == "schadenfreude");
2968 assert(lines[11] == "kindergarten");
2969 assert(lines[12] == "");
2970 assert(lines[13] == "cookies");
2973 ubyte[] u = ['a', 0xFF, 0x12, 'b']; // invalid UTF
2974 auto ulines = lineSplitter(cast(char[]) u).array;
2975 assert(cast(ubyte[])(ulines[0]) == u);
2977 lines = lineSplitter!(Yes.keepTerminator)(s).array;
2978 assert(lines.length == 14);
2979 assert(lines[0] == "\r");
2980 assert(lines[1] == "peter\n");
2981 assert(lines[2] == "\r");
2982 assert(lines[3] == "paul\r\n");
2983 assert(lines[4] == "jerry\u2028");
2984 assert(lines[5] == "ice\u2029");
2985 assert(lines[6] == "cream\n");
2986 assert(lines[7] == "\n");
2987 assert(lines[8] == "sunday\n");
2988 assert(lines[9] == "mon\u2030day\n");
2989 assert(lines[10] == "schadenfreude\v");
2990 assert(lines[11] == "kindergarten\f");
2991 assert(lines[12] == "\v");
2992 assert(lines[13] == "cookies\u0085");
2994 s.popBack(); // Lop-off trailing \n
2995 lines = lineSplitter(s).array;
2996 assert(lines.length == 14);
2997 assert(lines[9] == "mon\u2030day");
2999 lines = lineSplitter!(Yes.keepTerminator)(s).array;
3000 assert(lines.length == 14);
3001 assert(lines[13] == "cookies");
3007 @nogc @safe pure unittest
3009 auto s = "\rpeter\n\rpaul\r\njerry\u2028ice\u2029cream\n\nsunday\nmon\u2030day\n";
3010 auto lines = s.lineSplitter();
3011 static immutable witness = ["", "peter", "", "paul", "jerry", "ice", "cream", "", "sunday", "mon\u2030day"];
3012 uint i;
3013 foreach (line; lines)
3015 assert(line == witness[i++]);
3017 assert(i == witness.length);
3020 @nogc @safe pure unittest
3022 import std.algorithm.comparison : equal;
3023 import std.range : only;
3025 auto s = "std/string.d";
3026 auto as = TestAliasedString(s);
3027 assert(equal(s.lineSplitter(), as.lineSplitter()));
3029 enum S : string { a = "hello\nworld" }
3030 assert(equal(S.a.lineSplitter(), only("hello", "world")));
3032 char[S.a.length] sa = S.a[];
3033 assert(equal(sa.lineSplitter(), only("hello", "world")));
3036 @safe pure unittest
3038 auto s = "line1\nline2";
3039 auto spl0 = s.lineSplitter!(Yes.keepTerminator);
3040 auto spl1 = spl0.save;
3041 spl0.popFront;
3042 assert(spl1.front ~ spl0.front == s);
3043 string r = "a\xC2\x86b";
3044 assert(r.lineSplitter.front == r);
3048 Strips leading whitespace (as defined by $(REF isWhite, std,uni)) or
3049 as specified in the second argument.
3051 Params:
3052 input = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
3053 of characters
3054 chars = string of characters to be stripped
3056 Returns: `input` stripped of leading whitespace or characters
3057 specified in the second argument.
3059 Postconditions: `input` and the returned value
3060 will share the same tail (see $(REF sameTail, std,array)).
3062 See_Also:
3063 Generic stripping on ranges: $(REF _stripLeft, std, algorithm, mutation)
3065 auto stripLeft(Range)(Range input)
3066 if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
3067 !isInfinite!Range && !isConvertibleToString!Range)
3069 import std.traits : isDynamicArray;
3070 static import std.ascii;
3071 static import std.uni;
3073 static if (is(immutable ElementEncodingType!Range == immutable dchar)
3074 || is(immutable ElementEncodingType!Range == immutable wchar))
3076 // Decoding is never needed for dchar. It happens not to be needed
3077 // here for wchar because no whitepace is outside the basic
3078 // multilingual plane meaning every whitespace character is encoded
3079 // with a single wchar and due to the design of UTF-16 those wchars
3080 // will not occur as part of the encoding of multi-wchar codepoints.
3081 static if (isDynamicArray!Range)
3083 foreach (i; 0 .. input.length)
3085 if (!std.uni.isWhite(input[i]))
3086 return input[i .. $];
3088 return input[$ .. $];
3090 else
3092 while (!input.empty)
3094 if (!std.uni.isWhite(input.front))
3095 break;
3096 input.popFront();
3098 return input;
3101 else
3103 static if (isDynamicArray!Range)
3105 // ASCII optimization for dynamic arrays.
3106 size_t i = 0;
3107 for (const size_t end = input.length; i < end; ++i)
3109 auto c = input[i];
3110 if (c >= 0x80) goto NonAsciiPath;
3111 if (!std.ascii.isWhite(c)) break;
3113 input = input[i .. $];
3114 return input;
3116 NonAsciiPath:
3117 input = input[i .. $];
3118 // Fall through to standard case.
3121 import std.utf : decode, decodeFront, UseReplacementDchar;
3123 static if (isNarrowString!Range)
3125 for (size_t index = 0; index < input.length;)
3127 const saveIndex = index;
3128 if (!std.uni.isWhite(decode!(UseReplacementDchar.yes)(input, index)))
3129 return input[saveIndex .. $];
3131 return input[$ .. $];
3133 else
3135 while (!input.empty)
3137 auto c = input.front;
3138 if (std.ascii.isASCII(c))
3140 if (!std.ascii.isWhite(c))
3141 break;
3142 input.popFront();
3144 else
3146 auto save = input.save;
3147 auto dc = decodeFront!(UseReplacementDchar.yes)(input);
3148 if (!std.uni.isWhite(dc))
3149 return save;
3152 return input;
3158 nothrow @safe pure unittest
3160 import std.uni : lineSep, paraSep;
3161 assert(stripLeft(" hello world ") ==
3162 "hello world ");
3163 assert(stripLeft("\n\t\v\rhello world\n\t\v\r") ==
3164 "hello world\n\t\v\r");
3165 assert(stripLeft(" \u2028hello world") ==
3166 "hello world");
3167 assert(stripLeft("hello world") ==
3168 "hello world");
3169 assert(stripLeft([lineSep] ~ "hello world" ~ lineSep) ==
3170 "hello world" ~ [lineSep]);
3171 assert(stripLeft([paraSep] ~ "hello world" ~ paraSep) ==
3172 "hello world" ~ [paraSep]);
3174 import std.array : array;
3175 import std.utf : byChar;
3176 assert(stripLeft(" hello world "w.byChar).array ==
3177 "hello world ");
3178 assert(stripLeft(" \u2022hello world ".byChar).array ==
3179 "\u2022hello world ");
3182 auto stripLeft(Range)(auto ref Range str)
3183 if (isConvertibleToString!Range)
3185 return stripLeft!(StringTypeOf!Range)(str);
3188 @nogc nothrow @safe pure unittest
3190 assert(testAliasedString!stripLeft(" hello"));
3193 /// Ditto
3194 auto stripLeft(Range, Char)(Range input, const(Char)[] chars)
3195 if (((isForwardRange!Range && isSomeChar!(ElementEncodingType!Range)) ||
3196 isConvertibleToString!Range) && isSomeChar!Char)
3198 static if (isConvertibleToString!Range)
3199 return stripLeft!(StringTypeOf!Range)(input, chars);
3200 else
3202 for (; !input.empty; input.popFront)
3204 if (chars.indexOf(input.front) == -1)
3205 break;
3207 return input;
3212 @safe pure unittest
3214 assert(stripLeft(" hello world ", " ") ==
3215 "hello world ");
3216 assert(stripLeft("xxxxxhello world ", "x") ==
3217 "hello world ");
3218 assert(stripLeft("xxxyy hello world ", "xy ") ==
3219 "hello world ");
3223 @safe pure unittest
3225 import std.array : array;
3226 import std.utf : byChar, byWchar, byDchar;
3228 assert(stripLeft(" xxxyy hello world "w.byChar, "xy ").array ==
3229 "hello world ");
3231 assert(stripLeft("\u2028\u2020hello world\u2028"w.byWchar,
3232 "\u2028").array == "\u2020hello world\u2028");
3233 assert(stripLeft("\U00010001hello world"w.byWchar, " ").array ==
3234 "\U00010001hello world"w);
3235 assert(stripLeft("\U00010001 xyhello world"d.byDchar,
3236 "\U00010001 xy").array == "hello world"d);
3238 assert(stripLeft("\u2020hello"w, "\u2020"w) == "hello"w);
3239 assert(stripLeft("\U00010001hello"d, "\U00010001"d) == "hello"d);
3240 assert(stripLeft(" hello ", "") == " hello ");
3243 @safe pure unittest
3245 assert(testAliasedString!stripLeft(" xyz hello", "xyz "));
3249 Strips trailing whitespace (as defined by $(REF isWhite, std,uni)) or
3250 as specified in the second argument.
3252 Params:
3253 str = string or random access range of characters
3254 chars = string of characters to be stripped
3256 Returns:
3257 slice of `str` stripped of trailing whitespace or characters
3258 specified in the second argument.
3260 See_Also:
3261 Generic stripping on ranges: $(REF _stripRight, std, algorithm, mutation)
3263 auto stripRight(Range)(Range str)
3264 if (isSomeString!Range ||
3265 isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range &&
3266 !isConvertibleToString!Range &&
3267 isSomeChar!(ElementEncodingType!Range))
3269 import std.traits : isDynamicArray;
3270 import std.uni : isWhite;
3271 alias C = Unqual!(ElementEncodingType!(typeof(str)));
3273 static if (isSomeString!(typeof(str)) && C.sizeof >= 2)
3275 // No whitespace takes multiple wchars to encode and due to
3276 // the design of UTF-16 those wchars will not occur as part
3277 // of the encoding of multi-wchar codepoints.
3278 foreach_reverse (i, C c; str)
3280 if (!isWhite(c))
3281 return str[0 .. i + 1];
3283 return str[0 .. 0];
3285 else
3287 // ASCII optimization for dynamic arrays.
3288 static if (isDynamicArray!(typeof(str)))
3290 static import std.ascii;
3291 foreach_reverse (i, C c; str)
3293 if (c >= 0x80)
3295 str = str[0 .. i + 1];
3296 goto NonAsciiPath;
3298 if (!std.ascii.isWhite(c))
3300 return str[0 .. i + 1];
3303 return str[0 .. 0];
3306 NonAsciiPath:
3308 size_t i = str.length;
3309 while (i--)
3311 static if (C.sizeof >= 2)
3313 // No whitespace takes multiple wchars to encode and due to
3314 // the design of UTF-16 those wchars will not occur as part
3315 // of the encoding of multi-wchar codepoints.
3316 if (isWhite(str[i]))
3317 continue;
3318 break;
3320 else static if (C.sizeof == 1)
3322 const cx = str[i];
3323 if (cx <= 0x7F)
3325 if (isWhite(cx))
3326 continue;
3327 break;
3329 else
3331 if (i == 0 || (0b1100_0000 & cx) != 0b1000_0000)
3332 break;
3333 const uint d = 0b0011_1111 & cx;
3334 const c2 = str[i - 1];
3335 if ((c2 & 0b1110_0000) == 0b1100_0000) // 2 byte encoding.
3337 if (isWhite(d + (uint(c2 & 0b0001_1111) << 6)))
3339 i--;
3340 continue;
3342 break;
3344 if (i == 1 || (c2 & 0b1100_0000) != 0b1000_0000)
3345 break;
3346 const c3 = str[i - 2];
3347 // In UTF-8 all whitespace is encoded in 3 bytes or fewer.
3348 if ((c3 & 0b1111_0000) == 0b1110_0000 &&
3349 isWhite(d + (uint(c2 & 0b0011_1111) << 6) + (uint(c3 & 0b0000_1111) << 12)))
3351 i -= 2;
3352 continue;
3354 break;
3357 else
3358 static assert(0);
3361 return str[0 .. i + 1];
3366 nothrow @safe pure
3367 unittest
3369 import std.uni : lineSep, paraSep;
3370 assert(stripRight(" hello world ") ==
3371 " hello world");
3372 assert(stripRight("\n\t\v\rhello world\n\t\v\r") ==
3373 "\n\t\v\rhello world");
3374 assert(stripRight("hello world") ==
3375 "hello world");
3376 assert(stripRight([lineSep] ~ "hello world" ~ lineSep) ==
3377 [lineSep] ~ "hello world");
3378 assert(stripRight([paraSep] ~ "hello world" ~ paraSep) ==
3379 [paraSep] ~ "hello world");
3382 auto stripRight(Range)(auto ref Range str)
3383 if (isConvertibleToString!Range)
3385 return stripRight!(StringTypeOf!Range)(str);
3388 @nogc nothrow @safe pure unittest
3390 assert(testAliasedString!stripRight("hello "));
3393 @safe pure unittest
3395 import std.array : array;
3396 import std.uni : lineSep, paraSep;
3397 import std.utf : byChar, byDchar, byUTF, byWchar, invalidUTFstrings;
3398 assert(stripRight(" hello world ".byChar).array == " hello world");
3399 assert(stripRight("\n\t\v\rhello world\n\t\v\r"w.byWchar).array == "\n\t\v\rhello world"w);
3400 assert(stripRight("hello world"d.byDchar).array == "hello world"d);
3401 assert(stripRight("\u2028hello world\u2020\u2028".byChar).array == "\u2028hello world\u2020");
3402 assert(stripRight("hello world\U00010001"w.byWchar).array == "hello world\U00010001"w);
3404 static foreach (C; AliasSeq!(char, wchar, dchar))
3406 foreach (s; invalidUTFstrings!C())
3408 cast(void) stripRight(s.byUTF!C).array;
3412 cast(void) stripRight("a\x80".byUTF!char).array;
3413 wstring ws = ['a', cast(wchar) 0xDC00];
3414 cast(void) stripRight(ws.byUTF!wchar).array;
3417 /// Ditto
3418 auto stripRight(Range, Char)(Range str, const(Char)[] chars)
3419 if (((isBidirectionalRange!Range && isSomeChar!(ElementEncodingType!Range)) ||
3420 isConvertibleToString!Range) && isSomeChar!Char)
3422 static if (isConvertibleToString!Range)
3423 return stripRight!(StringTypeOf!Range)(str, chars);
3424 else
3426 for (; !str.empty; str.popBack)
3428 if (chars.indexOf(str.back) == -1)
3429 break;
3431 return str;
3436 @safe pure
3437 unittest
3439 assert(stripRight(" hello world ", "x") ==
3440 " hello world ");
3441 assert(stripRight(" hello world ", " ") ==
3442 " hello world");
3443 assert(stripRight(" hello worldxy ", "xy ") ==
3444 " hello world");
3447 @safe pure unittest
3449 assert(testAliasedString!stripRight("hello xyz ", "xyz "));
3452 @safe pure unittest
3454 import std.array : array;
3455 import std.utf : byChar, byDchar, byUTF, byWchar;
3457 assert(stripRight(" hello world xyz ".byChar,
3458 "xyz ").array == " hello world");
3459 assert(stripRight("\u2028hello world\u2020\u2028"w.byWchar,
3460 "\u2028").array == "\u2028hello world\u2020");
3461 assert(stripRight("hello world\U00010001"w.byWchar,
3462 " ").array == "hello world\U00010001"w);
3463 assert(stripRight("hello world\U00010001 xy"d.byDchar,
3464 "\U00010001 xy").array == "hello world"d);
3465 assert(stripRight("hello\u2020"w, "\u2020"w) == "hello"w);
3466 assert(stripRight("hello\U00010001"d, "\U00010001"d) == "hello"d);
3467 assert(stripRight(" hello ", "") == " hello ");
3472 Strips both leading and trailing whitespace (as defined by
3473 $(REF isWhite, std,uni)) or as specified in the second argument.
3475 Params:
3476 str = string or random access range of characters
3477 chars = string of characters to be stripped
3478 leftChars = string of leading characters to be stripped
3479 rightChars = string of trailing characters to be stripped
3481 Returns:
3482 slice of `str` stripped of leading and trailing whitespace
3483 or characters as specified in the second argument.
3485 See_Also:
3486 Generic stripping on ranges: $(REF _strip, std, algorithm, mutation)
3488 auto strip(Range)(Range str)
3489 if (isSomeString!Range ||
3490 isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range &&
3491 !isConvertibleToString!Range &&
3492 isSomeChar!(ElementEncodingType!Range))
3494 return stripRight(stripLeft(str));
3498 @safe pure unittest
3500 import std.uni : lineSep, paraSep;
3501 assert(strip(" hello world ") ==
3502 "hello world");
3503 assert(strip("\n\t\v\rhello world\n\t\v\r") ==
3504 "hello world");
3505 assert(strip("hello world") ==
3506 "hello world");
3507 assert(strip([lineSep] ~ "hello world" ~ [lineSep]) ==
3508 "hello world");
3509 assert(strip([paraSep] ~ "hello world" ~ [paraSep]) ==
3510 "hello world");
3513 auto strip(Range)(auto ref Range str)
3514 if (isConvertibleToString!Range)
3516 return strip!(StringTypeOf!Range)(str);
3519 @safe pure unittest
3521 assert(testAliasedString!strip(" hello world "));
3524 @safe pure unittest
3526 import std.algorithm.comparison : equal;
3527 import std.conv : to;
3528 import std.exception : assertCTFEable;
3530 assertCTFEable!(
3532 static foreach (S; AliasSeq!( char[], const char[], string,
3533 wchar[], const wchar[], wstring,
3534 dchar[], const dchar[], dstring))
3536 assert(equal(stripLeft(to!S(" foo\t ")), "foo\t "));
3537 assert(equal(stripLeft(to!S("\u2008 foo\t \u2007")), "foo\t \u2007"));
3538 assert(equal(stripLeft(to!S("\u0085 μ \u0085 \u00BB \r")), \u0085 \u00BB \r"));
3539 assert(equal(stripLeft(to!S("1")), "1"));
3540 assert(equal(stripLeft(to!S("\U0010FFFE")), "\U0010FFFE"));
3541 assert(equal(stripLeft(to!S("")), ""));
3543 assert(equal(stripRight(to!S(" foo\t ")), " foo"));
3544 assert(equal(stripRight(to!S("\u2008 foo\t \u2007")), "\u2008 foo"));
3545 assert(equal(stripRight(to!S("\u0085 μ \u0085 \u00BB \r")), "\u0085 μ \u0085 \u00BB"));
3546 assert(equal(stripRight(to!S("1")), "1"));
3547 assert(equal(stripRight(to!S("\U0010FFFE")), "\U0010FFFE"));
3548 assert(equal(stripRight(to!S("")), ""));
3550 assert(equal(strip(to!S(" foo\t ")), "foo"));
3551 assert(equal(strip(to!S("\u2008 foo\t \u2007")), "foo"));
3552 assert(equal(strip(to!S("\u0085 μ \u0085 \u00BB \r")), \u0085 \u00BB"));
3553 assert(equal(strip(to!S("\U0010FFFE")), "\U0010FFFE"));
3554 assert(equal(strip(to!S("")), ""));
3559 @safe pure unittest
3561 import std.array : sameHead, sameTail;
3562 import std.exception : assertCTFEable;
3563 assertCTFEable!(
3565 wstring s = " ";
3566 assert(s.sameTail(s.stripLeft()));
3567 assert(s.sameHead(s.stripRight()));
3571 /// Ditto
3572 auto strip(Range, Char)(Range str, const(Char)[] chars)
3573 if (((isBidirectionalRange!Range && isSomeChar!(ElementEncodingType!Range)) ||
3574 isConvertibleToString!Range) && isSomeChar!Char)
3576 static if (isConvertibleToString!Range)
3577 return strip!(StringTypeOf!Range)(str, chars);
3578 else
3579 return stripRight(stripLeft(str, chars), chars);
3583 @safe pure unittest
3585 assert(strip(" hello world ", "x") ==
3586 " hello world ");
3587 assert(strip(" hello world ", " ") ==
3588 "hello world");
3589 assert(strip(" xyxyhello worldxyxy ", "xy ") ==
3590 "hello world");
3591 assert(strip("\u2020hello\u2020"w, "\u2020"w) == "hello"w);
3592 assert(strip("\U00010001hello\U00010001"d, "\U00010001"d) == "hello"d);
3593 assert(strip(" hello ", "") == " hello ");
3596 @safe pure unittest
3598 assert(testAliasedString!strip(" xyz hello world xyz ", "xyz "));
3601 /// Ditto
3602 auto strip(Range, Char)(Range str, const(Char)[] leftChars, const(Char)[] rightChars)
3603 if (((isBidirectionalRange!Range && isSomeChar!(ElementEncodingType!Range)) ||
3604 isConvertibleToString!Range) && isSomeChar!Char)
3606 static if (isConvertibleToString!Range)
3607 return strip!(StringTypeOf!Range)(str, leftChars, rightChars);
3608 else
3609 return stripRight(stripLeft(str, leftChars), rightChars);
3613 @safe pure unittest
3615 assert(strip("xxhelloyy", "x", "y") == "hello");
3616 assert(strip(" xyxyhello worldxyxyzz ", "xy ", "xyz ") ==
3617 "hello world");
3618 assert(strip("\u2020hello\u2028"w, "\u2020"w, "\u2028"w) == "hello"w);
3619 assert(strip("\U00010001hello\U00010002"d, "\U00010001"d, "\U00010002"d) ==
3620 "hello"d);
3621 assert(strip(" hello ", "", "") == " hello ");
3624 @safe pure unittest
3626 assert(testAliasedString!strip(" xy hello world pq ", "xy ", "pq "));
3629 @safe pure unittest
3631 import std.algorithm.comparison : equal;
3632 import std.conv : to;
3633 import std.exception : assertCTFEable;
3635 assertCTFEable!(
3637 static foreach (S; AliasSeq!( char[], const char[], string,
3638 wchar[], const wchar[], wstring,
3639 dchar[], const dchar[], dstring))
3641 assert(equal(stripLeft(to!S(" \tfoo\t "), "\t "), "foo\t "));
3642 assert(equal(stripLeft(to!S("\u2008 foo\t \u2007"), "\u2008 "),
3643 "foo\t \u2007"));
3644 assert(equal(stripLeft(to!S("\u0085 μ \u0085 \u00BB \r"), "\u0085 "),
3645 \u0085 \u00BB \r"));
3646 assert(equal(stripLeft(to!S("1"), " "), "1"));
3647 assert(equal(stripLeft(to!S("\U0010FFFE"), " "), "\U0010FFFE"));
3648 assert(equal(stripLeft(to!S(""), " "), ""));
3650 assert(equal(stripRight(to!S(" foo\t "), "\t "), " foo"));
3651 assert(equal(stripRight(to!S("\u2008 foo\t \u2007"), "\u2007\t "),
3652 "\u2008 foo"));
3653 assert(equal(stripRight(to!S("\u0085 μ \u0085 \u00BB \r"), "\r "),
3654 "\u0085 μ \u0085 \u00BB"));
3655 assert(equal(stripRight(to!S("1"), " "), "1"));
3656 assert(equal(stripRight(to!S("\U0010FFFE"), " "), "\U0010FFFE"));
3657 assert(equal(stripRight(to!S(""), " "), ""));
3659 assert(equal(strip(to!S(" foo\t "), "\t "), "foo"));
3660 assert(equal(strip(to!S("\u2008 foo\t \u2007"), "\u2008\u2007\t "),
3661 "foo"));
3662 assert(equal(strip(to!S("\u0085 μ \u0085 \u00BB \r"), "\u0085\r "),
3663 \u0085 \u00BB"));
3664 assert(equal(strip(to!S("\U0010FFFE"), " "), "\U0010FFFE"));
3665 assert(equal(strip(to!S(""), " "), ""));
3667 assert(equal(strip(to!S(" \nfoo\t "), "\n ", "\t "), "foo"));
3668 assert(equal(strip(to!S("\u2008\n foo\t \u2007"),
3669 "\u2008\n ", "\u2007\t "), "foo"));
3670 assert(equal(strip(to!S("\u0085 μ \u0085 \u00BB μ \u00BB\r"),
3671 "\u0085 ", "\u00BB\r "), \u0085 \u00BB μ"));
3672 assert(equal(strip(to!S("\U0010FFFE"), " ", " "), "\U0010FFFE"));
3673 assert(equal(strip(to!S(""), " ", " "), ""));
3678 @safe pure unittest
3680 import std.array : sameHead, sameTail;
3681 import std.exception : assertCTFEable;
3682 assertCTFEable!(
3684 wstring s = " xyz ";
3685 assert(s.sameTail(s.stripLeft(" ")));
3686 assert(s.sameHead(s.stripRight(" ")));
3692 If `str` ends with `delimiter`, then `str` is returned without
3693 `delimiter` on its end. If it `str` does $(I not) end with
3694 `delimiter`, then it is returned unchanged.
3696 If no `delimiter` is given, then one trailing `'\r'`, `'\n'`,
3697 `"\r\n"`, `'\f'`, `'\v'`, $(REF lineSep, std,uni), $(REF paraSep, std,uni), or $(REF nelSep, std,uni)
3698 is removed from the end of `str`. If `str` does not end with any of those characters,
3699 then it is returned unchanged.
3701 Params:
3702 str = string or indexable range of characters
3703 delimiter = string of characters to be sliced off end of str[]
3705 Returns:
3706 slice of str
3708 Range chomp(Range)(Range str)
3709 if ((isRandomAccessRange!Range && isSomeChar!(ElementEncodingType!Range) ||
3710 isNarrowString!Range) &&
3711 !isConvertibleToString!Range)
3713 import std.uni : lineSep, paraSep, nelSep;
3714 if (str.empty)
3715 return str;
3717 alias C = ElementEncodingType!Range;
3719 switch (str[$ - 1])
3721 case '\n':
3723 if (str.length > 1 && str[$ - 2] == '\r')
3724 return str[0 .. $ - 2];
3725 goto case;
3727 case '\r', '\v', '\f':
3728 return str[0 .. $ - 1];
3730 // Pop off the last character if lineSep, paraSep, or nelSep
3731 static if (is(C : const char))
3733 /* Manually decode:
3734 * lineSep is E2 80 A8
3735 * paraSep is E2 80 A9
3737 case 0xA8: // Last byte of lineSep
3738 case 0xA9: // Last byte of paraSep
3739 if (str.length > 2 && str[$ - 2] == 0x80 && str[$ - 3] == 0xE2)
3740 return str [0 .. $ - 3];
3741 goto default;
3743 /* Manually decode:
3744 * NEL is C2 85
3746 case 0x85:
3747 if (str.length > 1 && str[$ - 2] == 0xC2)
3748 return str [0 .. $ - 2];
3749 goto default;
3751 else
3753 case lineSep:
3754 case paraSep:
3755 case nelSep:
3756 return str[0 .. $ - 1];
3758 default:
3759 return str;
3763 /// Ditto
3764 Range chomp(Range, C2)(Range str, const(C2)[] delimiter)
3765 if ((isBidirectionalRange!Range && isSomeChar!(ElementEncodingType!Range) ||
3766 isNarrowString!Range) &&
3767 !isConvertibleToString!Range &&
3768 isSomeChar!C2)
3770 if (delimiter.empty)
3771 return chomp(str);
3773 alias C1 = ElementEncodingType!Range;
3775 static if (is(immutable C1 == immutable C2) && (isSomeString!Range || (hasSlicing!Range && C2.sizeof == 4)))
3777 import std.algorithm.searching : endsWith;
3778 if (str.endsWith(delimiter))
3779 return str[0 .. $ - delimiter.length];
3780 return str;
3782 else
3784 auto orig = str.save;
3786 static if (isSomeString!Range)
3787 alias C = dchar; // because strings auto-decode
3788 else
3789 alias C = C1; // and ranges do not
3791 foreach_reverse (C c; delimiter)
3793 if (str.empty || str.back != c)
3794 return orig;
3796 str.popBack();
3799 return str;
3804 @safe pure
3805 unittest
3807 import std.uni : lineSep, paraSep, nelSep;
3808 import std.utf : decode;
3809 assert(chomp(" hello world \n\r") == " hello world \n");
3810 assert(chomp(" hello world \r\n") == " hello world ");
3811 assert(chomp(" hello world \f") == " hello world ");
3812 assert(chomp(" hello world \v") == " hello world ");
3813 assert(chomp(" hello world \n\n") == " hello world \n");
3814 assert(chomp(" hello world \n\n ") == " hello world \n\n ");
3815 assert(chomp(" hello world \n\n" ~ [lineSep]) == " hello world \n\n");
3816 assert(chomp(" hello world \n\n" ~ [paraSep]) == " hello world \n\n");
3817 assert(chomp(" hello world \n\n" ~ [ nelSep]) == " hello world \n\n");
3818 assert(chomp(" hello world ") == " hello world ");
3819 assert(chomp(" hello world") == " hello world");
3820 assert(chomp("") == "");
3822 assert(chomp(" hello world", "orld") == " hello w");
3823 assert(chomp(" hello world", " he") == " hello world");
3824 assert(chomp("", "hello") == "");
3826 // Don't decode pointlessly
3827 assert(chomp("hello\xFE", "\r") == "hello\xFE");
3830 StringTypeOf!Range chomp(Range)(auto ref Range str)
3831 if (isConvertibleToString!Range)
3833 return chomp!(StringTypeOf!Range)(str);
3836 StringTypeOf!Range chomp(Range, C2)(auto ref Range str, const(C2)[] delimiter)
3837 if (isConvertibleToString!Range)
3839 return chomp!(StringTypeOf!Range, C2)(str, delimiter);
3842 @safe pure unittest
3844 assert(testAliasedString!chomp(" hello world \n\r"));
3845 assert(testAliasedString!chomp(" hello world", "orld"));
3848 @safe pure unittest
3850 import std.conv : to;
3851 import std.exception : assertCTFEable;
3853 assertCTFEable!(
3855 static foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
3857 // @@@ BUG IN COMPILER, MUST INSERT CAST
3858 assert(chomp(cast(S) null) is null);
3859 assert(chomp(to!S("hello")) == "hello");
3860 assert(chomp(to!S("hello\n")) == "hello");
3861 assert(chomp(to!S("hello\r")) == "hello");
3862 assert(chomp(to!S("hello\r\n")) == "hello");
3863 assert(chomp(to!S("hello\n\r")) == "hello\n");
3864 assert(chomp(to!S("hello\n\n")) == "hello\n");
3865 assert(chomp(to!S("hello\r\r")) == "hello\r");
3866 assert(chomp(to!S("hello\nxxx\n")) == "hello\nxxx");
3867 assert(chomp(to!S("hello\u2028")) == "hello");
3868 assert(chomp(to!S("hello\u2029")) == "hello");
3869 assert(chomp(to!S("hello\u0085")) == "hello");
3870 assert(chomp(to!S("hello\u2028\u2028")) == "hello\u2028");
3871 assert(chomp(to!S("hello\u2029\u2029")) == "hello\u2029");
3872 assert(chomp(to!S("hello\u2029\u2129")) == "hello\u2029\u2129");
3873 assert(chomp(to!S("hello\u2029\u0185")) == "hello\u2029\u0185");
3875 static foreach (T; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
3877 // @@@ BUG IN COMPILER, MUST INSERT CAST
3878 assert(chomp(cast(S) null, cast(T) null) is null);
3879 assert(chomp(to!S("hello\n"), cast(T) null) == "hello");
3880 assert(chomp(to!S("hello"), to!T("o")) == "hell");
3881 assert(chomp(to!S("hello"), to!T("p")) == "hello");
3882 // @@@ BUG IN COMPILER, MUST INSERT CAST
3883 assert(chomp(to!S("hello"), cast(T) null) == "hello");
3884 assert(chomp(to!S("hello"), to!T("llo")) == "he");
3885 assert(chomp(to!S("\uFF28ello"), to!T("llo")) == "\uFF28e");
3886 assert(chomp(to!S("\uFF28el\uFF4co"), to!T("l\uFF4co")) == "\uFF28e");
3891 // Ranges
3892 import std.array : array;
3893 import std.utf : byChar, byWchar, byDchar;
3894 assert(chomp("hello world\r\n" .byChar ).array == "hello world");
3895 assert(chomp("hello world\r\n"w.byWchar).array == "hello world"w);
3896 assert(chomp("hello world\r\n"d.byDchar).array == "hello world"d);
3898 assert(chomp("hello world"d.byDchar, "ld").array == "hello wor"d);
3900 assert(chomp("hello\u2020" .byChar , "\u2020").array == "hello");
3901 assert(chomp("hello\u2020"d.byDchar, "\u2020"d).array == "hello"d);
3906 If `str` starts with `delimiter`, then the part of `str` following
3907 `delimiter` is returned. If `str` does $(I not) start with
3909 `delimiter`, then it is returned unchanged.
3911 Params:
3912 str = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
3913 of characters
3914 delimiter = string of characters to be sliced off front of str[]
3916 Returns:
3917 slice of str
3919 Range chompPrefix(Range, C2)(Range str, const(C2)[] delimiter)
3920 if ((isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) ||
3921 isNarrowString!Range) &&
3922 !isConvertibleToString!Range &&
3923 isSomeChar!C2)
3925 alias C1 = ElementEncodingType!Range;
3927 static if (is(immutable C1 == immutable C2) && (isSomeString!Range || (hasSlicing!Range && C2.sizeof == 4)))
3929 import std.algorithm.searching : startsWith;
3930 if (str.startsWith(delimiter))
3931 return str[delimiter.length .. $];
3932 return str;
3934 else
3936 auto orig = str.save;
3938 static if (isSomeString!Range)
3939 alias C = dchar; // because strings auto-decode
3940 else
3941 alias C = C1; // and ranges do not
3943 foreach (C c; delimiter)
3945 if (str.empty || str.front != c)
3946 return orig;
3948 str.popFront();
3951 return str;
3956 @safe pure unittest
3958 assert(chompPrefix("hello world", "he") == "llo world");
3959 assert(chompPrefix("hello world", "hello w") == "orld");
3960 assert(chompPrefix("hello world", " world") == "hello world");
3961 assert(chompPrefix("", "hello") == "");
3964 StringTypeOf!Range chompPrefix(Range, C2)(auto ref Range str, const(C2)[] delimiter)
3965 if (isConvertibleToString!Range)
3967 return chompPrefix!(StringTypeOf!Range, C2)(str, delimiter);
3970 @safe pure
3971 unittest
3973 import std.algorithm.comparison : equal;
3974 import std.conv : to;
3975 import std.exception : assertCTFEable;
3976 assertCTFEable!(
3978 static foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
3980 static foreach (T; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
3982 assert(equal(chompPrefix(to!S("abcdefgh"), to!T("abcde")), "fgh"));
3983 assert(equal(chompPrefix(to!S("abcde"), to!T("abcdefgh")), "abcde"));
3984 assert(equal(chompPrefix(to!S("\uFF28el\uFF4co"), to!T("\uFF28el\uFF4co")), ""));
3985 assert(equal(chompPrefix(to!S("\uFF28el\uFF4co"), to!T("\uFF28el")), "\uFF4co"));
3986 assert(equal(chompPrefix(to!S("\uFF28el"), to!T("\uFF28el\uFF4co")), "\uFF28el"));
3991 // Ranges
3992 import std.array : array;
3993 import std.utf : byChar, byWchar, byDchar;
3994 assert(chompPrefix("hello world" .byChar , "hello"d).array == " world");
3995 assert(chompPrefix("hello world"w.byWchar, "hello" ).array == " world"w);
3996 assert(chompPrefix("hello world"d.byDchar, "hello"w).array == " world"d);
3997 assert(chompPrefix("hello world"c.byDchar, "hello"w).array == " world"d);
3999 assert(chompPrefix("hello world"d.byDchar, "lx").array == "hello world"d);
4000 assert(chompPrefix("hello world"d.byDchar, "hello world xx").array == "hello world"d);
4002 assert(chompPrefix("\u2020world" .byChar , "\u2020").array == "world");
4003 assert(chompPrefix("\u2020world"d.byDchar, "\u2020"d).array == "world"d);
4006 @safe pure unittest
4008 assert(testAliasedString!chompPrefix("hello world", "hello"));
4012 Returns `str` without its last character, if there is one. If `str`
4013 ends with `"\r\n"`, then both are removed. If `str` is empty, then
4014 it is returned unchanged.
4016 Params:
4017 str = string (must be valid UTF)
4018 Returns:
4019 slice of str
4022 Range chop(Range)(Range str)
4023 if ((isBidirectionalRange!Range && isSomeChar!(ElementEncodingType!Range) ||
4024 isNarrowString!Range) &&
4025 !isConvertibleToString!Range)
4027 if (str.empty)
4028 return str;
4030 static if (isSomeString!Range)
4032 if (str.length >= 2 && str[$ - 1] == '\n' && str[$ - 2] == '\r')
4033 return str[0 .. $ - 2];
4034 str.popBack();
4035 return str;
4037 else
4039 alias C = Unqual!(ElementEncodingType!Range);
4040 C c = str.back;
4041 str.popBack();
4042 if (c == '\n')
4044 if (!str.empty && str.back == '\r')
4045 str.popBack();
4046 return str;
4048 // Pop back a dchar, not just a code unit
4049 static if (C.sizeof == 1)
4051 int cnt = 1;
4052 while ((c & 0xC0) == 0x80)
4054 if (str.empty)
4055 break;
4056 c = str.back;
4057 str.popBack();
4058 if (++cnt > 4)
4059 break;
4062 else static if (C.sizeof == 2)
4064 if (c >= 0xD800 && c <= 0xDBFF)
4066 if (!str.empty)
4067 str.popBack();
4070 else static if (C.sizeof == 4)
4073 else
4074 static assert(0);
4075 return str;
4080 @safe pure unittest
4082 assert(chop("hello world") == "hello worl");
4083 assert(chop("hello world\n") == "hello world");
4084 assert(chop("hello world\r") == "hello world");
4085 assert(chop("hello world\n\r") == "hello world\n");
4086 assert(chop("hello world\r\n") == "hello world");
4087 assert(chop("Walter Bright") == "Walter Brigh");
4088 assert(chop("") == "");
4091 StringTypeOf!Range chop(Range)(auto ref Range str)
4092 if (isConvertibleToString!Range)
4094 return chop!(StringTypeOf!Range)(str);
4097 @safe pure unittest
4099 assert(testAliasedString!chop("hello world"));
4102 @safe pure unittest
4104 import std.array : array;
4105 import std.utf : byChar, byWchar, byDchar, byCodeUnit, invalidUTFstrings;
4107 assert(chop("hello world".byChar).array == "hello worl");
4108 assert(chop("hello world\n"w.byWchar).array == "hello world"w);
4109 assert(chop("hello world\r"d.byDchar).array == "hello world"d);
4110 assert(chop("hello world\n\r".byChar).array == "hello world\n");
4111 assert(chop("hello world\r\n"w.byWchar).array == "hello world"w);
4112 assert(chop("Walter Bright"d.byDchar).array == "Walter Brigh"d);
4113 assert(chop("".byChar).array == "");
4115 assert(chop(`ミツバチと科学者` .byCodeUnit).array == "ミツバチと科学");
4116 assert(chop(`ミツバチと科学者`w.byCodeUnit).array == "ミツバチと科学"w);
4117 assert(chop(`ミツバチと科学者`d.byCodeUnit).array == "ミツバチと科学"d);
4119 auto ca = invalidUTFstrings!char();
4120 foreach (s; ca)
4122 foreach (c; chop(s.byCodeUnit))
4127 auto wa = invalidUTFstrings!wchar();
4128 foreach (s; wa)
4130 foreach (c; chop(s.byCodeUnit))
4136 @safe pure unittest
4138 import std.algorithm.comparison : equal;
4139 import std.conv : to;
4140 import std.exception : assertCTFEable;
4142 assertCTFEable!(
4144 static foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
4146 assert(chop(cast(S) null) is null);
4147 assert(equal(chop(to!S("hello")), "hell"));
4148 assert(equal(chop(to!S("hello\r\n")), "hello"));
4149 assert(equal(chop(to!S("hello\n\r")), "hello\n"));
4150 assert(equal(chop(to!S("Verité")), "Verit"));
4151 assert(equal(chop(to!S(`さいごの果実`)), "さいごの果"));
4152 assert(equal(chop(to!S(`ミツバチと科学者`)), "ミツバチと科学"));
4159 Left justify `s` in a field `width` characters wide. `fillChar`
4160 is the character that will be used to fill up the space in the field that
4161 `s` doesn't fill.
4163 Params:
4164 s = string
4165 width = minimum field width
4166 fillChar = used to pad end up to `width` characters
4168 Returns:
4169 GC allocated string
4171 See_Also:
4172 $(LREF leftJustifier), which does not allocate
4174 S leftJustify(S)(S s, size_t width, dchar fillChar = ' ')
4175 if (isSomeString!S)
4177 import std.array : array;
4178 return leftJustifier(s, width, fillChar).array;
4182 @safe pure unittest
4184 assert(leftJustify("hello", 7, 'X') == "helloXX");
4185 assert(leftJustify("hello", 2, 'X') == "hello");
4186 assert(leftJustify("hello", 9, 'X') == "helloXXXX");
4190 Left justify `s` in a field `width` characters wide. `fillChar`
4191 is the character that will be used to fill up the space in the field that
4192 `s` doesn't fill.
4194 Params:
4195 r = string or range of characters
4196 width = minimum field width
4197 fillChar = used to pad end up to `width` characters
4199 Returns:
4200 a lazy range of the left justified result
4202 See_Also:
4203 $(LREF rightJustifier)
4206 auto leftJustifier(Range)(Range r, size_t width, dchar fillChar = ' ')
4207 if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) &&
4208 !isConvertibleToString!Range)
4210 alias C = Unqual!(ElementEncodingType!Range);
4212 static if (C.sizeof == 1)
4214 import std.utf : byDchar, byChar;
4215 return leftJustifier(r.byDchar, width, fillChar).byChar;
4217 else static if (C.sizeof == 2)
4219 import std.utf : byDchar, byWchar;
4220 return leftJustifier(r.byDchar, width, fillChar).byWchar;
4222 else static if (C.sizeof == 4)
4224 static struct Result
4226 private:
4227 Range _input;
4228 size_t _width;
4229 dchar _fillChar;
4230 size_t len;
4232 public:
4234 @property bool empty()
4236 return len >= _width && _input.empty;
4239 @property C front()
4241 return _input.empty ? _fillChar : _input.front;
4244 void popFront()
4246 ++len;
4247 if (!_input.empty)
4248 _input.popFront();
4251 static if (isForwardRange!Range)
4253 @property typeof(this) save() return scope
4255 auto ret = this;
4256 ret._input = _input.save;
4257 return ret;
4262 return Result(r, width, fillChar);
4264 else
4265 static assert(0);
4269 @safe pure @nogc nothrow
4270 unittest
4272 import std.algorithm.comparison : equal;
4273 import std.utf : byChar;
4274 assert(leftJustifier("hello", 2).equal("hello".byChar));
4275 assert(leftJustifier("hello", 7).equal("hello ".byChar));
4276 assert(leftJustifier("hello", 7, 'x').equal("helloxx".byChar));
4279 auto leftJustifier(Range)(auto ref Range r, size_t width, dchar fillChar = ' ')
4280 if (isConvertibleToString!Range)
4282 return leftJustifier!(StringTypeOf!Range)(r, width, fillChar);
4285 @safe pure unittest
4287 auto r = "hello".leftJustifier(8);
4288 r.popFront();
4289 auto save = r.save;
4290 r.popFront();
4291 assert(r.front == 'l');
4292 assert(save.front == 'e');
4295 @safe pure unittest
4297 assert(testAliasedString!leftJustifier("hello", 2));
4301 Right justify `s` in a field `width` characters wide. `fillChar`
4302 is the character that will be used to fill up the space in the field that
4303 `s` doesn't fill.
4305 Params:
4306 s = string
4307 width = minimum field width
4308 fillChar = used to pad end up to `width` characters
4310 Returns:
4311 GC allocated string
4313 See_Also:
4314 $(LREF rightJustifier), which does not allocate
4316 S rightJustify(S)(S s, size_t width, dchar fillChar = ' ')
4317 if (isSomeString!S)
4319 import std.array : array;
4320 return rightJustifier(s, width, fillChar).array;
4324 @safe pure unittest
4326 assert(rightJustify("hello", 7, 'X') == "XXhello");
4327 assert(rightJustify("hello", 2, 'X') == "hello");
4328 assert(rightJustify("hello", 9, 'X') == "XXXXhello");
4332 Right justify `s` in a field `width` characters wide. `fillChar`
4333 is the character that will be used to fill up the space in the field that
4334 `s` doesn't fill.
4336 Params:
4337 r = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
4338 of characters
4339 width = minimum field width
4340 fillChar = used to pad end up to `width` characters
4342 Returns:
4343 a lazy range of the right justified result
4345 See_Also:
4346 $(LREF leftJustifier)
4349 auto rightJustifier(Range)(Range r, size_t width, dchar fillChar = ' ')
4350 if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
4351 !isConvertibleToString!Range)
4353 alias C = Unqual!(ElementEncodingType!Range);
4355 static if (C.sizeof == 1)
4357 import std.utf : byDchar, byChar;
4358 return rightJustifier(r.byDchar, width, fillChar).byChar;
4360 else static if (C.sizeof == 2)
4362 import std.utf : byDchar, byWchar;
4363 return rightJustifier(r.byDchar, width, fillChar).byWchar;
4365 else static if (C.sizeof == 4)
4367 static struct Result
4369 private:
4370 Range _input;
4371 size_t _width;
4372 alias nfill = _width; // number of fill characters to prepend
4373 dchar _fillChar;
4374 bool inited;
4376 // Lazy initialization so constructor is trivial and cannot fail
4377 void initialize()
4379 // Replace _width with nfill
4380 // (use alias instead of union because CTFE cannot deal with unions)
4381 assert(_width, "width of 0 not allowed");
4382 static if (hasLength!Range)
4384 immutable len = _input.length;
4385 nfill = (_width > len) ? _width - len : 0;
4387 else
4389 // Lookahead to see now many fill characters are needed
4390 import std.range : take;
4391 import std.range.primitives : walkLength;
4392 nfill = _width - walkLength(_input.save.take(_width), _width);
4394 inited = true;
4397 public:
4398 this(Range input, size_t width, dchar fillChar) pure nothrow
4400 _input = input;
4401 _fillChar = fillChar;
4402 _width = width;
4405 @property bool empty()
4407 return !nfill && _input.empty;
4410 @property C front()
4412 if (!nfill)
4413 return _input.front; // fast path
4414 if (!inited)
4415 initialize();
4416 return nfill ? _fillChar : _input.front;
4419 void popFront()
4421 if (!nfill)
4422 _input.popFront(); // fast path
4423 else
4425 if (!inited)
4426 initialize();
4427 if (nfill)
4428 --nfill;
4429 else
4430 _input.popFront();
4434 @property typeof(this) save()
4436 auto ret = this;
4437 ret._input = _input.save;
4438 return ret;
4442 return Result(r, width, fillChar);
4444 else
4445 static assert(0, "Invalid character type of " ~ C.stringof);
4449 @safe pure @nogc nothrow
4450 unittest
4452 import std.algorithm.comparison : equal;
4453 import std.utf : byChar;
4454 assert(rightJustifier("hello", 2).equal("hello".byChar));
4455 assert(rightJustifier("hello", 7).equal(" hello".byChar));
4456 assert(rightJustifier("hello", 7, 'x').equal("xxhello".byChar));
4459 auto rightJustifier(Range)(auto ref Range r, size_t width, dchar fillChar = ' ')
4460 if (isConvertibleToString!Range)
4462 return rightJustifier!(StringTypeOf!Range)(r, width, fillChar);
4465 @safe pure unittest
4467 assert(testAliasedString!rightJustifier("hello", 2));
4470 @safe pure unittest
4472 auto r = "hello"d.rightJustifier(6);
4473 r.popFront();
4474 auto save = r.save;
4475 r.popFront();
4476 assert(r.front == 'e');
4477 assert(save.front == 'h');
4479 auto t = "hello".rightJustifier(7);
4480 t.popFront();
4481 assert(t.front == ' ');
4482 t.popFront();
4483 assert(t.front == 'h');
4485 auto u = "hello"d.rightJustifier(5);
4486 u.popFront();
4487 u.popFront();
4488 u.popFront();
4492 Center `s` in a field `width` characters wide. `fillChar`
4493 is the character that will be used to fill up the space in the field that
4494 `s` doesn't fill.
4496 Params:
4497 s = The string to center
4498 width = Width of the field to center `s` in
4499 fillChar = The character to use for filling excess space in the field
4501 Returns:
4502 The resulting _center-justified string. The returned string is
4503 GC-allocated. To avoid GC allocation, use $(LREF centerJustifier)
4504 instead.
4506 S center(S)(S s, size_t width, dchar fillChar = ' ')
4507 if (isSomeString!S)
4509 import std.array : array;
4510 return centerJustifier(s, width, fillChar).array;
4514 @safe pure unittest
4516 assert(center("hello", 7, 'X') == "XhelloX");
4517 assert(center("hello", 2, 'X') == "hello");
4518 assert(center("hello", 9, 'X') == "XXhelloXX");
4521 @safe pure
4522 unittest
4524 import std.conv : to;
4525 import std.exception : assertCTFEable;
4527 assertCTFEable!(
4529 static foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
4531 S s = to!S("hello");
4533 assert(leftJustify(s, 2) == "hello");
4534 assert(rightJustify(s, 2) == "hello");
4535 assert(center(s, 2) == "hello");
4537 assert(leftJustify(s, 7) == "hello ");
4538 assert(rightJustify(s, 7) == " hello");
4539 assert(center(s, 7) == " hello ");
4541 assert(leftJustify(s, 8) == "hello ");
4542 assert(rightJustify(s, 8) == " hello");
4543 assert(center(s, 8) == " hello ");
4545 assert(leftJustify(s, 8, '\u0100') == "hello\u0100\u0100\u0100");
4546 assert(rightJustify(s, 8, '\u0100') == "\u0100\u0100\u0100hello");
4547 assert(center(s, 8, '\u0100') == "\u0100hello\u0100\u0100");
4549 assert(leftJustify(s, 8, 'ö') == "helloööö");
4550 assert(rightJustify(s, 8, 'ö') == "öööhello");
4551 assert(center(s, 8, 'ö') == "öhelloöö");
4557 Center justify `r` in a field `width` characters wide. `fillChar`
4558 is the character that will be used to fill up the space in the field that
4559 `r` doesn't fill.
4561 Params:
4562 r = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
4563 of characters
4564 width = minimum field width
4565 fillChar = used to pad end up to `width` characters
4567 Returns:
4568 a lazy range of the center justified result
4570 See_Also:
4571 $(LREF leftJustifier)
4572 $(LREF rightJustifier)
4575 auto centerJustifier(Range)(Range r, size_t width, dchar fillChar = ' ')
4576 if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
4577 !isConvertibleToString!Range)
4579 alias C = Unqual!(ElementEncodingType!Range);
4581 static if (C.sizeof == 1)
4583 import std.utf : byDchar, byChar;
4584 return centerJustifier(r.byDchar, width, fillChar).byChar;
4586 else static if (C.sizeof == 2)
4588 import std.utf : byDchar, byWchar;
4589 return centerJustifier(r.byDchar, width, fillChar).byWchar;
4591 else static if (C.sizeof == 4)
4593 import std.range : chain, repeat;
4594 import std.range.primitives : walkLength;
4596 auto len = walkLength(r.save, width);
4597 if (len > width)
4598 len = width;
4599 const nleft = (width - len) / 2;
4600 const nright = width - len - nleft;
4601 return chain(repeat(fillChar, nleft), r, repeat(fillChar, nright));
4603 else
4604 static assert(0);
4608 @safe pure @nogc nothrow
4609 unittest
4611 import std.algorithm.comparison : equal;
4612 import std.utf : byChar;
4613 assert(centerJustifier("hello", 2).equal("hello".byChar));
4614 assert(centerJustifier("hello", 8).equal(" hello ".byChar));
4615 assert(centerJustifier("hello", 7, 'x').equal("xhellox".byChar));
4618 auto centerJustifier(Range)(auto ref Range r, size_t width, dchar fillChar = ' ')
4619 if (isConvertibleToString!Range)
4621 return centerJustifier!(StringTypeOf!Range)(r, width, fillChar);
4624 @safe pure unittest
4626 assert(testAliasedString!centerJustifier("hello", 8));
4629 @safe unittest
4631 static auto byFwdRange(dstring s)
4633 static struct FRange
4635 @safe:
4636 dstring str;
4637 this(dstring s) { str = s; }
4638 @property bool empty() { return str.length == 0; }
4639 @property dchar front() { return str[0]; }
4640 void popFront() { str = str[1 .. $]; }
4641 @property FRange save() { return this; }
4643 return FRange(s);
4646 auto r = centerJustifier(byFwdRange("hello"d), 6);
4647 r.popFront();
4648 auto save = r.save;
4649 r.popFront();
4650 assert(r.front == 'l');
4651 assert(save.front == 'e');
4653 auto t = "hello".centerJustifier(7);
4654 t.popFront();
4655 assert(t.front == 'h');
4656 t.popFront();
4657 assert(t.front == 'e');
4659 auto u = byFwdRange("hello"d).centerJustifier(6);
4660 u.popFront();
4661 u.popFront();
4662 u.popFront();
4663 u.popFront();
4664 u.popFront();
4665 u.popFront();
4670 Replace each tab character in `s` with the number of spaces necessary
4671 to align the following character at the next tab stop.
4673 Params:
4674 s = string
4675 tabSize = distance between tab stops
4677 Returns:
4678 GC allocated string with tabs replaced with spaces
4680 auto detab(Range)(auto ref Range s, size_t tabSize = 8) pure
4681 if ((isForwardRange!Range && isSomeChar!(ElementEncodingType!Range))
4682 || __traits(compiles, StringTypeOf!Range))
4684 import std.array : array;
4685 return detabber(s, tabSize).array;
4689 @safe pure unittest
4691 assert(detab(" \n\tx", 9) == " \n x");
4694 @safe pure unittest
4696 static struct TestStruct
4698 string s;
4699 alias s this;
4702 static struct TestStruct2
4704 string s;
4705 alias s this;
4706 @disable this(this);
4709 string s = " \n\tx";
4710 string cmp = " \n x";
4711 auto t = TestStruct(s);
4712 assert(detab(t, 9) == cmp);
4713 assert(detab(TestStruct(s), 9) == cmp);
4714 assert(detab(TestStruct(s), 9) == detab(TestStruct(s), 9));
4715 assert(detab(TestStruct2(s), 9) == detab(TestStruct2(s), 9));
4716 assert(detab(TestStruct2(s), 9) == cmp);
4720 Replace each tab character in `r` with the number of spaces
4721 necessary to align the following character at the next tab stop.
4723 Params:
4724 r = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
4725 tabSize = distance between tab stops
4727 Returns:
4728 lazy forward range with tabs replaced with spaces
4730 auto detabber(Range)(Range r, size_t tabSize = 8)
4731 if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) &&
4732 !isConvertibleToString!Range)
4734 import std.uni : lineSep, paraSep, nelSep;
4735 import std.utf : codeUnitLimit, decodeFront;
4737 assert(tabSize > 0);
4739 alias C = Unqual!(ElementEncodingType!(Range));
4741 static struct Result
4743 private:
4744 Range _input;
4745 size_t _tabSize;
4746 size_t nspaces;
4747 int column;
4748 size_t index;
4750 public:
4752 this(Range input, size_t tabSize)
4754 _input = input;
4755 _tabSize = tabSize;
4758 static if (isInfinite!(Range))
4760 enum bool empty = false;
4762 else
4764 @property bool empty()
4766 return _input.empty && nspaces == 0;
4770 @property C front()
4772 if (nspaces)
4773 return ' ';
4774 static if (isSomeString!(Range))
4775 C c = _input[0];
4776 else
4777 C c = _input.front;
4778 if (index)
4779 return c;
4780 dchar dc;
4781 if (c < codeUnitLimit!(immutable(C)[]))
4783 dc = c;
4784 index = 1;
4786 else
4788 auto r = _input.save;
4789 dc = decodeFront(r, index); // lookahead to decode
4791 switch (dc)
4793 case '\r':
4794 case '\n':
4795 case paraSep:
4796 case lineSep:
4797 case nelSep:
4798 column = 0;
4799 break;
4801 case '\t':
4802 nspaces = _tabSize - (column % _tabSize);
4803 column += nspaces;
4804 c = ' ';
4805 break;
4807 default:
4808 ++column;
4809 break;
4811 return c;
4814 void popFront()
4816 if (!index)
4817 front;
4818 if (nspaces)
4819 --nspaces;
4820 if (!nspaces)
4822 static if (isSomeString!(Range))
4823 _input = _input[1 .. $];
4824 else
4825 _input.popFront();
4826 --index;
4830 @property typeof(this) save()
4832 auto ret = this;
4833 ret._input = _input.save;
4834 return ret;
4838 return Result(r, tabSize);
4842 @safe pure unittest
4844 import std.array : array;
4846 assert(detabber(" \n\tx", 9).array == " \n x");
4849 /// ditto
4850 auto detabber(Range)(auto ref Range r, size_t tabSize = 8)
4851 if (isConvertibleToString!Range)
4853 return detabber!(StringTypeOf!Range)(r, tabSize);
4856 @safe pure unittest
4858 assert(testAliasedString!detabber( " ab\t asdf ", 8));
4861 @safe pure unittest
4863 import std.algorithm.comparison : cmp;
4864 import std.conv : to;
4865 import std.exception : assertCTFEable;
4867 assertCTFEable!(
4869 static foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring))
4871 S s = to!S("This \tis\t a fofof\tof list");
4872 assert(cmp(detab(s), "This is a fofof of list") == 0);
4874 assert(detab(cast(S) null) is null);
4875 assert(detab("").empty);
4876 assert(detab("a") == "a");
4877 assert(detab("\t") == " ");
4878 assert(detab("\t", 3) == " ");
4879 assert(detab("\t", 9) == " ");
4880 assert(detab( " ab\t asdf ") == " ab asdf ");
4881 assert(detab( " \U00010000b\tasdf ") == " \U00010000b asdf ");
4882 assert(detab("\r\t", 9) == "\r ");
4883 assert(detab("\n\t", 9) == "\n ");
4884 assert(detab("\u0085\t", 9) == "\u0085 ");
4885 assert(detab("\u2028\t", 9) == "\u2028 ");
4886 assert(detab(" \u2029\t", 9) == " \u2029 ");
4892 @safe pure unittest
4894 import std.array : array;
4895 import std.utf : byChar, byWchar;
4897 assert(detabber(" \u2029\t".byChar, 9).array == " \u2029 ");
4898 auto r = "hel\tx".byWchar.detabber();
4899 assert(r.front == 'h');
4900 auto s = r.save;
4901 r.popFront();
4902 r.popFront();
4903 assert(r.front == 'l');
4904 assert(s.front == 'h');
4908 Replaces spaces in `s` with the optimal number of tabs.
4909 All spaces and tabs at the end of a line are removed.
4911 Params:
4912 s = String to convert.
4913 tabSize = Tab columns are `tabSize` spaces apart.
4915 Returns:
4916 GC allocated string with spaces replaced with tabs;
4917 use $(LREF entabber) to not allocate.
4919 See_Also:
4920 $(LREF entabber)
4922 auto entab(Range)(Range s, size_t tabSize = 8)
4923 if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range))
4925 import std.array : array;
4926 return entabber(s, tabSize).array;
4930 @safe pure unittest
4932 assert(entab(" x \n") == "\tx\n");
4935 auto entab(Range)(auto ref Range s, size_t tabSize = 8)
4936 if (!(isForwardRange!Range && isSomeChar!(ElementEncodingType!Range)) &&
4937 is(StringTypeOf!Range))
4939 return entab!(StringTypeOf!Range)(s, tabSize);
4942 @safe pure unittest
4944 assert(testAliasedString!entab(" x \n"));
4948 Replaces spaces in range `r` with the optimal number of tabs.
4949 All spaces and tabs at the end of a line are removed.
4951 Params:
4952 r = string or $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
4953 tabSize = distance between tab stops
4955 Returns:
4956 lazy forward range with spaces replaced with tabs
4958 See_Also:
4959 $(LREF entab)
4961 auto entabber(Range)(Range r, size_t tabSize = 8)
4962 if (isForwardRange!Range && !isConvertibleToString!Range)
4964 import std.uni : lineSep, paraSep, nelSep;
4965 import std.utf : codeUnitLimit, decodeFront;
4967 assert(tabSize > 0, "tabSize must be greater than 0");
4968 alias C = Unqual!(ElementEncodingType!Range);
4970 static struct Result
4972 private:
4973 Range _input;
4974 size_t _tabSize;
4975 size_t nspaces;
4976 size_t ntabs;
4977 int column;
4978 size_t index;
4980 @property C getFront()
4982 static if (isSomeString!Range)
4983 return _input[0]; // avoid autodecode
4984 else
4985 return _input.front;
4988 public:
4990 this(Range input, size_t tabSize)
4992 _input = input;
4993 _tabSize = tabSize;
4996 @property bool empty()
4998 if (ntabs || nspaces)
4999 return false;
5001 /* Since trailing spaces are removed,
5002 * look ahead for anything that is not a trailing space
5004 static if (isSomeString!Range)
5006 foreach (c; _input)
5008 if (c != ' ' && c != '\t')
5009 return false;
5011 return true;
5013 else
5015 if (_input.empty)
5016 return true;
5017 immutable c = _input.front;
5018 if (c != ' ' && c != '\t')
5019 return false;
5020 auto t = _input.save;
5021 t.popFront();
5022 foreach (c2; t)
5024 if (c2 != ' ' && c2 != '\t')
5025 return false;
5027 return true;
5031 @property C front()
5033 //writefln(" front(): ntabs = %s nspaces = %s index = %s front = '%s'", ntabs, nspaces, index, getFront);
5034 if (ntabs)
5035 return '\t';
5036 if (nspaces)
5037 return ' ';
5038 C c = getFront;
5039 if (index)
5040 return c;
5041 dchar dc;
5042 if (c < codeUnitLimit!(immutable(C)[]))
5044 index = 1;
5045 dc = c;
5046 if (c == ' ' || c == '\t')
5048 // Consume input until a non-blank is encountered
5049 immutable startcol = column;
5050 C cx;
5051 static if (isSomeString!Range)
5053 while (1)
5055 assert(_input.length, "input did not contain non "
5056 ~ "whitespace character");
5057 cx = _input[0];
5058 if (cx == ' ')
5059 ++column;
5060 else if (cx == '\t')
5061 column += _tabSize - (column % _tabSize);
5062 else
5063 break;
5064 _input = _input[1 .. $];
5067 else
5069 while (1)
5071 assert(_input.length, "input did not contain non "
5072 ~ "whitespace character");
5073 cx = _input.front;
5074 if (cx == ' ')
5075 ++column;
5076 else if (cx == '\t')
5077 column += _tabSize - (column % _tabSize);
5078 else
5079 break;
5080 _input.popFront();
5083 // Compute ntabs+nspaces to get from startcol to column
5084 immutable n = column - startcol;
5085 if (n == 1)
5087 nspaces = 1;
5089 else
5091 ntabs = column / _tabSize - startcol / _tabSize;
5092 if (ntabs == 0)
5093 nspaces = column - startcol;
5094 else
5095 nspaces = column % _tabSize;
5097 //writefln("\tstartcol = %s, column = %s, _tabSize = %s", startcol, column, _tabSize);
5098 //writefln("\tntabs = %s, nspaces = %s", ntabs, nspaces);
5099 if (cx < codeUnitLimit!(immutable(C)[]))
5101 dc = cx;
5102 index = 1;
5104 else
5106 auto r = _input.save;
5107 dc = decodeFront(r, index); // lookahead to decode
5109 switch (dc)
5111 case '\r':
5112 case '\n':
5113 case paraSep:
5114 case lineSep:
5115 case nelSep:
5116 column = 0;
5117 // Spaces followed by newline are ignored
5118 ntabs = 0;
5119 nspaces = 0;
5120 return cx;
5122 default:
5123 ++column;
5124 break;
5126 return ntabs ? '\t' : ' ';
5129 else
5131 auto r = _input.save;
5132 dc = decodeFront(r, index); // lookahead to decode
5134 //writefln("dc = x%x", dc);
5135 switch (dc)
5137 case '\r':
5138 case '\n':
5139 case paraSep:
5140 case lineSep:
5141 case nelSep:
5142 column = 0;
5143 break;
5145 default:
5146 ++column;
5147 break;
5149 return c;
5152 void popFront()
5154 //writefln("popFront(): ntabs = %s nspaces = %s index = %s front = '%s'", ntabs, nspaces, index, getFront);
5155 if (!index)
5156 front;
5157 if (ntabs)
5158 --ntabs;
5159 else if (nspaces)
5160 --nspaces;
5161 else if (!ntabs && !nspaces)
5163 static if (isSomeString!Range)
5164 _input = _input[1 .. $];
5165 else
5166 _input.popFront();
5167 --index;
5171 @property typeof(this) save()
5173 auto ret = this;
5174 ret._input = _input.save;
5175 return ret;
5179 return Result(r, tabSize);
5183 @safe pure unittest
5185 import std.array : array;
5186 assert(entabber(" x \n").array == "\tx\n");
5189 auto entabber(Range)(auto ref Range r, size_t tabSize = 8)
5190 if (isConvertibleToString!Range)
5192 return entabber!(StringTypeOf!Range)(r, tabSize);
5195 @safe pure unittest
5197 assert(testAliasedString!entabber(" ab asdf ", 8));
5200 @safe pure
5201 unittest
5203 import std.conv : to;
5204 import std.exception : assertCTFEable;
5206 assertCTFEable!(
5208 assert(entab(cast(string) null) is null);
5209 assert(entab("").empty);
5210 assert(entab("a") == "a");
5211 assert(entab(" ") == "");
5212 assert(entab(" x") == "\tx");
5213 assert(entab(" ab asdf ") == " ab\tasdf");
5214 assert(entab(" ab asdf ") == " ab\t asdf");
5215 assert(entab(" ab \t asdf ") == " ab\t asdf");
5216 assert(entab("1234567 \ta") == "1234567\t\ta");
5217 assert(entab("1234567 \ta") == "1234567\t\ta");
5218 assert(entab("1234567 \ta") == "1234567\t\ta");
5219 assert(entab("1234567 \ta") == "1234567\t\ta");
5220 assert(entab("1234567 \ta") == "1234567\t\ta");
5221 assert(entab("1234567 \ta") == "1234567\t\ta");
5222 assert(entab("1234567 \ta") == "1234567\t\ta");
5223 assert(entab("1234567 \ta") == "1234567\t\ta");
5224 assert(entab("1234567 \ta") == "1234567\t\t\ta");
5226 assert(entab("a ") == "a");
5227 assert(entab("a\v") == "a\v");
5228 assert(entab("a\f") == "a\f");
5229 assert(entab("a\n") == "a\n");
5230 assert(entab("a\n\r") == "a\n\r");
5231 assert(entab("a\r\n") == "a\r\n");
5232 assert(entab("a\u2028") == "a\u2028");
5233 assert(entab("a\u2029") == "a\u2029");
5234 assert(entab("a\u0085") == "a\u0085");
5235 assert(entab("a ") == "a");
5236 assert(entab("a\t") == "a");
5237 assert(entab("\uFF28\uFF45\uFF4C\uFF4C567 \t\uFF4F \t") ==
5238 "\uFF28\uFF45\uFF4C\uFF4C567\t\t\uFF4F");
5239 assert(entab(" \naa") == "\naa");
5240 assert(entab(" \r aa") == "\r aa");
5241 assert(entab(" \u2028 aa") == "\u2028 aa");
5242 assert(entab(" \u2029 aa") == "\u2029 aa");
5243 assert(entab(" \u0085 aa") == "\u0085 aa");
5247 @safe pure
5248 unittest
5250 import std.array : array;
5251 import std.utf : byChar;
5252 assert(entabber(" \u0085 aa".byChar).array == "\u0085 aa");
5253 assert(entabber(" \u2028\t aa \t".byChar).array == "\u2028\t aa");
5255 auto r = entabber("1234", 4);
5256 r.popFront();
5257 auto rsave = r.save;
5258 r.popFront();
5259 assert(r.front == '3');
5260 assert(rsave.front == '2');
5265 Replaces the characters in `str` which are keys in `transTable` with
5266 their corresponding values in `transTable`. `transTable` is an AA
5267 where its keys are `dchar` and its values are either `dchar` or some
5268 type of string. Also, if `toRemove` is given, the characters in it are
5269 removed from `str` prior to translation. `str` itself is unaltered.
5270 A copy with the changes is returned.
5272 See_Also:
5273 $(LREF tr),
5274 $(REF replace, std,array),
5275 $(REF substitute, std,algorithm,iteration)
5277 Params:
5278 str = The original string.
5279 transTable = The AA indicating which characters to replace and what to
5280 replace them with.
5281 toRemove = The characters to remove from the string.
5283 C1[] translate(C1, C2 = immutable char)(C1[] str,
5284 in dchar[dchar] transTable,
5285 const(C2)[] toRemove = null) @safe pure
5286 if (isSomeChar!C1 && isSomeChar!C2)
5288 import std.array : appender;
5289 auto buffer = appender!(C1[])();
5290 translateImpl(str, transTable, toRemove, buffer);
5291 return buffer.data;
5295 @safe pure unittest
5297 dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q'];
5298 assert(translate("hello world", transTable1) == "h5ll7 w7rld");
5300 assert(translate("hello world", transTable1, "low") == "h5 rd");
5302 string[dchar] transTable2 = ['e' : "5", 'o' : "orange"];
5303 assert(translate("hello world", transTable2) == "h5llorange worangerld");
5306 // https://issues.dlang.org/show_bug.cgi?id=13018
5307 @safe pure unittest
5309 immutable dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q'];
5310 assert(translate("hello world", transTable1) == "h5ll7 w7rld");
5312 assert(translate("hello world", transTable1, "low") == "h5 rd");
5314 immutable string[dchar] transTable2 = ['e' : "5", 'o' : "orange"];
5315 assert(translate("hello world", transTable2) == "h5llorange worangerld");
5318 @system pure unittest
5320 import std.conv : to;
5321 import std.exception : assertCTFEable;
5323 assertCTFEable!(
5325 static foreach (S; AliasSeq!( char[], const( char)[], immutable( char)[],
5326 wchar[], const(wchar)[], immutable(wchar)[],
5327 dchar[], const(dchar)[], immutable(dchar)[]))
5328 {(){ // workaround slow optimizations for large functions
5329 // https://issues.dlang.org/show_bug.cgi?id=2396
5330 assert(translate(to!S("hello world"), cast(dchar[dchar])['h' : 'q', 'l' : '5']) ==
5331 to!S("qe55o wor5d"));
5332 assert(translate(to!S("hello world"), cast(dchar[dchar])['o' : 'l', 'l' : '\U00010143']) ==
5333 to!S("he\U00010143\U00010143l wlr\U00010143d"));
5334 assert(translate(to!S("hello \U00010143 world"), cast(dchar[dchar])['h' : 'q', 'l': '5']) ==
5335 to!S("qe55o \U00010143 wor5d"));
5336 assert(translate(to!S("hello \U00010143 world"), cast(dchar[dchar])['o' : '0', '\U00010143' : 'o']) ==
5337 to!S("hell0 o w0rld"));
5338 assert(translate(to!S("hello world"), cast(dchar[dchar]) null) == to!S("hello world"));
5340 static foreach (T; AliasSeq!( char[], const( char)[], immutable( char)[],
5341 wchar[], const(wchar)[], immutable(wchar)[],
5342 dchar[], const(dchar)[], immutable(dchar)[]))
5343 (){ // workaround slow optimizations for large functions
5344 // https://issues.dlang.org/show_bug.cgi?id=2396
5345 static foreach (R; AliasSeq!(dchar[dchar], const dchar[dchar],
5346 immutable dchar[dchar]))
5348 R tt = ['h' : 'q', 'l' : '5'];
5349 assert(translate(to!S("hello world"), tt, to!T("r"))
5350 == to!S("qe55o wo5d"));
5351 assert(translate(to!S("hello world"), tt, to!T("helo"))
5352 == to!S(" wrd"));
5353 assert(translate(to!S("hello world"), tt, to!T("q5"))
5354 == to!S("qe55o wor5d"));
5356 }();
5358 auto s = to!S("hello world");
5359 dchar[dchar] transTable = ['h' : 'q', 'l' : '5'];
5360 static assert(is(typeof(s) == typeof(translate(s, transTable))));
5361 assert(translate(s, transTable) == "qe55o wor5d");
5362 }();}
5366 /++ Ditto +/
5367 C1[] translate(C1, S, C2 = immutable char)(C1[] str,
5368 in S[dchar] transTable,
5369 const(C2)[] toRemove = null) @safe pure
5370 if (isSomeChar!C1 && isSomeString!S && isSomeChar!C2)
5372 import std.array : appender;
5373 auto buffer = appender!(C1[])();
5374 translateImpl(str, transTable, toRemove, buffer);
5375 return buffer.data;
5378 @system pure unittest
5380 import std.conv : to;
5381 import std.exception : assertCTFEable;
5383 assertCTFEable!(
5385 static foreach (S; AliasSeq!( char[], const( char)[], immutable( char)[],
5386 wchar[], const(wchar)[], immutable(wchar)[],
5387 dchar[], const(dchar)[], immutable(dchar)[]))
5388 {(){ // workaround slow optimizations for large functions
5389 // https://issues.dlang.org/show_bug.cgi?id=2396
5390 assert(translate(to!S("hello world"), ['h' : "yellow", 'l' : "42"]) ==
5391 to!S("yellowe4242o wor42d"));
5392 assert(translate(to!S("hello world"), ['o' : "owl", 'l' : "\U00010143\U00010143"]) ==
5393 to!S("he\U00010143\U00010143\U00010143\U00010143owl wowlr\U00010143\U00010143d"));
5394 assert(translate(to!S("hello \U00010143 world"), ['h' : "yellow", 'l' : "42"]) ==
5395 to!S("yellowe4242o \U00010143 wor42d"));
5396 assert(translate(to!S("hello \U00010143 world"), ['o' : "owl", 'l' : "\U00010143\U00010143"]) ==
5397 to!S("he\U00010143\U00010143\U00010143\U00010143owl \U00010143 wowlr\U00010143\U00010143d"));
5398 assert(translate(to!S("hello \U00010143 world"), ['h' : ""]) ==
5399 to!S("ello \U00010143 world"));
5400 assert(translate(to!S("hello \U00010143 world"), ['\U00010143' : ""]) ==
5401 to!S("hello world"));
5402 assert(translate(to!S("hello world"), cast(string[dchar]) null) == to!S("hello world"));
5404 static foreach (T; AliasSeq!( char[], const( char)[], immutable( char)[],
5405 wchar[], const(wchar)[], immutable(wchar)[],
5406 dchar[], const(dchar)[], immutable(dchar)[]))
5407 (){ // workaround slow optimizations for large functions
5408 // https://issues.dlang.org/show_bug.cgi?id=2396
5409 static foreach (R; AliasSeq!(string[dchar], const string[dchar],
5410 immutable string[dchar]))
5412 R tt = ['h' : "yellow", 'l' : "42"];
5413 assert(translate(to!S("hello world"), tt, to!T("r")) ==
5414 to!S("yellowe4242o wo42d"));
5415 assert(translate(to!S("hello world"), tt, to!T("helo")) ==
5416 to!S(" wrd"));
5417 assert(translate(to!S("hello world"), tt, to!T("y42")) ==
5418 to!S("yellowe4242o wor42d"));
5419 assert(translate(to!S("hello world"), tt, to!T("hello world")) ==
5420 to!S(""));
5421 assert(translate(to!S("hello world"), tt, to!T("42")) ==
5422 to!S("yellowe4242o wor42d"));
5424 }();
5426 auto s = to!S("hello world");
5427 string[dchar] transTable = ['h' : "silly", 'l' : "putty"];
5428 static assert(is(typeof(s) == typeof(translate(s, transTable))));
5429 assert(translate(s, transTable) == "sillyeputtyputtyo worputtyd");
5430 }();}
5435 This is an overload of `translate` which takes an existing buffer to write the contents to.
5437 Params:
5438 str = The original string.
5439 transTable = The AA indicating which characters to replace and what to
5440 replace them with.
5441 toRemove = The characters to remove from the string.
5442 buffer = An output range to write the contents to.
5444 void translate(C1, C2 = immutable char, Buffer)(const(C1)[] str,
5445 in dchar[dchar] transTable,
5446 const(C2)[] toRemove,
5447 Buffer buffer)
5448 if (isSomeChar!C1 && isSomeChar!C2 && isOutputRange!(Buffer, C1))
5450 translateImpl(str, transTable, toRemove, buffer);
5454 @safe pure unittest
5456 import std.array : appender;
5457 dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q'];
5458 auto buffer = appender!(dchar[])();
5459 translate("hello world", transTable1, null, buffer);
5460 assert(buffer.data == "h5ll7 w7rld");
5462 buffer.clear();
5463 translate("hello world", transTable1, "low", buffer);
5464 assert(buffer.data == "h5 rd");
5466 buffer.clear();
5467 string[dchar] transTable2 = ['e' : "5", 'o' : "orange"];
5468 translate("hello world", transTable2, null, buffer);
5469 assert(buffer.data == "h5llorange worangerld");
5472 // https://issues.dlang.org/show_bug.cgi?id=13018
5473 @safe pure unittest
5475 import std.array : appender;
5476 immutable dchar[dchar] transTable1 = ['e' : '5', 'o' : '7', '5': 'q'];
5477 auto buffer = appender!(dchar[])();
5478 translate("hello world", transTable1, null, buffer);
5479 assert(buffer.data == "h5ll7 w7rld");
5481 buffer.clear();
5482 translate("hello world", transTable1, "low", buffer);
5483 assert(buffer.data == "h5 rd");
5485 buffer.clear();
5486 immutable string[dchar] transTable2 = ['e' : "5", 'o' : "orange"];
5487 translate("hello world", transTable2, null, buffer);
5488 assert(buffer.data == "h5llorange worangerld");
5491 /++ Ditto +/
5492 void translate(C1, S, C2 = immutable char, Buffer)(C1[] str,
5493 in S[dchar] transTable,
5494 const(C2)[] toRemove,
5495 Buffer buffer)
5496 if (isSomeChar!C1 && isSomeString!S && isSomeChar!C2 && isOutputRange!(Buffer, S))
5498 translateImpl(str, transTable, toRemove, buffer);
5501 private void translateImpl(C1, T, C2, Buffer)(const(C1)[] str,
5502 scope T transTable,
5503 const(C2)[] toRemove,
5504 Buffer buffer)
5506 bool[dchar] removeTable;
5508 foreach (dchar c; toRemove)
5509 removeTable[c] = true;
5511 foreach (dchar c; str)
5513 if (c in removeTable)
5514 continue;
5516 auto newC = c in transTable;
5518 if (newC)
5519 put(buffer, *newC);
5520 else
5521 put(buffer, c);
5526 This is an $(I $(RED ASCII-only)) overload of $(LREF _translate). It
5527 will $(I not) work with Unicode. It exists as an optimization for the
5528 cases where Unicode processing is not necessary.
5530 Unlike the other overloads of $(LREF _translate), this one does not take
5531 an AA. Rather, it takes a `string` generated by $(LREF makeTransTable).
5533 The array generated by `makeTransTable` is `256` elements long such that
5534 the index is equal to the ASCII character being replaced and the value is
5535 equal to the character that it's being replaced with. Note that translate
5536 does not decode any of the characters, so you can actually pass it Extended
5537 ASCII characters if you want to (ASCII only actually uses `128`
5538 characters), but be warned that Extended ASCII characters are not valid
5539 Unicode and therefore will result in a `UTFException` being thrown from
5540 most other Phobos functions.
5542 Also, because no decoding occurs, it is possible to use this overload to
5543 translate ASCII characters within a proper UTF-8 string without altering the
5544 other, non-ASCII characters. It's replacing any code unit greater than
5545 `127` with another code unit or replacing any code unit with another code
5546 unit greater than `127` which will cause UTF validation issues.
5548 See_Also:
5549 $(LREF tr),
5550 $(REF replace, std,array),
5551 $(REF substitute, std,algorithm,iteration)
5553 Params:
5554 str = The original string.
5555 transTable = The string indicating which characters to replace and what
5556 to replace them with. It is generated by $(LREF makeTransTable).
5557 toRemove = The characters to remove from the string.
5559 C[] translate(C = immutable char)(scope const(char)[] str, scope const(char)[] transTable,
5560 scope const(char)[] toRemove = null) @trusted pure nothrow
5561 if (is(immutable C == immutable char))
5564 import std.conv : to;
5565 assert(transTable.length == 256, "transTable had invalid length of " ~
5566 to!string(transTable.length));
5570 bool[256] remTable = false;
5572 foreach (char c; toRemove)
5573 remTable[c] = true;
5575 size_t count = 0;
5576 foreach (char c; str)
5578 if (!remTable[c])
5579 ++count;
5582 auto buffer = new char[count];
5584 size_t i = 0;
5585 foreach (char c; str)
5587 if (!remTable[c])
5588 buffer[i++] = transTable[c];
5591 return cast(C[])(buffer);
5595 @safe pure nothrow unittest
5597 auto transTable1 = makeTrans("eo5", "57q");
5598 assert(translate("hello world", transTable1) == "h5ll7 w7rld");
5600 assert(translate("hello world", transTable1, "low") == "h5 rd");
5604 * Do same thing as $(LREF makeTransTable) but allocate the translation table
5605 * on the GC heap.
5607 * Use $(LREF makeTransTable) instead.
5609 string makeTrans(scope const(char)[] from, scope const(char)[] to) @trusted pure nothrow
5611 return makeTransTable(from, to)[].idup;
5615 @safe pure nothrow unittest
5617 auto transTable1 = makeTrans("eo5", "57q");
5618 assert(translate("hello world", transTable1) == "h5ll7 w7rld");
5620 assert(translate("hello world", transTable1, "low") == "h5 rd");
5623 /*******
5624 * Construct 256 character translation table, where characters in from[] are replaced
5625 * by corresponding characters in to[].
5627 * Params:
5628 * from = array of chars, less than or equal to 256 in length
5629 * to = corresponding array of chars to translate to
5630 * Returns:
5631 * translation array
5633 char[256] makeTransTable(scope const(char)[] from, scope const(char)[] to) @safe pure nothrow @nogc
5636 import std.ascii : isASCII;
5637 assert(from.length == to.length, "from.length must match to.length");
5638 assert(from.length <= 256, "from.length must be <= 256");
5639 foreach (char c; from)
5640 assert(isASCII(c),
5641 "all characters in from must be valid ascii character");
5642 foreach (char c; to)
5643 assert(isASCII(c),
5644 "all characters in to must be valid ascii character");
5648 char[256] result = void;
5650 foreach (i; 0 .. result.length)
5651 result[i] = cast(char) i;
5652 foreach (i, c; from)
5653 result[c] = to[i];
5654 return result;
5658 @safe pure unittest
5660 assert(translate("hello world", makeTransTable("hl", "q5")) == "qe55o wor5d");
5661 assert(translate("hello world", makeTransTable("12345", "67890")) == "hello world");
5664 @safe pure unittest
5666 import std.conv : to;
5667 import std.exception : assertCTFEable;
5669 assertCTFEable!(
5671 static foreach (C; AliasSeq!(char, const char, immutable char))
5673 assert(translate!C("hello world", makeTransTable("hl", "q5")) == to!(C[])("qe55o wor5d"));
5675 auto s = to!(C[])("hello world");
5676 auto transTable = makeTransTable("hl", "q5");
5677 static assert(is(typeof(s) == typeof(translate!C(s, transTable))));
5678 assert(translate(s, transTable) == "qe55o wor5d");
5681 static foreach (S; AliasSeq!(char[], const(char)[], immutable(char)[]))
5683 assert(translate(to!S("hello world"), makeTransTable("hl", "q5")) == to!S("qe55o wor5d"));
5684 assert(translate(to!S("hello \U00010143 world"), makeTransTable("hl", "q5")) ==
5685 to!S("qe55o \U00010143 wor5d"));
5686 assert(translate(to!S("hello world"), makeTransTable("ol", "1o")) == to!S("heoo1 w1rod"));
5687 assert(translate(to!S("hello world"), makeTransTable("", "")) == to!S("hello world"));
5688 assert(translate(to!S("hello world"), makeTransTable("12345", "67890")) == to!S("hello world"));
5689 assert(translate(to!S("hello \U00010143 world"), makeTransTable("12345", "67890")) ==
5690 to!S("hello \U00010143 world"));
5692 static foreach (T; AliasSeq!(char[], const(char)[], immutable(char)[]))
5694 assert(translate(to!S("hello world"), makeTransTable("hl", "q5"), to!T("r")) ==
5695 to!S("qe55o wo5d"));
5696 assert(translate(to!S("hello \U00010143 world"), makeTransTable("hl", "q5"), to!T("r")) ==
5697 to!S("qe55o \U00010143 wo5d"));
5698 assert(translate(to!S("hello world"), makeTransTable("hl", "q5"), to!T("helo")) ==
5699 to!S(" wrd"));
5700 assert(translate(to!S("hello world"), makeTransTable("hl", "q5"), to!T("q5")) ==
5701 to!S("qe55o wor5d"));
5708 This is an $(I $(RED ASCII-only)) overload of `translate` which takes an existing buffer to write the contents to.
5710 Params:
5711 str = The original string.
5712 transTable = The string indicating which characters to replace and what
5713 to replace them with. It is generated by $(LREF makeTransTable).
5714 toRemove = The characters to remove from the string.
5715 buffer = An output range to write the contents to.
5717 void translate(C = immutable char, Buffer)(scope const(char)[] str, scope const(char)[] transTable,
5718 scope const(char)[] toRemove, Buffer buffer) @trusted pure
5719 if (is(immutable C == immutable char) && isOutputRange!(Buffer, char))
5722 assert(transTable.length == 256, format!
5723 "transTable.length %s must equal 256"(transTable.length));
5727 bool[256] remTable = false;
5729 foreach (char c; toRemove)
5730 remTable[c] = true;
5732 foreach (char c; str)
5734 if (!remTable[c])
5735 put(buffer, transTable[c]);
5740 @safe pure unittest
5742 import std.array : appender;
5743 auto buffer = appender!(char[])();
5744 auto transTable1 = makeTransTable("eo5", "57q");
5745 translate("hello world", transTable1, null, buffer);
5746 assert(buffer.data == "h5ll7 w7rld");
5748 buffer.clear();
5749 translate("hello world", transTable1, "low", buffer);
5750 assert(buffer.data == "h5 rd");
5753 /**********************************************
5754 * Return string that is the 'successor' to s[].
5755 * If the rightmost character is a-zA-Z0-9, it is incremented within
5756 * its case or digits. If it generates a carry, the process is
5757 * repeated with the one to its immediate left.
5760 S succ(S)(S s) @safe pure
5761 if (isSomeString!S)
5763 import std.ascii : isAlphaNum;
5765 if (s.length && isAlphaNum(s[$ - 1]))
5767 auto r = s.dup;
5768 size_t i = r.length - 1;
5770 while (1)
5772 dchar c = s[i];
5773 dchar carry;
5775 switch (c)
5777 case '9':
5778 c = '0';
5779 carry = '1';
5780 goto Lcarry;
5781 case 'z':
5782 case 'Z':
5783 c -= 'Z' - 'A';
5784 carry = c;
5785 Lcarry:
5786 r[i] = cast(char) c;
5787 if (i == 0)
5789 auto t = new typeof(r[0])[r.length + 1];
5790 t[0] = cast(char) carry;
5791 t[1 .. $] = r[];
5792 return t;
5794 i--;
5795 break;
5797 default:
5798 if (isAlphaNum(c))
5799 r[i]++;
5800 return r;
5804 return s;
5808 @safe pure unittest
5810 assert(succ("1") == "2");
5811 assert(succ("9") == "10");
5812 assert(succ("999") == "1000");
5813 assert(succ("zz99") == "aaa00");
5816 @safe pure unittest
5818 import std.conv : to;
5819 import std.exception : assertCTFEable;
5821 assertCTFEable!(
5823 assert(succ(string.init) is null);
5824 assert(succ("!@#$%") == "!@#$%");
5825 assert(succ("1") == "2");
5826 assert(succ("9") == "10");
5827 assert(succ("999") == "1000");
5828 assert(succ("zz99") == "aaa00");
5834 Replaces the characters in `str` which are in `from` with the
5835 the corresponding characters in `to` and returns the resulting string.
5837 `tr` is based on
5838 $(HTTP pubs.opengroup.org/onlinepubs/9699919799/utilities/_tr.html, Posix's tr),
5839 though it doesn't do everything that the Posix utility does.
5841 Params:
5842 str = The original string.
5843 from = The characters to replace.
5844 to = The characters to replace with.
5845 modifiers = String containing modifiers.
5847 Modifiers:
5848 $(BOOKTABLE,
5849 $(TR $(TD Modifier) $(TD Description))
5850 $(TR $(TD `'c'`) $(TD Complement the list of characters in `from`))
5851 $(TR $(TD `'d'`) $(TD Removes matching characters with no corresponding
5852 replacement in `to`))
5853 $(TR $(TD `'s'`) $(TD Removes adjacent duplicates in the replaced
5854 characters))
5857 If the modifier `'d'` is present, then the number of characters in
5858 `to` may be only `0` or `1`.
5860 If the modifier `'d'` is $(I not) present, and `to` is empty, then
5861 `to` is taken to be the same as `from`.
5863 If the modifier `'d'` is $(I not) present, and `to` is shorter than
5864 `from`, then `to` is extended by replicating the last character in
5865 `to`.
5867 Both `from` and `to` may contain ranges using the `'-'` character
5868 (e.g. `"a-d"` is synonymous with `"abcd"`.) Neither accept a leading
5869 `'^'` as meaning the complement of the string (use the `'c'` modifier
5870 for that).
5872 See_Also:
5873 $(LREF translate),
5874 $(REF replace, std,array),
5875 $(REF substitute, std,algorithm,iteration)
5877 C1[] tr(C1, C2, C3, C4 = immutable char)
5878 (C1[] str, const(C2)[] from, const(C3)[] to, const(C4)[] modifiers = null)
5880 import std.array : appender;
5881 import std.conv : conv_to = to;
5882 import std.utf : decode;
5884 bool mod_c;
5885 bool mod_d;
5886 bool mod_s;
5888 foreach (char c; modifiers)
5890 switch (c)
5892 case 'c': mod_c = 1; break; // complement
5893 case 'd': mod_d = 1; break; // delete unreplaced chars
5894 case 's': mod_s = 1; break; // squeeze duplicated replaced chars
5895 default: assert(false, "modifier must be one of ['c', 'd', 's'] not "
5896 ~ c);
5900 if (to.empty && !mod_d)
5901 to = conv_to!(typeof(to))(from);
5903 auto result = appender!(C1[])();
5904 bool modified;
5905 dchar lastc;
5907 foreach (dchar c; str)
5909 dchar lastf;
5910 dchar lastt;
5911 dchar newc;
5912 int n = 0;
5914 for (size_t i = 0; i < from.length; )
5916 immutable f = decode(from, i);
5917 if (f == '-' && lastf != dchar.init && i < from.length)
5919 immutable nextf = decode(from, i);
5920 if (lastf <= c && c <= nextf)
5922 n += c - lastf - 1;
5923 if (mod_c)
5924 goto Lnotfound;
5925 goto Lfound;
5927 n += nextf - lastf;
5928 lastf = lastf.init;
5929 continue;
5932 if (c == f)
5933 { if (mod_c)
5934 goto Lnotfound;
5935 goto Lfound;
5937 lastf = f;
5938 n++;
5940 if (!mod_c)
5941 goto Lnotfound;
5942 n = 0; // consider it 'found' at position 0
5944 Lfound:
5946 // Find the nth character in to[]
5947 dchar nextt;
5948 for (size_t i = 0; i < to.length; )
5950 immutable t = decode(to, i);
5951 if (t == '-' && lastt != dchar.init && i < to.length)
5953 nextt = decode(to, i);
5954 n -= nextt - lastt;
5955 if (n < 0)
5957 newc = nextt + n + 1;
5958 goto Lnewc;
5960 lastt = dchar.init;
5961 continue;
5963 if (n == 0)
5964 { newc = t;
5965 goto Lnewc;
5967 lastt = t;
5968 nextt = t;
5969 n--;
5971 if (mod_d)
5972 continue;
5973 newc = nextt;
5975 Lnewc:
5976 if (mod_s && modified && newc == lastc)
5977 continue;
5978 result.put(newc);
5979 assert(newc != dchar.init, "character must not be dchar.init");
5980 modified = true;
5981 lastc = newc;
5982 continue;
5984 Lnotfound:
5985 result.put(c);
5986 lastc = c;
5987 modified = false;
5990 return result.data;
5994 @safe pure unittest
5996 assert(tr("abcdef", "cd", "CD") == "abCDef");
5997 assert(tr("1st March, 2018", "March", "MAR", "s") == "1st MAR, 2018");
5998 assert(tr("abcdef", "ef", "", "d") == "abcd");
5999 assert(tr("14-Jul-87", "a-zA-Z", " ", "cs") == " Jul ");
6002 @safe pure unittest
6004 import std.algorithm.comparison : equal;
6005 import std.conv : to;
6006 import std.exception : assertCTFEable;
6008 // Complete list of test types; too slow to test'em all
6009 // alias TestTypes = AliasSeq!(
6010 // char[], const( char)[], immutable( char)[],
6011 // wchar[], const(wchar)[], immutable(wchar)[],
6012 // dchar[], const(dchar)[], immutable(dchar)[]);
6014 // Reduced list of test types
6015 alias TestTypes = AliasSeq!(char[], const(wchar)[], immutable(dchar)[]);
6017 assertCTFEable!(
6019 foreach (S; TestTypes)
6021 foreach (T; TestTypes)
6023 foreach (U; TestTypes)
6025 assert(equal(tr(to!S("abcdef"), to!T("cd"), to!U("CD")), "abCDef"));
6026 assert(equal(tr(to!S("abcdef"), to!T("b-d"), to!U("B-D")), "aBCDef"));
6027 assert(equal(tr(to!S("abcdefgh"), to!T("b-dh"), to!U("B-Dx")), "aBCDefgx"));
6028 assert(equal(tr(to!S("abcdefgh"), to!T("b-dh"), to!U("B-CDx")), "aBCDefgx"));
6029 assert(equal(tr(to!S("abcdefgh"), to!T("b-dh"), to!U("B-BCDx")), "aBCDefgx"));
6030 assert(equal(tr(to!S("abcdef"), to!T("ef"), to!U("*"), to!S("c")), "****ef"));
6031 assert(equal(tr(to!S("abcdef"), to!T("ef"), to!U(""), to!T("d")), "abcd"));
6032 assert(equal(tr(to!S("hello goodbye"), to!T("lo"), to!U(""), to!U("s")), "helo godbye"));
6033 assert(equal(tr(to!S("hello goodbye"), to!T("lo"), to!U("x"), "s"), "hex gxdbye"));
6034 assert(equal(tr(to!S("14-Jul-87"), to!T("a-zA-Z"), to!U(" "), "cs"), " Jul "));
6035 assert(equal(tr(to!S("Abc"), to!T("AAA"), to!U("XYZ")), "Xbc"));
6039 auto s = to!S("hello world");
6040 static assert(is(typeof(s) == typeof(tr(s, "he", "if"))));
6041 assert(tr(s, "he", "if") == "ifllo world");
6046 @system pure unittest
6048 import core.exception : AssertError;
6049 import std.exception : assertThrown;
6050 assertThrown!AssertError(tr("abcdef", "cd", "CD", "X"));
6054 * Takes a string `s` and determines if it represents a number. This function
6055 * also takes an optional parameter, `bAllowSep`, which will accept the
6056 * separator characters `','` and `'__'` within the string. But these
6057 * characters should be stripped from the string before using any
6058 * of the conversion functions like `to!int()`, `to!float()`, and etc
6059 * else an error will occur.
6061 * Also please note, that no spaces are allowed within the string
6062 * anywhere whether it's a leading, trailing, or embedded space(s),
6063 * thus they too must be stripped from the string before using this
6064 * function, or any of the conversion functions.
6066 * Params:
6067 * s = the string or random access range to check
6068 * bAllowSep = accept separator characters or not
6070 * Returns:
6071 * `bool`
6073 bool isNumeric(S)(S s, bool bAllowSep = false)
6074 if (isSomeString!S ||
6075 (isRandomAccessRange!S &&
6076 hasSlicing!S &&
6077 isSomeChar!(ElementType!S) &&
6078 !isInfinite!S))
6080 import std.algorithm.comparison : among;
6081 import std.ascii : isASCII;
6083 // ASCII only case insensitive comparison with two ranges
6084 static bool asciiCmp(S1)(S1 a, string b)
6086 import std.algorithm.comparison : equal;
6087 import std.algorithm.iteration : map;
6088 import std.ascii : toLower;
6089 import std.utf : byChar;
6090 return a.map!toLower.equal(b.byChar.map!toLower);
6093 // auto-decoding special case, we're only comparing characters
6094 // in the ASCII range so there's no reason to decode
6095 static if (isSomeString!S)
6097 import std.utf : byCodeUnit;
6098 auto codeUnits = s.byCodeUnit;
6100 else
6102 alias codeUnits = s;
6105 if (codeUnits.empty)
6106 return false;
6108 // Check for NaN (Not a Number) and for Infinity
6109 if (codeUnits.among!((a, b) => asciiCmp(a.save, b))
6110 ("nan", "nani", "nan+nani", "inf", "-inf"))
6111 return true;
6113 immutable frontResult = codeUnits.front;
6114 if (frontResult == '-' || frontResult == '+')
6115 codeUnits.popFront;
6117 immutable iLen = codeUnits.length;
6118 bool bDecimalPoint, bExponent, bComplex, sawDigits;
6120 for (size_t i = 0; i < iLen; i++)
6122 immutable c = codeUnits[i];
6124 if (!c.isASCII)
6125 return false;
6127 // Digits are good, skip to the next character
6128 if (c >= '0' && c <= '9')
6130 sawDigits = true;
6131 continue;
6134 // Check for the complex type, and if found
6135 // reset the flags for checking the 2nd number.
6136 if (c == '+')
6138 if (!i)
6139 return false;
6140 bDecimalPoint = false;
6141 bExponent = false;
6142 bComplex = true;
6143 sawDigits = false;
6144 continue;
6147 // Allow only one exponent per number
6148 if (c == 'e' || c == 'E')
6150 // A 2nd exponent found, return not a number
6151 if (bExponent || i + 1 >= iLen)
6152 return false;
6153 // Look forward for the sign, and if
6154 // missing then this is not a number.
6155 if (codeUnits[i + 1] != '-' && codeUnits[i + 1] != '+')
6156 return false;
6157 bExponent = true;
6158 i++;
6159 continue;
6161 // Allow only one decimal point per number to be used
6162 if (c == '.')
6164 // A 2nd decimal point found, return not a number
6165 if (bDecimalPoint)
6166 return false;
6167 bDecimalPoint = true;
6168 continue;
6170 // Check for ending literal characters: "f,u,l,i,ul,fi,li",
6171 // and whether they're being used with the correct datatype.
6172 if (i == iLen - 2)
6174 if (!sawDigits)
6175 return false;
6176 // Integer Whole Number
6177 if (asciiCmp(codeUnits[i .. iLen], "ul") &&
6178 (!bDecimalPoint && !bExponent && !bComplex))
6179 return true;
6180 // Floating-Point Number
6181 if (codeUnits[i .. iLen].among!((a, b) => asciiCmp(a, b))("fi", "li") &&
6182 (bDecimalPoint || bExponent || bComplex))
6183 return true;
6184 if (asciiCmp(codeUnits[i .. iLen], "ul") &&
6185 (bDecimalPoint || bExponent || bComplex))
6186 return false;
6187 // Could be a Integer or a Float, thus
6188 // all these suffixes are valid for both
6189 return codeUnits[i .. iLen].among!((a, b) => asciiCmp(a, b))
6190 ("ul", "fi", "li") != 0;
6192 if (i == iLen - 1)
6194 if (!sawDigits)
6195 return false;
6196 // Integer Whole Number
6197 if (c.among!('u', 'l', 'U', 'L')() &&
6198 (!bDecimalPoint && !bExponent && !bComplex))
6199 return true;
6200 // Check to see if the last character in the string
6201 // is the required 'i' character
6202 if (bComplex)
6203 return c.among!('i', 'I')() != 0;
6204 // Floating-Point Number
6205 return c.among!('l', 'L', 'f', 'F', 'i', 'I')() != 0;
6207 // Check if separators are allowed to be in the numeric string
6208 if (!bAllowSep || !c.among!('_', ',')())
6209 return false;
6212 return sawDigits;
6216 * Integer Whole Number: (byte, ubyte, short, ushort, int, uint, long, and ulong)
6217 * ['+'|'-']digit(s)[U|L|UL]
6219 @safe @nogc pure nothrow unittest
6221 assert(isNumeric("123"));
6222 assert(isNumeric("123UL"));
6223 assert(isNumeric("123L"));
6224 assert(isNumeric("+123U"));
6225 assert(isNumeric("-123L"));
6229 * Floating-Point Number: (float, double, real, ifloat, idouble, and ireal)
6230 * ['+'|'-']digit(s)[.][digit(s)][[e-|e+]digit(s)][i|f|L|Li|fi]]
6231 * or [nan|nani|inf|-inf]
6233 @safe @nogc pure nothrow unittest
6235 assert(isNumeric("+123"));
6236 assert(isNumeric("-123.01"));
6237 assert(isNumeric("123.3e-10f"));
6238 assert(isNumeric("123.3e-10fi"));
6239 assert(isNumeric("123.3e-10L"));
6241 assert(isNumeric("nan"));
6242 assert(isNumeric("nani"));
6243 assert(isNumeric("-inf"));
6247 * Floating-Point Number: (cfloat, cdouble, and creal)
6248 * ['+'|'-']digit(s)[.][digit(s)][[e-|e+]digit(s)][+]
6249 * [digit(s)[.][digit(s)][[e-|e+]digit(s)][i|f|L|Li|fi]]
6250 * or [nan|nani|nan+nani|inf|-inf]
6252 @safe @nogc pure nothrow unittest
6254 assert(isNumeric("-123e-1+456.9e-10Li"));
6255 assert(isNumeric("+123e+10+456i"));
6256 assert(isNumeric("123+456"));
6259 @safe @nogc pure nothrow unittest
6261 assert(!isNumeric("F"));
6262 assert(!isNumeric("L"));
6263 assert(!isNumeric("U"));
6264 assert(!isNumeric("i"));
6265 assert(!isNumeric("fi"));
6266 assert(!isNumeric("ul"));
6267 assert(!isNumeric("li"));
6268 assert(!isNumeric("."));
6269 assert(!isNumeric("-"));
6270 assert(!isNumeric("+"));
6271 assert(!isNumeric("e-"));
6272 assert(!isNumeric("e+"));
6273 assert(!isNumeric(".f"));
6274 assert(!isNumeric("e+f"));
6275 assert(!isNumeric("++1"));
6276 assert(!isNumeric(""));
6277 assert(!isNumeric("1E+1E+1"));
6278 assert(!isNumeric("1E1"));
6279 assert(!isNumeric("\x81"));
6282 // Test string types
6283 @safe unittest
6285 import std.conv : to;
6287 static foreach (T; AliasSeq!(string, char[], wstring, wchar[], dstring, dchar[]))
6289 assert("123".to!T.isNumeric());
6290 assert("123UL".to!T.isNumeric());
6291 assert("123fi".to!T.isNumeric());
6292 assert("123li".to!T.isNumeric());
6293 assert(!"--123L".to!T.isNumeric());
6297 // test ranges
6298 @system pure unittest
6300 import std.range : refRange;
6301 import std.utf : byCodeUnit;
6303 assert("123".byCodeUnit.isNumeric());
6304 assert("123UL".byCodeUnit.isNumeric());
6305 assert("123fi".byCodeUnit.isNumeric());
6306 assert("123li".byCodeUnit.isNumeric());
6307 assert(!"--123L".byCodeUnit.isNumeric());
6309 dstring z = "0";
6310 assert(isNumeric(refRange(&z)));
6312 dstring nani = "nani";
6313 assert(isNumeric(refRange(&nani)));
6316 /// isNumeric works with CTFE
6317 @safe pure unittest
6319 enum a = isNumeric("123.00E-5+1234.45E-12Li");
6320 enum b = isNumeric("12345xxxx890");
6322 static assert( a);
6323 static assert(!b);
6326 @system unittest
6328 import std.conv : to;
6329 import std.exception : assertCTFEable;
6331 assertCTFEable!(
6333 // Test the isNumeric(in string) function
6334 assert(isNumeric("1") == true );
6335 assert(isNumeric("1.0") == true );
6336 assert(isNumeric("1e-1") == true );
6337 assert(isNumeric("12345xxxx890") == false );
6338 assert(isNumeric("567L") == true );
6339 assert(isNumeric("23UL") == true );
6340 assert(isNumeric("-123..56f") == false );
6341 assert(isNumeric("12.3.5.6") == false );
6342 assert(isNumeric(" 12.356") == false );
6343 assert(isNumeric("123 5.6") == false );
6344 assert(isNumeric("1233E-1+1.0e-1i") == true );
6346 assert(isNumeric("123.00E-5+1234.45E-12Li") == true);
6347 assert(isNumeric("123.00e-5+1234.45E-12iL") == false);
6348 assert(isNumeric("123.00e-5+1234.45e-12uL") == false);
6349 assert(isNumeric("123.00E-5+1234.45e-12lu") == false);
6351 assert(isNumeric("123fi") == true);
6352 assert(isNumeric("123li") == true);
6353 assert(isNumeric("--123L") == false);
6354 assert(isNumeric("+123.5UL") == false);
6355 assert(isNumeric("123f") == true);
6356 assert(isNumeric("123.u") == false);
6358 // @@@BUG@@ to!string(float) is not CTFEable.
6359 // Related: formatValue(T) if (is(FloatingPointTypeOf!T))
6360 if (!__ctfe)
6362 assert(isNumeric(to!string(real.nan)) == true);
6363 assert(isNumeric(to!string(-real.infinity)) == true);
6366 string s = "$250.99-";
6367 assert(isNumeric(s[1 .. s.length - 2]) == true);
6368 assert(isNumeric(s) == false);
6369 assert(isNumeric(s[0 .. s.length - 1]) == false);
6372 assert(!isNumeric("-"));
6373 assert(!isNumeric("+"));
6376 /*****************************
6377 * Soundex algorithm.
6379 * The Soundex algorithm converts a word into 4 characters
6380 * based on how the word sounds phonetically. The idea is that
6381 * two spellings that sound alike will have the same Soundex
6382 * value, which means that Soundex can be used for fuzzy matching
6383 * of names.
6385 * Params:
6386 * str = String or InputRange to convert to Soundex representation.
6388 * Returns:
6389 * The four character array with the Soundex result in it.
6390 * The array has zero's in it if there is no Soundex representation for the string.
6392 * See_Also:
6393 * $(LINK2 http://en.wikipedia.org/wiki/Soundex, Wikipedia),
6394 * $(LUCKY The Soundex Indexing System)
6395 * $(LREF soundex)
6397 * Note:
6398 * Only works well with English names.
6400 char[4] soundexer(Range)(Range str)
6401 if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) &&
6402 !isConvertibleToString!Range)
6404 alias C = Unqual!(ElementEncodingType!Range);
6406 static immutable dex =
6407 // ABCDEFGHIJKLMNOPQRSTUVWXYZ
6408 "01230120022455012623010202";
6410 char[4] result = void;
6411 size_t b = 0;
6412 C lastc;
6413 foreach (C c; str)
6415 if (c >= 'a' && c <= 'z')
6416 c -= 'a' - 'A';
6417 else if (c >= 'A' && c <= 'Z')
6420 else
6422 lastc = lastc.init;
6423 continue;
6425 if (b == 0)
6427 result[0] = cast(char) c;
6428 b++;
6429 lastc = dex[c - 'A'];
6431 else
6433 if (c == 'H' || c == 'W')
6434 continue;
6435 if (c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U')
6436 lastc = lastc.init;
6437 c = dex[c - 'A'];
6438 if (c != '0' && c != lastc)
6440 result[b] = cast(char) c;
6441 b++;
6442 lastc = c;
6444 if (b == 4)
6445 goto Lret;
6448 if (b == 0)
6449 result[] = 0;
6450 else
6451 result[b .. 4] = '0';
6452 Lret:
6453 return result;
6456 /// ditto
6457 char[4] soundexer(Range)(auto ref Range str)
6458 if (isConvertibleToString!Range)
6460 return soundexer!(StringTypeOf!Range)(str);
6464 @safe unittest
6466 assert(soundexer("Gauss") == "G200");
6467 assert(soundexer("Ghosh") == "G200");
6469 assert(soundexer("Robert") == "R163");
6470 assert(soundexer("Rupert") == "R163");
6472 assert(soundexer("0123^&^^**&^") == ['\0', '\0', '\0', '\0']);
6475 /*****************************
6476 * Like $(LREF soundexer), but with different parameters
6477 * and return value.
6479 * Params:
6480 * str = String to convert to Soundex representation.
6481 * buffer = Optional 4 char array to put the resulting Soundex
6482 * characters into. If null, the return value
6483 * buffer will be allocated on the heap.
6484 * Returns:
6485 * The four character array with the Soundex result in it.
6486 * Returns null if there is no Soundex representation for the string.
6487 * See_Also:
6488 * $(LREF soundexer)
6490 char[] soundex(scope const(char)[] str, return scope char[] buffer = null)
6491 @safe pure nothrow
6494 assert(buffer is null || buffer.length >= 4);
6496 out (result)
6498 if (result !is null)
6500 assert(result.length == 4, "Result must have length of 4");
6501 assert(result[0] >= 'A' && result[0] <= 'Z', "The first character of "
6502 ~ " the result must be an upper character not " ~ result);
6503 foreach (char c; result[1 .. 4])
6504 assert(c >= '0' && c <= '6', "the last three character of the"
6505 ~ " result must be number between 0 and 6 not " ~ result);
6510 char[4] result = soundexer(str);
6511 if (result[0] == 0)
6512 return null;
6513 if (buffer is null)
6514 buffer = new char[4];
6515 buffer[] = result[];
6516 return buffer;
6520 @safe unittest
6522 assert(soundex("Gauss") == "G200");
6523 assert(soundex("Ghosh") == "G200");
6525 assert(soundex("Robert") == "R163");
6526 assert(soundex("Rupert") == "R163");
6528 assert(soundex("0123^&^^**&^") == null);
6531 @safe pure nothrow unittest
6533 import std.exception : assertCTFEable;
6534 assertCTFEable!(
6536 char[4] buffer;
6538 assert(soundex(null) == null);
6539 assert(soundex("") == null);
6540 assert(soundex("0123^&^^**&^") == null);
6541 assert(soundex("Euler") == "E460");
6542 assert(soundex(" Ellery ") == "E460");
6543 assert(soundex("Gauss") == "G200");
6544 assert(soundex("Ghosh") == "G200");
6545 assert(soundex("Hilbert") == "H416");
6546 assert(soundex("Heilbronn") == "H416");
6547 assert(soundex("Knuth") == "K530");
6548 assert(soundex("Kant", buffer) == "K530");
6549 assert(soundex("Lloyd") == "L300");
6550 assert(soundex("Ladd") == "L300");
6551 assert(soundex("Lukasiewicz", buffer) == "L222");
6552 assert(soundex("Lissajous") == "L222");
6553 assert(soundex("Robert") == "R163");
6554 assert(soundex("Rupert") == "R163");
6555 assert(soundex("Rubin") == "R150");
6556 assert(soundex("Washington") == "W252");
6557 assert(soundex("Lee") == "L000");
6558 assert(soundex("Gutierrez") == "G362");
6559 assert(soundex("Pfister") == "P236");
6560 assert(soundex("Jackson") == "J250");
6561 assert(soundex("Tymczak") == "T522");
6562 assert(soundex("Ashcraft") == "A261");
6564 assert(soundex("Woo") == "W000");
6565 assert(soundex("Pilgrim") == "P426");
6566 assert(soundex("Flingjingwaller") == "F452");
6567 assert(soundex("PEARSE") == "P620");
6568 assert(soundex("PIERCE") == "P620");
6569 assert(soundex("Price") == "P620");
6570 assert(soundex("CATHY") == "C300");
6571 assert(soundex("KATHY") == "K300");
6572 assert(soundex("Jones") == "J520");
6573 assert(soundex("johnsons") == "J525");
6574 assert(soundex("Hardin") == "H635");
6575 assert(soundex("Martinez") == "M635");
6577 import std.utf : byChar, byDchar, byWchar;
6578 assert(soundexer("Martinez".byChar ) == "M635");
6579 assert(soundexer("Martinez".byWchar) == "M635");
6580 assert(soundexer("Martinez".byDchar) == "M635");
6584 @safe pure unittest
6586 assert(testAliasedString!soundexer("Martinez"));
6590 /***************************************************
6591 * Construct an associative array consisting of all
6592 * abbreviations that uniquely map to the strings in values.
6594 * This is useful in cases where the user is expected to type
6595 * in one of a known set of strings, and the program will helpfully
6596 * auto-complete the string once sufficient characters have been
6597 * entered that uniquely identify it.
6599 string[string] abbrev(string[] values) @safe pure
6601 import std.algorithm.sorting : sort;
6603 string[string] result;
6605 // Make a copy when sorting so we follow COW principles.
6606 values = values.dup;
6607 sort(values);
6609 size_t values_length = values.length;
6610 size_t lasti = values_length;
6611 size_t nexti;
6613 string nv;
6614 string lv;
6616 for (size_t i = 0; i < values_length; i = nexti)
6618 string value = values[i];
6620 // Skip dups
6621 for (nexti = i + 1; nexti < values_length; nexti++)
6623 nv = values[nexti];
6624 if (value != values[nexti])
6625 break;
6628 import std.utf : stride;
6630 for (size_t j = 0; j < value.length; j += stride(value, j))
6632 string v = value[0 .. j];
6634 if ((nexti == values_length || j > nv.length || v != nv[0 .. j]) &&
6635 (lasti == values_length || j > lv.length || v != lv[0 .. j]))
6637 result[v] = value;
6640 result[value] = value;
6641 lasti = i;
6642 lv = value;
6645 return result;
6649 @safe unittest
6651 import std.string;
6653 static string[] list = [ "food", "foxy" ];
6654 auto abbrevs = abbrev(list);
6655 assert(abbrevs == ["fox": "foxy", "food": "food",
6656 "foxy": "foxy", "foo": "food"]);
6660 @system pure unittest
6662 import std.algorithm.sorting : sort;
6663 import std.conv : to;
6664 import std.exception : assertCTFEable;
6666 assertCTFEable!(
6668 string[] values;
6669 values ~= "hello";
6670 values ~= "hello";
6671 values ~= "he";
6673 string[string] r;
6675 r = abbrev(values);
6676 auto keys = r.keys.dup;
6677 sort(keys);
6679 assert(keys.length == 4);
6680 assert(keys[0] == "he");
6681 assert(keys[1] == "hel");
6682 assert(keys[2] == "hell");
6683 assert(keys[3] == "hello");
6685 assert(r[keys[0]] == "he");
6686 assert(r[keys[1]] == "hello");
6687 assert(r[keys[2]] == "hello");
6688 assert(r[keys[3]] == "hello");
6693 /******************************************
6694 * Compute _column number at the end of the printed form of the string,
6695 * assuming the string starts in the leftmost _column, which is numbered
6696 * starting from 0.
6698 * Tab characters are expanded into enough spaces to bring the _column number
6699 * to the next multiple of tabsize.
6700 * If there are multiple lines in the string, the _column number of the last
6701 * line is returned.
6703 * Params:
6704 * str = string or InputRange to be analyzed
6705 * tabsize = number of columns a tab character represents
6707 * Returns:
6708 * column number
6711 size_t column(Range)(Range str, in size_t tabsize = 8)
6712 if ((isInputRange!Range && isSomeChar!(ElementEncodingType!Range) ||
6713 isNarrowString!Range) &&
6714 !isConvertibleToString!Range)
6716 static if (is(immutable ElementEncodingType!Range == immutable char))
6718 // decoding needed for chars
6719 import std.utf : byDchar;
6721 return str.byDchar.column(tabsize);
6723 else
6725 // decoding not needed for wchars and dchars
6726 import std.uni : lineSep, paraSep, nelSep;
6728 size_t column;
6730 foreach (const c; str)
6732 switch (c)
6734 case '\t':
6735 column = (column + tabsize) / tabsize * tabsize;
6736 break;
6738 case '\r':
6739 case '\n':
6740 case paraSep:
6741 case lineSep:
6742 case nelSep:
6743 column = 0;
6744 break;
6746 default:
6747 column++;
6748 break;
6751 return column;
6756 @safe pure unittest
6758 import std.utf : byChar, byWchar, byDchar;
6760 assert(column("1234 ") == 5);
6761 assert(column("1234 "w) == 5);
6762 assert(column("1234 "d) == 5);
6764 assert(column("1234 ".byChar()) == 5);
6765 assert(column("1234 "w.byWchar()) == 5);
6766 assert(column("1234 "d.byDchar()) == 5);
6768 // Tab stops are set at 8 spaces by default; tab characters insert enough
6769 // spaces to bring the column position to the next multiple of 8.
6770 assert(column("\t") == 8);
6771 assert(column("1\t") == 8);
6772 assert(column("\t1") == 9);
6773 assert(column("123\t") == 8);
6775 // Other tab widths are possible by specifying it explicitly:
6776 assert(column("\t", 4) == 4);
6777 assert(column("1\t", 4) == 4);
6778 assert(column("\t1", 4) == 5);
6779 assert(column("123\t", 4) == 4);
6781 // New lines reset the column number.
6782 assert(column("abc\n") == 0);
6783 assert(column("abc\n1") == 1);
6784 assert(column("abcdefg\r1234") == 4);
6785 assert(column("abc\u20281") == 1);
6786 assert(column("abc\u20291") == 1);
6787 assert(column("abc\u00851") == 1);
6788 assert(column("abc\u00861") == 5);
6791 size_t column(Range)(auto ref Range str, in size_t tabsize = 8)
6792 if (isConvertibleToString!Range)
6794 return column!(StringTypeOf!Range)(str, tabsize);
6797 @safe pure unittest
6799 assert(testAliasedString!column("abc\u00861"));
6802 @safe @nogc unittest
6804 import std.conv : to;
6805 import std.exception : assertCTFEable;
6807 assertCTFEable!(
6809 assert(column(string.init) == 0);
6810 assert(column("") == 0);
6811 assert(column("\t") == 8);
6812 assert(column("abc\t") == 8);
6813 assert(column("12345678\t") == 16);
6817 /******************************************
6818 * Wrap text into a paragraph.
6820 * The input text string s is formed into a paragraph
6821 * by breaking it up into a sequence of lines, delineated
6822 * by \n, such that the number of columns is not exceeded
6823 * on each line.
6824 * The last line is terminated with a \n.
6825 * Params:
6826 * s = text string to be wrapped
6827 * columns = maximum number of _columns in the paragraph
6828 * firstindent = string used to _indent first line of the paragraph
6829 * indent = string to use to _indent following lines of the paragraph
6830 * tabsize = column spacing of tabs in firstindent[] and indent[]
6831 * Returns:
6832 * resulting paragraph as an allocated string
6835 S wrap(S)(S s, in size_t columns = 80, S firstindent = null,
6836 S indent = null, in size_t tabsize = 8)
6837 if (isSomeString!S)
6839 import std.uni : isWhite;
6840 typeof(s.dup) result;
6841 bool inword;
6842 bool first = true;
6843 size_t wordstart;
6845 const indentcol = column(indent, tabsize);
6847 result.length = firstindent.length + s.length;
6848 result.length = firstindent.length;
6849 result[] = firstindent[];
6850 auto col = column(firstindent, tabsize);
6851 foreach (size_t i, dchar c; s)
6853 if (isWhite(c))
6855 if (inword)
6857 if (first)
6860 else if (col + 1 + (i - wordstart) > columns)
6862 result ~= '\n';
6863 result ~= indent;
6864 col = indentcol;
6866 else
6868 result ~= ' ';
6869 col += 1;
6871 result ~= s[wordstart .. i];
6872 col += i - wordstart;
6873 inword = false;
6874 first = false;
6877 else
6879 if (!inword)
6881 wordstart = i;
6882 inword = true;
6887 if (inword)
6889 if (col + 1 + (s.length - wordstart) > columns)
6891 result ~= '\n';
6892 result ~= indent;
6894 else if (result.length != firstindent.length)
6895 result ~= ' ';
6896 result ~= s[wordstart .. s.length];
6898 result ~= '\n';
6900 return result;
6904 @safe pure unittest
6906 assert(wrap("a short string", 7) == "a short\nstring\n");
6908 // wrap will not break inside of a word, but at the next space
6909 assert(wrap("a short string", 4) == "a\nshort\nstring\n");
6911 assert(wrap("a short string", 7, "\t") == "\ta\nshort\nstring\n");
6912 assert(wrap("a short string", 7, "\t", " ") == "\ta\n short\n string\n");
6915 @safe pure unittest
6917 import std.conv : to;
6918 import std.exception : assertCTFEable;
6920 assertCTFEable!(
6922 assert(wrap(string.init) == "\n");
6923 assert(wrap(" a b df ") == "a b df\n");
6924 assert(wrap(" a b df ", 3) == "a b\ndf\n");
6925 assert(wrap(" a bc df ", 3) == "a\nbc\ndf\n");
6926 assert(wrap(" abcd df ", 3) == "abcd\ndf\n");
6927 assert(wrap("x") == "x\n");
6928 assert(wrap("u u") == "u u\n");
6929 assert(wrap("abcd", 3) == "\nabcd\n");
6930 assert(wrap("a de", 10, "\t", " ", 8) == "\ta\n de\n");
6934 @safe pure unittest // https://issues.dlang.org/show_bug.cgi?id=23298
6936 assert("1 2 3 4 5 6 7 8 9".wrap(17) == "1 2 3 4 5 6 7 8 9\n");
6937 assert("1 2 3 4 5 6 7 8 9 ".wrap(17) == "1 2 3 4 5 6 7 8 9\n");
6938 assert("1 2 3 4 5 6 7 8 99".wrap(17) == "1 2 3 4 5 6 7 8\n99\n");
6941 /******************************************
6942 * Removes one level of indentation from a multi-line string.
6944 * This uniformly outdents the text as much as possible.
6945 * Whitespace-only lines are always converted to blank lines.
6947 * Does not allocate memory if it does not throw.
6949 * Params:
6950 * str = multi-line string
6952 * Returns:
6953 * outdented string
6955 * Throws:
6956 * StringException if indentation is done with different sequences
6957 * of whitespace characters.
6959 S outdent(S)(S str) @safe pure
6960 if (isSomeString!S)
6962 return str.splitLines(Yes.keepTerminator).outdent().join();
6966 @safe pure unittest
6968 enum pretty = q{
6969 import std.stdio;
6970 void main() {
6971 writeln("Hello");
6973 }.outdent();
6975 enum ugly = q{
6976 import std.stdio;
6977 void main() {
6978 writeln("Hello");
6982 assert(pretty == ugly);
6986 /******************************************
6987 * Removes one level of indentation from an array of single-line strings.
6989 * This uniformly outdents the text as much as possible.
6990 * Whitespace-only lines are always converted to blank lines.
6992 * Params:
6993 * lines = array of single-line strings
6995 * Returns:
6996 * lines[] is rewritten in place with outdented lines
6998 * Throws:
6999 * StringException if indentation is done with different sequences
7000 * of whitespace characters.
7002 S[] outdent(S)(return scope S[] lines) @safe pure
7003 if (isSomeString!S)
7005 import std.algorithm.searching : startsWith;
7007 if (lines.empty)
7009 return null;
7012 static S leadingWhiteOf(S str)
7014 return str[ 0 .. $ - stripLeft(str).length ];
7017 S shortestIndent;
7018 foreach (ref line; lines)
7020 const stripped = line.stripLeft();
7022 if (stripped.empty)
7024 line = line[line.chomp().length .. $];
7026 else
7028 const indent = leadingWhiteOf(line);
7030 // Comparing number of code units instead of code points is OK here
7031 // because this function throws upon inconsistent indentation.
7032 if (shortestIndent is null || indent.length < shortestIndent.length)
7034 if (indent.empty)
7035 return lines;
7036 shortestIndent = indent;
7041 foreach (ref line; lines)
7043 const stripped = line.stripLeft();
7045 if (stripped.empty)
7047 // Do nothing
7049 else if (line.startsWith(shortestIndent))
7051 line = line[shortestIndent.length .. $];
7053 else
7055 throw new StringException("outdent: Inconsistent indentation");
7059 return lines;
7063 @safe pure unittest
7065 auto str1 = [
7066 " void main()\n",
7067 " {\n",
7068 " test();\n",
7069 " }\n"
7071 auto str1Expected = [
7072 "void main()\n",
7073 "{\n",
7074 " test();\n",
7075 "}\n"
7077 assert(str1.outdent == str1Expected);
7079 auto str2 = [
7080 "void main()\n",
7081 " {\n",
7082 " test();\n",
7083 " }\n"
7085 assert(str2.outdent == str2);
7088 @safe pure unittest
7090 import std.conv : to;
7091 import std.exception : assertCTFEable;
7093 template outdent_testStr(S)
7095 enum S outdent_testStr =
7097 \t\tX
7098 \t\U00010143X
7099 \t\t
7101 \t\t\tX
7102 \t ";
7105 template outdent_expected(S)
7107 enum S outdent_expected =
7110 \U00010143X
7113 \t\tX
7117 assertCTFEable!(
7120 static foreach (S; AliasSeq!(string, wstring, dstring))
7122 enum S blank = "";
7123 assert(blank.outdent() == blank);
7124 static assert(blank.outdent() == blank);
7126 enum S testStr1 = " \n \t\n ";
7127 enum S expected1 = "\n\n";
7128 assert(testStr1.outdent() == expected1);
7129 static assert(testStr1.outdent() == expected1);
7131 assert(testStr1[0..$-1].outdent() == expected1);
7132 static assert(testStr1[0..$-1].outdent() == expected1);
7134 enum S testStr2 = "a\n \t\nb";
7135 assert(testStr2.outdent() == testStr2);
7136 static assert(testStr2.outdent() == testStr2);
7138 enum S testStr3 =
7140 \t\tX
7141 \t\U00010143X
7142 \t\t
7144 \t\t\tX
7145 \t ";
7147 enum S expected3 =
7150 \U00010143X
7153 \t\tX
7155 assert(testStr3.outdent() == expected3);
7156 static assert(testStr3.outdent() == expected3);
7158 enum testStr4 = " X\r X\n X\r\n X\u2028 X\u2029 X";
7159 enum expected4 = "X\rX\nX\r\nX\u2028X\u2029X";
7160 assert(testStr4.outdent() == expected4);
7161 static assert(testStr4.outdent() == expected4);
7163 enum testStr5 = testStr4[0..$-1];
7164 enum expected5 = expected4[0..$-1];
7165 assert(testStr5.outdent() == expected5);
7166 static assert(testStr5.outdent() == expected5);
7168 enum testStr6 = " \r \n \r\n \u2028 \u2029";
7169 enum expected6 = "\r\n\r\n\u2028\u2029";
7170 assert(testStr6.outdent() == expected6);
7171 static assert(testStr6.outdent() == expected6);
7173 enum testStr7 = " a \n b ";
7174 enum expected7 = "a \nb ";
7175 assert(testStr7.outdent() == expected7);
7176 static assert(testStr7.outdent() == expected7);
7181 @safe pure unittest
7183 import std.exception : assertThrown;
7184 auto bad = " a\n\tb\n c";
7185 assertThrown!StringException(bad.outdent);
7188 /** Assume the given array of integers `arr` is a well-formed UTF string and
7189 return it typed as a UTF string.
7191 `ubyte` becomes `char`, `ushort` becomes `wchar` and `uint`
7192 becomes `dchar`. Type qualifiers are preserved.
7194 When compiled with debug mode, this function performs an extra check to make
7195 sure the return value is a valid Unicode string.
7197 Params:
7198 arr = array of bytes, ubytes, shorts, ushorts, ints, or uints
7200 Returns:
7201 arr retyped as an array of chars, wchars, or dchars
7203 Throws:
7204 In debug mode `AssertError`, when the result is not a well-formed UTF string.
7206 See_Also: $(LREF representation)
7208 auto assumeUTF(T)(T[] arr)
7209 if (staticIndexOf!(immutable T, immutable ubyte, immutable ushort, immutable uint) != -1)
7211 import std.traits : ModifyTypePreservingTQ;
7212 import std.exception : collectException;
7213 import std.utf : validate;
7215 alias ToUTFType(U) = AliasSeq!(char, wchar, dchar)[U.sizeof / 2];
7216 auto asUTF = cast(ModifyTypePreservingTQ!(ToUTFType, T)[]) arr;
7218 debug
7220 scope ex = collectException(validate(asUTF));
7221 assert(!ex, ex.msg);
7224 return asUTF;
7228 @safe pure unittest
7230 string a = "Hölo World";
7231 immutable(ubyte)[] b = a.representation;
7232 string c = b.assumeUTF;
7234 assert(c == "Hölo World");
7237 pure @system unittest
7239 import std.algorithm.comparison : equal;
7240 static foreach (T; AliasSeq!(char[], wchar[], dchar[]))
7242 immutable T jti = "Hello World";
7243 T jt = jti.dup;
7245 static if (is(T == char[]))
7247 auto gt = cast(ubyte[]) jt;
7248 auto gtc = cast(const(ubyte)[])jt;
7249 auto gti = cast(immutable(ubyte)[])jt;
7251 else static if (is(T == wchar[]))
7253 auto gt = cast(ushort[]) jt;
7254 auto gtc = cast(const(ushort)[])jt;
7255 auto gti = cast(immutable(ushort)[])jt;
7257 else static if (is(T == dchar[]))
7259 auto gt = cast(uint[]) jt;
7260 auto gtc = cast(const(uint)[])jt;
7261 auto gti = cast(immutable(uint)[])jt;
7264 auto ht = assumeUTF(gt);
7265 auto htc = assumeUTF(gtc);
7266 auto hti = assumeUTF(gti);
7267 assert(equal(jt, ht));
7268 assert(equal(jt, htc));
7269 assert(equal(jt, hti));
7273 pure @system unittest
7275 import core.exception : AssertError;
7276 import std.exception : assertThrown, assertNotThrown;
7278 immutable(ubyte)[] a = [ 0xC0 ];
7280 debug
7281 assertThrown!AssertError( () nothrow @nogc @safe {cast(void) a.assumeUTF;} () );
7282 else
7283 assertNotThrown!AssertError( () nothrow @nogc @safe {cast(void) a.assumeUTF;} () );