| // Written in the D programming language. |
| |
| /** |
| String handling functions. |
| |
| $(SCRIPT inhibitQuickIndex = 1;) |
| |
| $(DIVC quickindex, |
| $(BOOKTABLE , |
| $(TR $(TH Category) $(TH Functions) ) |
| $(TR $(TDNW Searching) |
| $(TD |
| $(MYREF column) |
| $(MYREF indexOf) |
| $(MYREF indexOfAny) |
| $(MYREF indexOfNeither) |
| $(MYREF lastIndexOf) |
| $(MYREF lastIndexOfAny) |
| $(MYREF lastIndexOfNeither) |
| ) |
| ) |
| $(TR $(TDNW Comparison) |
| $(TD |
| $(MYREF isNumeric) |
| ) |
| ) |
| $(TR $(TDNW Mutation) |
| $(TD |
| $(MYREF capitalize) |
| ) |
| ) |
| $(TR $(TDNW Pruning and Filling) |
| $(TD |
| $(MYREF center) |
| $(MYREF chomp) |
| $(MYREF chompPrefix) |
| $(MYREF chop) |
| $(MYREF detabber) |
| $(MYREF detab) |
| $(MYREF entab) |
| $(MYREF entabber) |
| $(MYREF leftJustify) |
| $(MYREF outdent) |
| $(MYREF rightJustify) |
| $(MYREF strip) |
| $(MYREF stripLeft) |
| $(MYREF stripRight) |
| $(MYREF wrap) |
| ) |
| ) |
| $(TR $(TDNW Substitution) |
| $(TD |
| $(MYREF abbrev) |
| $(MYREF soundex) |
| $(MYREF soundexer) |
| $(MYREF succ) |
| $(MYREF tr) |
| $(MYREF translate) |
| ) |
| ) |
| $(TR $(TDNW Miscellaneous) |
| $(TD |
| $(MYREF assumeUTF) |
| $(MYREF fromStringz) |
| $(MYREF lineSplitter) |
| $(MYREF representation) |
| $(MYREF splitLines) |
| $(MYREF toStringz) |
| ) |
| ))) |
| |
| Objects of types $(D _string), $(D wstring), and $(D dstring) are value types |
| and cannot be mutated element-by-element. For using mutation during building |
| strings, use $(D char[]), $(D wchar[]), or $(D dchar[]). The $(D xxxstring) |
| types are preferable because they don't exhibit undesired aliasing, thus |
| making code more robust. |
| |
| The following functions are publicly imported: |
| |
| $(BOOKTABLE , |
| $(TR $(TH Module) $(TH Functions) ) |
| $(LEADINGROW Publicly imported functions) |
| $(TR $(TD std.algorithm) |
| $(TD |
| $(REF_SHORT cmp, std,algorithm,comparison) |
| $(REF_SHORT count, std,algorithm,searching) |
| $(REF_SHORT endsWith, std,algorithm,searching) |
| $(REF_SHORT startsWith, std,algorithm,searching) |
| )) |
| $(TR $(TD std.array) |
| $(TD |
| $(REF_SHORT join, std,array) |
| $(REF_SHORT replace, std,array) |
| $(REF_SHORT replaceInPlace, std,array) |
| $(REF_SHORT split, std,array) |
| $(REF_SHORT empty, std,array) |
| )) |
| $(TR $(TD std.format) |
| $(TD |
| $(REF_SHORT format, std,format) |
| $(REF_SHORT sformat, std,format) |
| )) |
| $(TR $(TD std.uni) |
| $(TD |
| $(REF_SHORT icmp, std,uni) |
| $(REF_SHORT toLower, std,uni) |
| $(REF_SHORT toLowerInPlace, std,uni) |
| $(REF_SHORT toUpper, std,uni) |
| $(REF_SHORT toUpperInPlace, std,uni) |
| )) |
| ) |
| |
| There is a rich set of functions for _string handling defined in other modules. |
| Functions related to Unicode and ASCII are found in $(MREF std, uni) |
| and $(MREF std, ascii), respectively. Other functions that have a |
| wider generality than just strings can be found in $(MREF std, algorithm) |
| and $(MREF std, range). |
| |
| See_Also: |
| $(LIST |
| $(MREF std, algorithm) and |
| $(MREF std, range) |
| for generic range algorithms |
| , |
| $(MREF std, ascii) |
| for functions that work with ASCII strings |
| , |
| $(MREF std, uni) |
| for functions that work with unicode strings |
| ) |
| |
| Copyright: Copyright Digital Mars 2007-. |
| |
| License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). |
| |
| Authors: $(HTTP digitalmars.com, Walter Bright), |
| $(HTTP erdani.org, Andrei Alexandrescu), |
| Jonathan M Davis, |
| and David L. 'SpottedTiger' Davis |
| |
| Source: $(PHOBOSSRC std/_string.d) |
| |
| */ |
| module std.string; |
| |
| version (unittest) |
| { |
| private: |
| struct TestAliasedString |
| { |
| string get() @safe @nogc pure nothrow { return _s; } |
| alias get this; |
| @disable this(this); |
| string _s; |
| } |
| |
| bool testAliasedString(alias func, Args...)(string s, Args args) |
| { |
| import std.algorithm.comparison : equal; |
| auto a = func(TestAliasedString(s), args); |
| auto b = func(s, args); |
| static if (is(typeof(equal(a, b)))) |
| { |
| // For ranges, compare contents instead of object identity. |
| return equal(a, b); |
| } |
| else |
| { |
| return a == b; |
| } |
| } |
| } |
| |
| public import std.format : format, sformat; |
| import std.typecons : Flag, Yes, No; |
| public import std.uni : icmp, toLower, toLowerInPlace, toUpper, toUpperInPlace; |
| |
| import std.meta; // AliasSeq, staticIndexOf |
| import std.range.primitives; // back, ElementEncodingType, ElementType, front, |
| // hasLength, hasSlicing, isBidirectionalRange, isForwardRange, isInfinite, |
| // isInputRange, isOutputRange, isRandomAccessRange, popBack, popFront, put, |
| // save; |
| import std.traits; // isConvertibleToString, isNarrowString, isSomeChar, |
| // isSomeString, StringTypeOf, Unqual |
| |
| //public imports for backward compatibility |
| public import std.algorithm.comparison : cmp; |
| public import std.algorithm.searching : startsWith, endsWith, count; |
| public import std.array : join, replace, replaceInPlace, split, empty; |
| |
| /* ************* Exceptions *************** */ |
| |
| /++ |
| Exception thrown on errors in std.string functions. |
| +/ |
| class StringException : Exception |
| { |
| import std.exception : basicExceptionCtors; |
| |
| /// |
| mixin basicExceptionCtors; |
| } |
| |
| |
| /++ |
| Params: |
| cString = A null-terminated c-style string. |
| |
| Returns: A D-style array of $(D char) referencing the same string. The |
| returned array will retain the same type qualifiers as the input. |
| |
| $(RED Important Note:) The returned array is a slice of the original buffer. |
| The original data is not changed and not copied. |
| +/ |
| |
| inout(char)[] fromStringz(inout(char)* cString) @nogc @system pure nothrow { |
| import core.stdc.string : strlen; |
| return cString ? cString[0 .. strlen(cString)] : null; |
| } |
| |
| /// |
| @system pure unittest |
| { |
| assert(fromStringz(null) == null); |
| assert(fromStringz("foo") == "foo"); |
| } |
| |
| /++ |
| Params: |
| s = A D-style string. |
| |
| Returns: A C-style null-terminated string equivalent to $(D s). $(D s) |
| must not contain embedded $(D '\0')'s as any C function will treat the |
| first $(D '\0') that it sees as the end of the string. If $(D s.empty) is |
| $(D true), then a string containing only $(D '\0') is returned. |
| |
| $(RED Important Note:) When passing a $(D char*) to a C function, and the C |
| function keeps it around for any reason, make sure that you keep a |
| reference to it in your D code. Otherwise, it may become invalid during a |
| garbage collection cycle and cause a nasty bug when the C code tries to use |
| it. |
| +/ |
| immutable(char)* toStringz(const(char)[] s) @trusted pure nothrow |
| out (result) |
| { |
| import core.stdc.string : strlen, memcmp; |
| if (result) |
| { |
| auto slen = s.length; |
| while (slen > 0 && s[slen-1] == 0) --slen; |
| assert(strlen(result) == slen); |
| assert(result[0 .. slen] == s[0 .. slen]); |
| } |
| } |
| body |
| { |
| import std.exception : assumeUnique; |
| /+ Unfortunately, this isn't reliable. |
| We could make this work if string literals are put |
| in read-only memory and we test if s[] is pointing into |
| that. |
| |
| /* Peek past end of s[], if it's 0, no conversion necessary. |
| * Note that the compiler will put a 0 past the end of static |
| * strings, and the storage allocator will put a 0 past the end |
| * of newly allocated char[]'s. |
| */ |
| char* p = &s[0] + s.length; |
| if (*p == 0) |
| return s; |
| +/ |
| |
| // Need to make a copy |
| auto copy = new char[s.length + 1]; |
| copy[0 .. s.length] = s[]; |
| copy[s.length] = 0; |
| |
| return &assumeUnique(copy)[0]; |
| } |
| |
| /++ Ditto +/ |
| immutable(char)* toStringz(in string s) @trusted pure nothrow |
| { |
| if (s.empty) return "".ptr; |
| /* Peek past end of s[], if it's 0, no conversion necessary. |
| * Note that the compiler will put a 0 past the end of static |
| * strings, and the storage allocator will put a 0 past the end |
| * of newly allocated char[]'s. |
| */ |
| immutable p = s.ptr + s.length; |
| // Is p dereferenceable? A simple test: if the p points to an |
| // address multiple of 4, then conservatively assume the pointer |
| // might be pointing to a new block of memory, which might be |
| // unreadable. Otherwise, it's definitely pointing to valid |
| // memory. |
| if ((cast(size_t) p & 3) && *p == 0) |
| return &s[0]; |
| return toStringz(cast(const char[]) s); |
| } |
| |
| /// |
| pure nothrow @system unittest |
| { |
| import core.stdc.string : strlen; |
| import std.conv : to; |
| |
| auto p = toStringz("foo"); |
| assert(strlen(p) == 3); |
| const(char)[] foo = "abbzxyzzy"; |
| p = toStringz(foo[3 .. 5]); |
| assert(strlen(p) == 2); |
| |
| string test = ""; |
| p = toStringz(test); |
| assert(*p == 0); |
| |
| test = "\0"; |
| p = toStringz(test); |
| assert(*p == 0); |
| |
| test = "foo\0"; |
| p = toStringz(test); |
| assert(p[0] == 'f' && p[1] == 'o' && p[2] == 'o' && p[3] == 0); |
| |
| const string test2 = ""; |
| p = toStringz(test2); |
| assert(*p == 0); |
| } |
| |
| |
| /** |
| Flag indicating whether a search is case-sensitive. |
| */ |
| alias CaseSensitive = Flag!"caseSensitive"; |
| |
| /++ |
| Searches for character in range. |
| |
| Params: |
| s = string or InputRange of characters to search in correct UTF format |
| c = character to search for |
| startIdx = starting index to a well-formed code point |
| cs = $(D Yes.caseSensitive) or $(D No.caseSensitive) |
| |
| Returns: |
| the index of the first occurrence of $(D c) in $(D s) with |
| respect to the start index $(D startIdx). If $(D c) |
| is not found, then $(D -1) is returned. |
| If $(D c) is found the value of the returned index is at least |
| $(D startIdx). |
| If the parameters are not valid UTF, the result will still |
| be in the range [-1 .. s.length], but will not be reliable otherwise. |
| |
| Throws: |
| If the sequence starting at $(D startIdx) does not represent a well |
| formed codepoint, then a $(REF UTFException, std,utf) may be thrown. |
| |
| See_Also: $(REF countUntil, std,algorithm,searching) |
| +/ |
| ptrdiff_t indexOf(Range)(Range s, in dchar c, |
| in CaseSensitive cs = Yes.caseSensitive) |
| if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) && |
| !isConvertibleToString!Range) |
| { |
| static import std.ascii; |
| static import std.uni; |
| import std.utf : byDchar, byCodeUnit, UTFException, codeLength; |
| alias Char = Unqual!(ElementEncodingType!Range); |
| |
| if (cs == Yes.caseSensitive) |
| { |
| static if (Char.sizeof == 1 && isSomeString!Range) |
| { |
| if (std.ascii.isASCII(c) && !__ctfe) |
| { // Plain old ASCII |
| static ptrdiff_t trustedmemchr(Range s, char c) @trusted |
| { |
| import core.stdc.string : memchr; |
| const p = cast(const(Char)*)memchr(s.ptr, c, s.length); |
| return p ? p - s.ptr : -1; |
| } |
| |
| return trustedmemchr(s, cast(char) c); |
| } |
| } |
| |
| static if (Char.sizeof == 1) |
| { |
| if (c <= 0x7F) |
| { |
| ptrdiff_t i; |
| foreach (const c2; s) |
| { |
| if (c == c2) |
| return i; |
| ++i; |
| } |
| } |
| else |
| { |
| ptrdiff_t i; |
| foreach (const c2; s.byDchar()) |
| { |
| if (c == c2) |
| return i; |
| i += codeLength!Char(c2); |
| } |
| } |
| } |
| else static if (Char.sizeof == 2) |
| { |
| if (c <= 0xFFFF) |
| { |
| ptrdiff_t i; |
| foreach (const c2; s) |
| { |
| if (c == c2) |
| return i; |
| ++i; |
| } |
| } |
| else if (c <= 0x10FFFF) |
| { |
| // Encode UTF-16 surrogate pair |
| const wchar c1 = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); |
| const wchar c2 = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00); |
| ptrdiff_t i; |
| for (auto r = s.byCodeUnit(); !r.empty; r.popFront()) |
| { |
| if (c1 == r.front) |
| { |
| r.popFront(); |
| if (r.empty) // invalid UTF - missing second of pair |
| break; |
| if (c2 == r.front) |
| return i; |
| ++i; |
| } |
| ++i; |
| } |
| } |
| } |
| else static if (Char.sizeof == 4) |
| { |
| ptrdiff_t i; |
| foreach (const c2; s) |
| { |
| if (c == c2) |
| return i; |
| ++i; |
| } |
| } |
| else |
| static assert(0); |
| return -1; |
| } |
| else |
| { |
| if (std.ascii.isASCII(c)) |
| { // Plain old ASCII |
| immutable c1 = cast(char) std.ascii.toLower(c); |
| |
| ptrdiff_t i; |
| foreach (const c2; s.byCodeUnit()) |
| { |
| if (c1 == std.ascii.toLower(c2)) |
| return i; |
| ++i; |
| } |
| } |
| else |
| { // c is a universal character |
| immutable c1 = std.uni.toLower(c); |
| |
| ptrdiff_t i; |
| foreach (const c2; s.byDchar()) |
| { |
| if (c1 == std.uni.toLower(c2)) |
| return i; |
| i += codeLength!Char(c2); |
| } |
| } |
| } |
| return -1; |
| } |
| |
| /// Ditto |
| ptrdiff_t indexOf(Range)(Range s, in dchar c, in size_t startIdx, |
| in CaseSensitive cs = Yes.caseSensitive) |
| if (isInputRange!Range && isSomeChar!(ElementEncodingType!Range) && |
| !isConvertibleToString!Range) |
| { |
| static if (isSomeString!(typeof(s)) || |
| (hasSlicing!(typeof(s)) && hasLength!(typeof(s)))) |
| { |
| if (startIdx < s.length) |
| { |
| ptrdiff_t foundIdx = indexOf(s[startIdx .. $], c, cs); |
| if (foundIdx != -1) |
| { |
| return foundIdx + cast(ptrdiff_t) startIdx; |
| } |
| } |
| } |
| else |
| { |
| foreach (i; 0 .. startIdx) |
| { |
| if (s.empty) |
| return -1; |
| s.popFront(); |
| } |
| ptrdiff_t foundIdx = indexOf(s, c, cs); |
| if (foundIdx != -1) |
| { |
| return foundIdx + cast(ptrdiff_t) startIdx; |
| } |
| } |
| return -1; |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| import std.typecons : No; |
| |
| string s = "Hello World"; |
| assert(indexOf(s, 'W') == 6); |
| assert(indexOf(s, 'Z') == -1); |
| assert(indexOf(s, 'w', No.caseSensitive) == 6); |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| import std.typecons : No; |
| |
| string s = "Hello World"; |
| assert(indexOf(s, 'W', 4) == 6); |
| assert(indexOf(s, 'Z', 100) == -1); |
| assert(indexOf(s, 'w', 3, No.caseSensitive) == 6); |
| } |
| |
| ptrdiff_t indexOf(Range)(auto ref Range s, in dchar c, |
| in CaseSensitive cs = Yes.caseSensitive) |
| if (isConvertibleToString!Range) |
| { |
| return indexOf!(StringTypeOf!Range)(s, c, cs); |
| } |
| |
| ptrdiff_t indexOf(Range)(auto ref Range s, in dchar c, in size_t startIdx, |
| in CaseSensitive cs = Yes.caseSensitive) |
| if (isConvertibleToString!Range) |
| { |
| return indexOf!(StringTypeOf!Range)(s, c, startIdx, cs); |
| } |
| |
| @safe pure unittest |
| { |
| assert(testAliasedString!indexOf("std/string.d", '/')); |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| import std.exception : assertCTFEable; |
| import std.traits : EnumMembers; |
| import std.utf : byChar, byWchar, byDchar; |
| |
| assertCTFEable!( |
| { |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| assert(indexOf(cast(S) null, cast(dchar)'a') == -1); |
| assert(indexOf(to!S("def"), cast(dchar)'a') == -1); |
| assert(indexOf(to!S("abba"), cast(dchar)'a') == 0); |
| assert(indexOf(to!S("def"), cast(dchar)'f') == 2); |
| |
| assert(indexOf(to!S("def"), cast(dchar)'a', No.caseSensitive) == -1); |
| assert(indexOf(to!S("def"), cast(dchar)'a', No.caseSensitive) == -1); |
| assert(indexOf(to!S("Abba"), cast(dchar)'a', No.caseSensitive) == 0); |
| assert(indexOf(to!S("def"), cast(dchar)'F', No.caseSensitive) == 2); |
| assert(indexOf(to!S("ödef"), 'ö', No.caseSensitive) == 0); |
| |
| S sPlts = "Mars: the fourth Rock (Planet) from the Sun."; |
| assert(indexOf("def", cast(char)'f', No.caseSensitive) == 2); |
| assert(indexOf(sPlts, cast(char)'P', No.caseSensitive) == 23); |
| assert(indexOf(sPlts, cast(char)'R', No.caseSensitive) == 2); |
| } |
| |
| foreach (cs; EnumMembers!CaseSensitive) |
| { |
| assert(indexOf("hello\U00010143\u0100\U00010143", '\u0100', cs) == 9); |
| assert(indexOf("hello\U00010143\u0100\U00010143"w, '\u0100', cs) == 7); |
| assert(indexOf("hello\U00010143\u0100\U00010143"d, '\u0100', cs) == 6); |
| |
| assert(indexOf("hello\U00010143\u0100\U00010143".byChar, '\u0100', cs) == 9); |
| assert(indexOf("hello\U00010143\u0100\U00010143".byWchar, '\u0100', cs) == 7); |
| assert(indexOf("hello\U00010143\u0100\U00010143".byDchar, '\u0100', cs) == 6); |
| |
| assert(indexOf("hello\U000007FF\u0100\U00010143".byChar, 'l', cs) == 2); |
| assert(indexOf("hello\U000007FF\u0100\U00010143".byChar, '\u0100', cs) == 7); |
| assert(indexOf("hello\U0000EFFF\u0100\U00010143".byChar, '\u0100', cs) == 8); |
| |
| assert(indexOf("hello\U00010100".byWchar, '\U00010100', cs) == 5); |
| assert(indexOf("hello\U00010100".byWchar, '\U00010101', cs) == -1); |
| } |
| |
| char[10] fixedSizeArray = "0123456789"; |
| assert(indexOf(fixedSizeArray, '2') == 2); |
| }); |
| } |
| |
| @safe pure unittest |
| { |
| assert(testAliasedString!indexOf("std/string.d", '/', 3)); |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| import std.traits : EnumMembers; |
| import std.utf : byCodeUnit, byChar, byWchar; |
| |
| assert("hello".byCodeUnit.indexOf(cast(dchar)'l', 1) == 2); |
| assert("hello".byWchar.indexOf(cast(dchar)'l', 1) == 2); |
| assert("hello".byWchar.indexOf(cast(dchar)'l', 6) == -1); |
| |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| assert(indexOf(cast(S) null, cast(dchar)'a', 1) == -1); |
| assert(indexOf(to!S("def"), cast(dchar)'a', 1) == -1); |
| assert(indexOf(to!S("abba"), cast(dchar)'a', 1) == 3); |
| assert(indexOf(to!S("def"), cast(dchar)'f', 1) == 2); |
| |
| assert((to!S("def")).indexOf(cast(dchar)'a', 1, |
| No.caseSensitive) == -1); |
| assert(indexOf(to!S("def"), cast(dchar)'a', 1, |
| No.caseSensitive) == -1); |
| assert(indexOf(to!S("def"), cast(dchar)'a', 12, |
| No.caseSensitive) == -1); |
| assert(indexOf(to!S("AbbA"), cast(dchar)'a', 2, |
| No.caseSensitive) == 3); |
| assert(indexOf(to!S("def"), cast(dchar)'F', 2, No.caseSensitive) == 2); |
| |
| S sPlts = "Mars: the fourth Rock (Planet) from the Sun."; |
| assert(indexOf("def", cast(char)'f', cast(uint) 2, |
| No.caseSensitive) == 2); |
| assert(indexOf(sPlts, cast(char)'P', 12, No.caseSensitive) == 23); |
| assert(indexOf(sPlts, cast(char)'R', cast(ulong) 1, |
| No.caseSensitive) == 2); |
| } |
| |
| foreach (cs; EnumMembers!CaseSensitive) |
| { |
| assert(indexOf("hello\U00010143\u0100\U00010143", '\u0100', 2, cs) |
| == 9); |
| assert(indexOf("hello\U00010143\u0100\U00010143"w, '\u0100', 3, cs) |
| == 7); |
| assert(indexOf("hello\U00010143\u0100\U00010143"d, '\u0100', 6, cs) |
| == 6); |
| } |
| } |
| |
| /++ |
| Searches for substring in $(D s). |
| |
| Params: |
| s = string or ForwardRange of characters to search in correct UTF format |
| sub = substring to search for |
| startIdx = the index into s to start searching from |
| cs = $(D Yes.caseSensitive) or $(D No.caseSensitive) |
| |
| Returns: |
| the index of the first occurrence of $(D sub) in $(D s) with |
| respect to the start index $(D startIdx). If $(D sub) is not found, |
| then $(D -1) is returned. |
| If the arguments are not valid UTF, the result will still |
| be in the range [-1 .. s.length], but will not be reliable otherwise. |
| If $(D sub) is found the value of the returned index is at least |
| $(D startIdx). |
| |
| Throws: |
| If the sequence starting at $(D startIdx) does not represent a well |
| formed codepoint, then a $(REF UTFException, std,utf) may be thrown. |
| |
| Bugs: |
| Does not work with case insensitive strings where the mapping of |
| tolower and toupper is not 1:1. |
| +/ |
| ptrdiff_t indexOf(Range, Char)(Range s, const(Char)[] sub, |
| in CaseSensitive cs = Yes.caseSensitive) |
| if (isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && |
| isSomeChar!Char) |
| { |
| alias Char1 = Unqual!(ElementEncodingType!Range); |
| |
| static if (isSomeString!Range) |
| { |
| import std.algorithm.searching : find; |
| |
| const(Char1)[] balance; |
| if (cs == Yes.caseSensitive) |
| { |
| balance = find(s, sub); |
| } |
| else |
| { |
| balance = find! |
| ((a, b) => toLower(a) == toLower(b)) |
| (s, sub); |
| } |
| return () @trusted { return balance.empty ? -1 : balance.ptr - s.ptr; } (); |
| } |
| else |
| { |
| if (s.empty) |
| return -1; |
| if (sub.empty) |
| return 0; // degenerate case |
| |
| import std.utf : byDchar, codeLength; |
| auto subr = sub.byDchar; // decode sub[] by dchar's |
| dchar sub0 = subr.front; // cache first character of sub[] |
| subr.popFront(); |
| |
| // Special case for single character search |
| if (subr.empty) |
| return indexOf(s, sub0, cs); |
| |
| if (cs == No.caseSensitive) |
| sub0 = toLower(sub0); |
| |
| /* Classic double nested loop search algorithm |
| */ |
| ptrdiff_t index = 0; // count code unit index into s |
| for (auto sbydchar = s.byDchar(); !sbydchar.empty; sbydchar.popFront()) |
| { |
| dchar c2 = sbydchar.front; |
| if (cs == No.caseSensitive) |
| c2 = toLower(c2); |
| if (c2 == sub0) |
| { |
| auto s2 = sbydchar.save; // why s must be a forward range |
| foreach (c; subr.save) |
| { |
| s2.popFront(); |
| if (s2.empty) |
| return -1; |
| if (cs == Yes.caseSensitive ? c != s2.front |
| : toLower(c) != toLower(s2.front) |
| ) |
| goto Lnext; |
| } |
| return index; |
| } |
| Lnext: |
| index += codeLength!Char1(c2); |
| } |
| return -1; |
| } |
| } |
| |
| /// Ditto |
| ptrdiff_t indexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, |
| in size_t startIdx, in CaseSensitive cs = Yes.caseSensitive) |
| @safe |
| if (isSomeChar!Char1 && isSomeChar!Char2) |
| { |
| if (startIdx < s.length) |
| { |
| ptrdiff_t foundIdx = indexOf(s[startIdx .. $], sub, cs); |
| if (foundIdx != -1) |
| { |
| return foundIdx + cast(ptrdiff_t) startIdx; |
| } |
| } |
| return -1; |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| import std.typecons : No; |
| |
| string s = "Hello World"; |
| assert(indexOf(s, "Wo", 4) == 6); |
| assert(indexOf(s, "Zo", 100) == -1); |
| assert(indexOf(s, "wo", 3, No.caseSensitive) == 6); |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| import std.typecons : No; |
| |
| string s = "Hello World"; |
| assert(indexOf(s, "Wo") == 6); |
| assert(indexOf(s, "Zo") == -1); |
| assert(indexOf(s, "wO", No.caseSensitive) == 6); |
| } |
| |
| ptrdiff_t indexOf(Range, Char)(auto ref Range s, const(Char)[] sub, |
| in CaseSensitive cs = Yes.caseSensitive) |
| if (!(isForwardRange!Range && isSomeChar!(ElementEncodingType!Range) && |
| isSomeChar!Char) && |
| is(StringTypeOf!Range)) |
| { |
| return indexOf!(StringTypeOf!Range)(s, sub, cs); |
| } |
| |
| @safe pure unittest |
| { |
| assert(testAliasedString!indexOf("std/string.d", "string")); |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| import std.exception : assertCTFEable; |
| import std.traits : EnumMembers; |
| |
| assertCTFEable!( |
| { |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| foreach (T; AliasSeq!(string, wstring, dstring)) |
| (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 |
| assert(indexOf(cast(S) null, to!T("a")) == -1); |
| assert(indexOf(to!S("def"), to!T("a")) == -1); |
| assert(indexOf(to!S("abba"), to!T("a")) == 0); |
| assert(indexOf(to!S("def"), to!T("f")) == 2); |
| assert(indexOf(to!S("dfefffg"), to!T("fff")) == 3); |
| assert(indexOf(to!S("dfeffgfff"), to!T("fff")) == 6); |
| |
| assert(indexOf(to!S("dfeffgfff"), to!T("a"), No.caseSensitive) == -1); |
| assert(indexOf(to!S("def"), to!T("a"), No.caseSensitive) == -1); |
| assert(indexOf(to!S("abba"), to!T("a"), No.caseSensitive) == 0); |
| assert(indexOf(to!S("def"), to!T("f"), No.caseSensitive) == 2); |
| assert(indexOf(to!S("dfefffg"), to!T("fff"), No.caseSensitive) == 3); |
| assert(indexOf(to!S("dfeffgfff"), to!T("fff"), No.caseSensitive) == 6); |
| |
| S sPlts = "Mars: the fourth Rock (Planet) from the Sun."; |
| S sMars = "Who\'s \'My Favorite Maritian?\'"; |
| |
| assert(indexOf(sMars, to!T("MY fAVe"), No.caseSensitive) == -1); |
| assert(indexOf(sMars, to!T("mY fAVOriTe"), No.caseSensitive) == 7); |
| assert(indexOf(sPlts, to!T("mArS:"), No.caseSensitive) == 0); |
| assert(indexOf(sPlts, to!T("rOcK"), No.caseSensitive) == 17); |
| assert(indexOf(sPlts, to!T("Un."), No.caseSensitive) == 41); |
| assert(indexOf(sPlts, to!T(sPlts), No.caseSensitive) == 0); |
| |
| assert(indexOf("\u0100", to!T("\u0100"), No.caseSensitive) == 0); |
| |
| // Thanks to Carlos Santander B. and zwang |
| assert(indexOf("sus mejores cortesanos. Se embarcaron en el puerto de Dubai y", |
| to!T("page-break-before"), No.caseSensitive) == -1); |
| }(); |
| |
| foreach (cs; EnumMembers!CaseSensitive) |
| { |
| assert(indexOf("hello\U00010143\u0100\U00010143", to!S("\u0100"), cs) == 9); |
| assert(indexOf("hello\U00010143\u0100\U00010143"w, to!S("\u0100"), cs) == 7); |
| assert(indexOf("hello\U00010143\u0100\U00010143"d, to!S("\u0100"), cs) == 6); |
| } |
| } |
| }); |
| } |
| |
| @safe pure @nogc nothrow |
| unittest |
| { |
| import std.traits : EnumMembers; |
| import std.utf : byWchar; |
| |
| foreach (cs; EnumMembers!CaseSensitive) |
| { |
| assert(indexOf("".byWchar, "", cs) == -1); |
| assert(indexOf("hello".byWchar, "", cs) == 0); |
| assert(indexOf("hello".byWchar, "l", cs) == 2); |
| assert(indexOf("heLLo".byWchar, "LL", cs) == 2); |
| assert(indexOf("hello".byWchar, "lox", cs) == -1); |
| assert(indexOf("hello".byWchar, "betty", cs) == -1); |
| assert(indexOf("hello\U00010143\u0100*\U00010143".byWchar, "\u0100*", cs) == 7); |
| } |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| import std.traits : EnumMembers; |
| |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| foreach (T; AliasSeq!(string, wstring, dstring)) |
| (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 |
| assert(indexOf(cast(S) null, to!T("a"), 1337) == -1); |
| assert(indexOf(to!S("def"), to!T("a"), 0) == -1); |
| assert(indexOf(to!S("abba"), to!T("a"), 2) == 3); |
| assert(indexOf(to!S("def"), to!T("f"), 1) == 2); |
| assert(indexOf(to!S("dfefffg"), to!T("fff"), 1) == 3); |
| assert(indexOf(to!S("dfeffgfff"), to!T("fff"), 5) == 6); |
| |
| assert(indexOf(to!S("dfeffgfff"), to!T("a"), 1, No.caseSensitive) == -1); |
| assert(indexOf(to!S("def"), to!T("a"), 2, No.caseSensitive) == -1); |
| assert(indexOf(to!S("abba"), to!T("a"), 3, No.caseSensitive) == 3); |
| assert(indexOf(to!S("def"), to!T("f"), 1, No.caseSensitive) == 2); |
| assert(indexOf(to!S("dfefffg"), to!T("fff"), 2, No.caseSensitive) == 3); |
| assert(indexOf(to!S("dfeffgfff"), to!T("fff"), 4, No.caseSensitive) == 6); |
| assert(indexOf(to!S("dfeffgffföä"), to!T("öä"), 9, No.caseSensitive) == 9, |
| to!string(indexOf(to!S("dfeffgffföä"), to!T("öä"), 9, No.caseSensitive)) |
| ~ " " ~ S.stringof ~ " " ~ T.stringof); |
| |
| S sPlts = "Mars: the fourth Rock (Planet) from the Sun."; |
| S sMars = "Who\'s \'My Favorite Maritian?\'"; |
| |
| assert(indexOf(sMars, to!T("MY fAVe"), 10, |
| No.caseSensitive) == -1); |
| assert(indexOf(sMars, to!T("mY fAVOriTe"), 4, No.caseSensitive) == 7); |
| assert(indexOf(sPlts, to!T("mArS:"), 0, No.caseSensitive) == 0); |
| assert(indexOf(sPlts, to!T("rOcK"), 12, No.caseSensitive) == 17); |
| assert(indexOf(sPlts, to!T("Un."), 32, No.caseSensitive) == 41); |
| assert(indexOf(sPlts, to!T(sPlts), 0, No.caseSensitive) == 0); |
| |
| assert(indexOf("\u0100", to!T("\u0100"), 0, No.caseSensitive) == 0); |
| |
| // Thanks to Carlos Santander B. and zwang |
| assert(indexOf("sus mejores cortesanos. Se embarcaron en el puerto de Dubai y", |
| to!T("page-break-before"), 10, No.caseSensitive) == -1); |
| |
| // In order for indexOf with and without index to be consistent |
| assert(indexOf(to!S(""), to!T("")) == indexOf(to!S(""), to!T(""), 0)); |
| }(); |
| |
| foreach (cs; EnumMembers!CaseSensitive) |
| { |
| assert(indexOf("hello\U00010143\u0100\U00010143", to!S("\u0100"), |
| 3, cs) == 9); |
| assert(indexOf("hello\U00010143\u0100\U00010143"w, to!S("\u0100"), |
| 3, cs) == 7); |
| assert(indexOf("hello\U00010143\u0100\U00010143"d, to!S("\u0100"), |
| 3, cs) == 6); |
| } |
| } |
| } |
| |
| /++ |
| Params: |
| s = string to search |
| c = character to search for |
| startIdx = the index into s to start searching from |
| cs = $(D Yes.caseSensitive) or $(D No.caseSensitive) |
| |
| Returns: |
| The index of the last occurrence of $(D c) in $(D s). If $(D c) is not |
| found, then $(D -1) is returned. The $(D startIdx) slices $(D s) in |
| the following way $(D s[0 .. startIdx]). $(D startIdx) represents a |
| codeunit index in $(D s). |
| |
| Throws: |
| If the sequence ending at $(D startIdx) does not represent a well |
| formed codepoint, then a $(REF UTFException, std,utf) may be thrown. |
| |
| $(D cs) indicates whether the comparisons are case sensitive. |
| +/ |
| ptrdiff_t lastIndexOf(Char)(const(Char)[] s, in dchar c, |
| in CaseSensitive cs = Yes.caseSensitive) @safe pure |
| if (isSomeChar!Char) |
| { |
| static import std.ascii, std.uni; |
| import std.utf : canSearchInCodeUnits; |
| if (cs == Yes.caseSensitive) |
| { |
| if (canSearchInCodeUnits!Char(c)) |
| { |
| foreach_reverse (i, it; s) |
| { |
| if (it == c) |
| { |
| return i; |
| } |
| } |
| } |
| else |
| { |
| foreach_reverse (i, dchar it; s) |
| { |
| if (it == c) |
| { |
| return i; |
| } |
| } |
| } |
| } |
| else |
| { |
| if (std.ascii.isASCII(c)) |
| { |
| immutable c1 = std.ascii.toLower(c); |
| |
| foreach_reverse (i, it; s) |
| { |
| immutable c2 = std.ascii.toLower(it); |
| if (c1 == c2) |
| { |
| return i; |
| } |
| } |
| } |
| else |
| { |
| immutable c1 = std.uni.toLower(c); |
| |
| foreach_reverse (i, dchar it; s) |
| { |
| immutable c2 = std.uni.toLower(it); |
| if (c1 == c2) |
| { |
| return i; |
| } |
| } |
| } |
| } |
| |
| return -1; |
| } |
| |
| /// Ditto |
| ptrdiff_t lastIndexOf(Char)(const(Char)[] s, in dchar c, in size_t startIdx, |
| in CaseSensitive cs = Yes.caseSensitive) @safe pure |
| if (isSomeChar!Char) |
| { |
| if (startIdx <= s.length) |
| { |
| return lastIndexOf(s[0u .. startIdx], c, cs); |
| } |
| |
| return -1; |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| import std.typecons : No; |
| |
| string s = "Hello World"; |
| assert(lastIndexOf(s, 'l') == 9); |
| assert(lastIndexOf(s, 'Z') == -1); |
| assert(lastIndexOf(s, 'L', No.caseSensitive) == 9); |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| import std.typecons : No; |
| |
| string s = "Hello World"; |
| assert(lastIndexOf(s, 'l', 4) == 3); |
| assert(lastIndexOf(s, 'Z', 1337) == -1); |
| assert(lastIndexOf(s, 'L', 7, No.caseSensitive) == 3); |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| import std.exception : assertCTFEable; |
| import std.traits : EnumMembers; |
| |
| assertCTFEable!( |
| { |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| assert(lastIndexOf(cast(S) null, 'a') == -1); |
| assert(lastIndexOf(to!S("def"), 'a') == -1); |
| assert(lastIndexOf(to!S("abba"), 'a') == 3); |
| assert(lastIndexOf(to!S("def"), 'f') == 2); |
| assert(lastIndexOf(to!S("ödef"), 'ö') == 0); |
| |
| assert(lastIndexOf(cast(S) null, 'a', No.caseSensitive) == -1); |
| assert(lastIndexOf(to!S("def"), 'a', No.caseSensitive) == -1); |
| assert(lastIndexOf(to!S("AbbA"), 'a', No.caseSensitive) == 3); |
| assert(lastIndexOf(to!S("def"), 'F', No.caseSensitive) == 2); |
| assert(lastIndexOf(to!S("ödef"), 'ö', No.caseSensitive) == 0); |
| assert(lastIndexOf(to!S("i\u0100def"), to!dchar("\u0100"), |
| No.caseSensitive) == 1); |
| |
| S sPlts = "Mars: the fourth Rock (Planet) from the Sun."; |
| |
| assert(lastIndexOf(to!S("def"), 'f', No.caseSensitive) == 2); |
| assert(lastIndexOf(sPlts, 'M', No.caseSensitive) == 34); |
| assert(lastIndexOf(sPlts, 'S', No.caseSensitive) == 40); |
| } |
| |
| foreach (cs; EnumMembers!CaseSensitive) |
| { |
| assert(lastIndexOf("\U00010143\u0100\U00010143hello", '\u0100', cs) == 4); |
| assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, '\u0100', cs) == 2); |
| assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, '\u0100', cs) == 1); |
| } |
| }); |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| import std.traits : EnumMembers; |
| |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| assert(lastIndexOf(cast(S) null, 'a') == -1); |
| assert(lastIndexOf(to!S("def"), 'a') == -1); |
| assert(lastIndexOf(to!S("abba"), 'a', 3) == 0); |
| assert(lastIndexOf(to!S("deff"), 'f', 3) == 2); |
| |
| assert(lastIndexOf(cast(S) null, 'a', No.caseSensitive) == -1); |
| assert(lastIndexOf(to!S("def"), 'a', No.caseSensitive) == -1); |
| assert(lastIndexOf(to!S("AbbAa"), 'a', to!ushort(4), No.caseSensitive) == 3, |
| to!string(lastIndexOf(to!S("AbbAa"), 'a', 4, No.caseSensitive))); |
| assert(lastIndexOf(to!S("def"), 'F', 3, No.caseSensitive) == 2); |
| |
| S sPlts = "Mars: the fourth Rock (Planet) from the Sun."; |
| |
| assert(lastIndexOf(to!S("def"), 'f', 4, No.caseSensitive) == -1); |
| assert(lastIndexOf(sPlts, 'M', sPlts.length -2, No.caseSensitive) == 34); |
| assert(lastIndexOf(sPlts, 'S', sPlts.length -2, No.caseSensitive) == 40); |
| } |
| |
| foreach (cs; EnumMembers!CaseSensitive) |
| { |
| assert(lastIndexOf("\U00010143\u0100\U00010143hello", '\u0100', cs) == 4); |
| assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, '\u0100', cs) == 2); |
| assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, '\u0100', cs) == 1); |
| } |
| } |
| |
| /++ |
| Params: |
| s = string to search |
| sub = substring to search for |
| startIdx = the index into s to start searching from |
| cs = $(D Yes.caseSensitive) or $(D No.caseSensitive) |
| |
| Returns: |
| the index of the last occurrence of $(D sub) in $(D s). If $(D sub) is |
| not found, then $(D -1) is returned. The $(D startIdx) slices $(D s) |
| in the following way $(D s[0 .. startIdx]). $(D startIdx) represents a |
| codeunit index in $(D s). |
| |
| Throws: |
| If the sequence ending at $(D startIdx) does not represent a well |
| formed codepoint, then a $(REF UTFException, std,utf) may be thrown. |
| |
| $(D cs) indicates whether the comparisons are case sensitive. |
| +/ |
| ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, |
| in CaseSensitive cs = Yes.caseSensitive) @safe pure |
| if (isSomeChar!Char1 && isSomeChar!Char2) |
| { |
| import std.algorithm.searching : endsWith; |
| import std.conv : to; |
| import std.range.primitives : walkLength; |
| static import std.uni; |
| import std.utf : strideBack; |
| if (sub.empty) |
| return -1; |
| |
| if (walkLength(sub) == 1) |
| return lastIndexOf(s, sub.front, cs); |
| |
| if (cs == Yes.caseSensitive) |
| { |
| static if (is(Unqual!Char1 == Unqual!Char2)) |
| { |
| import core.stdc.string : memcmp; |
| |
| immutable c = sub[0]; |
| |
| for (ptrdiff_t i = s.length - sub.length; i >= 0; --i) |
| { |
| if (s[i] == c) |
| { |
| if (__ctfe) |
| { |
| foreach (j; 1 .. sub.length) |
| { |
| if (s[i + j] != sub[j]) |
| continue; |
| } |
| return i; |
| } |
| else |
| { |
| auto trustedMemcmp(in void* s1, in void* s2, size_t n) @trusted |
| { |
| return memcmp(s1, s2, n); |
| } |
| if (trustedMemcmp(&s[i + 1], &sub[1], |
| (sub.length - 1) * Char1.sizeof) == 0) |
| return i; |
| } |
| } |
| } |
| } |
| else |
| { |
| for (size_t i = s.length; !s.empty;) |
| { |
| if (s.endsWith(sub)) |
| return cast(ptrdiff_t) i - to!(const(Char1)[])(sub).length; |
| |
| i -= strideBack(s, i); |
| s = s[0 .. i]; |
| } |
| } |
| } |
| else |
| { |
| for (size_t i = s.length; !s.empty;) |
| { |
| if (endsWith!((a, b) => std.uni.toLower(a) == std.uni.toLower(b)) |
| (s, sub)) |
| { |
| return cast(ptrdiff_t) i - to!(const(Char1)[])(sub).length; |
| } |
| |
| i -= strideBack(s, i); |
| s = s[0 .. i]; |
| } |
| } |
| |
| return -1; |
| } |
| |
| /// Ditto |
| ptrdiff_t lastIndexOf(Char1, Char2)(const(Char1)[] s, const(Char2)[] sub, |
| in size_t startIdx, in CaseSensitive cs = Yes.caseSensitive) @safe pure |
| if (isSomeChar!Char1 && isSomeChar!Char2) |
| { |
| if (startIdx <= s.length) |
| { |
| return lastIndexOf(s[0u .. startIdx], sub, cs); |
| } |
| |
| return -1; |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| import std.typecons : No; |
| |
| string s = "Hello World"; |
| assert(lastIndexOf(s, "ll") == 2); |
| assert(lastIndexOf(s, "Zo") == -1); |
| assert(lastIndexOf(s, "lL", No.caseSensitive) == 2); |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| import std.typecons : No; |
| |
| string s = "Hello World"; |
| assert(lastIndexOf(s, "ll", 4) == 2); |
| assert(lastIndexOf(s, "Zo", 128) == -1); |
| assert(lastIndexOf(s, "lL", 3, No.caseSensitive) == -1); |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| auto r = to!S("").lastIndexOf("hello"); |
| assert(r == -1, to!string(r)); |
| |
| r = to!S("hello").lastIndexOf(""); |
| assert(r == -1, to!string(r)); |
| |
| r = to!S("").lastIndexOf(""); |
| assert(r == -1, to!string(r)); |
| } |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| import std.exception : assertCTFEable; |
| import std.traits : EnumMembers; |
| |
| assertCTFEable!( |
| { |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| foreach (T; AliasSeq!(string, wstring, dstring)) |
| (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 |
| enum typeStr = S.stringof ~ " " ~ T.stringof; |
| |
| assert(lastIndexOf(cast(S) null, to!T("a")) == -1, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T("c")) == 6, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd")) == 6, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T("ef")) == 8, typeStr); |
| assert(lastIndexOf(to!S("abcdefCdef"), to!T("c")) == 2, typeStr); |
| assert(lastIndexOf(to!S("abcdefCdef"), to!T("cd")) == 2, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T("x")) == -1, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T("xy")) == -1, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T("")) == -1, typeStr); |
| assert(lastIndexOf(to!S("öabcdefcdef"), to!T("ö")) == 0, typeStr); |
| |
| assert(lastIndexOf(cast(S) null, to!T("a"), No.caseSensitive) == -1, typeStr); |
| assert(lastIndexOf(to!S("abcdefCdef"), to!T("c"), No.caseSensitive) == 6, typeStr); |
| assert(lastIndexOf(to!S("abcdefCdef"), to!T("cD"), No.caseSensitive) == 6, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T("x"), No.caseSensitive) == -1, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T("xy"), No.caseSensitive) == -1, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T(""), No.caseSensitive) == -1, typeStr); |
| assert(lastIndexOf(to!S("öabcdefcdef"), to!T("ö"), No.caseSensitive) == 0, typeStr); |
| |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T("c"), No.caseSensitive) == 6, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd"), No.caseSensitive) == 6, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T("def"), No.caseSensitive) == 7, typeStr); |
| |
| assert(lastIndexOf(to!S("ödfeffgfff"), to!T("ö"), Yes.caseSensitive) == 0); |
| |
| S sPlts = "Mars: the fourth Rock (Planet) from the Sun."; |
| S sMars = "Who\'s \'My Favorite Maritian?\'"; |
| |
| assert(lastIndexOf(sMars, to!T("RiTE maR"), No.caseSensitive) == 14, typeStr); |
| assert(lastIndexOf(sPlts, to!T("FOuRTh"), No.caseSensitive) == 10, typeStr); |
| assert(lastIndexOf(sMars, to!T("whO\'s \'MY"), No.caseSensitive) == 0, typeStr); |
| assert(lastIndexOf(sMars, to!T(sMars), No.caseSensitive) == 0, typeStr); |
| }(); |
| |
| foreach (cs; EnumMembers!CaseSensitive) |
| { |
| enum csString = to!string(cs); |
| |
| assert(lastIndexOf("\U00010143\u0100\U00010143hello", to!S("\u0100"), cs) == 4, csString); |
| assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, to!S("\u0100"), cs) == 2, csString); |
| assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, to!S("\u0100"), cs) == 1, csString); |
| } |
| } |
| }); |
| } |
| |
| @safe pure unittest // issue13529 |
| { |
| import std.conv : to; |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| foreach (T; AliasSeq!(string, wstring, dstring)) |
| { |
| enum typeStr = S.stringof ~ " " ~ T.stringof; |
| auto idx = lastIndexOf(to!T("Hällö Wörldö ö"),to!S("ö ö")); |
| assert(idx != -1, to!string(idx) ~ " " ~ typeStr); |
| |
| idx = lastIndexOf(to!T("Hällö Wörldö ö"),to!S("ö öd")); |
| assert(idx == -1, to!string(idx) ~ " " ~ typeStr); |
| } |
| } |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| import std.traits : EnumMembers; |
| |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| foreach (T; AliasSeq!(string, wstring, dstring)) |
| (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 |
| enum typeStr = S.stringof ~ " " ~ T.stringof; |
| |
| assert(lastIndexOf(cast(S) null, to!T("a")) == -1, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T("c"), 5) == 2, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd"), 3) == -1, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T("ef"), 6) == 4, typeStr ~ |
| format(" %u", lastIndexOf(to!S("abcdefcdef"), to!T("ef"), 6))); |
| assert(lastIndexOf(to!S("abcdefCdef"), to!T("c"), 5) == 2, typeStr); |
| assert(lastIndexOf(to!S("abcdefCdef"), to!T("cd"), 3) == -1, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdefx"), to!T("x"), 1) == -1, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdefxy"), to!T("xy"), 6) == -1, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T(""), 8) == -1, typeStr); |
| assert(lastIndexOf(to!S("öafö"), to!T("ö"), 3) == 0, typeStr ~ |
| to!string(lastIndexOf(to!S("öafö"), to!T("ö"), 3))); //BUG 10472 |
| |
| assert(lastIndexOf(cast(S) null, to!T("a"), 1, No.caseSensitive) == -1, typeStr); |
| assert(lastIndexOf(to!S("abcdefCdef"), to!T("c"), 5, No.caseSensitive) == 2, typeStr); |
| assert(lastIndexOf(to!S("abcdefCdef"), to!T("cD"), 4, No.caseSensitive) == 2, typeStr ~ |
| " " ~ to!string(lastIndexOf(to!S("abcdefCdef"), to!T("cD"), 3, No.caseSensitive))); |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T("x"),3 , No.caseSensitive) == -1, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdefXY"), to!T("xy"), 4, No.caseSensitive) == -1, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T(""), 7, No.caseSensitive) == -1, typeStr); |
| |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T("c"), 4, No.caseSensitive) == 2, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T("cd"), 4, No.caseSensitive) == 2, typeStr); |
| assert(lastIndexOf(to!S("abcdefcdef"), to!T("def"), 6, No.caseSensitive) == 3, typeStr); |
| assert(lastIndexOf(to!S(""), to!T(""), 0) == lastIndexOf(to!S(""), to!T("")), typeStr); |
| }(); |
| |
| foreach (cs; EnumMembers!CaseSensitive) |
| { |
| enum csString = to!string(cs); |
| |
| assert(lastIndexOf("\U00010143\u0100\U00010143hello", to!S("\u0100"), 6, cs) == 4, csString); |
| assert(lastIndexOf("\U00010143\u0100\U00010143hello"w, to!S("\u0100"), 6, cs) == 2, csString); |
| assert(lastIndexOf("\U00010143\u0100\U00010143hello"d, to!S("\u0100"), 3, cs) == 1, csString); |
| } |
| } |
| } |
| |
| private ptrdiff_t indexOfAnyNeitherImpl(bool forward, bool any, Char, Char2)( |
| const(Char)[] haystack, const(Char2)[] needles, |
| in CaseSensitive cs = Yes.caseSensitive) @safe pure |
| if (isSomeChar!Char && isSomeChar!Char2) |
| { |
| import std.algorithm.searching : canFind, findAmong; |
| if (cs == Yes.caseSensitive) |
| { |
| static if (forward) |
| { |
| static if (any) |
| { |
| size_t n = haystack.findAmong(needles).length; |
| return n ? haystack.length - n : -1; |
| } |
| else |
| { |
| foreach (idx, dchar hay; haystack) |
| { |
| if (!canFind(needles, hay)) |
| { |
| return idx; |
| } |
| } |
| } |
| } |
| else |
| { |
| static if (any) |
| { |
| import std.range : retro; |
| import std.utf : strideBack; |
| size_t n = haystack.retro.findAmong(needles).source.length; |
| if (n) |
| { |
| return n - haystack.strideBack(n); |
| } |
| } |
| else |
| { |
| foreach_reverse (idx, dchar hay; haystack) |
| { |
| if (!canFind(needles, hay)) |
| { |
| return idx; |
| } |
| } |
| } |
| } |
| } |
| else |
| { |
| import std.range.primitives : walkLength; |
| if (needles.length <= 16 && needles.walkLength(17)) |
| { |
| size_t si = 0; |
| dchar[16] scratch = void; |
| foreach ( dchar c; needles) |
| { |
| scratch[si++] = toLower(c); |
| } |
| |
| static if (forward) |
| { |
| foreach (i, dchar c; haystack) |
| { |
| if (canFind(scratch[0 .. si], toLower(c)) == any) |
| { |
| return i; |
| } |
| } |
| } |
| else |
| { |
| foreach_reverse (i, dchar c; haystack) |
| { |
| if (canFind(scratch[0 .. si], toLower(c)) == any) |
| { |
| return i; |
| } |
| } |
| } |
| } |
| else |
| { |
| static bool f(dchar a, dchar b) |
| { |
| return toLower(a) == b; |
| } |
| |
| static if (forward) |
| { |
| foreach (i, dchar c; haystack) |
| { |
| if (canFind!f(needles, toLower(c)) == any) |
| { |
| return i; |
| } |
| } |
| } |
| else |
| { |
| foreach_reverse (i, dchar c; haystack) |
| { |
| if (canFind!f(needles, toLower(c)) == any) |
| { |
| return i; |
| } |
| } |
| } |
| } |
| } |
| |
| return -1; |
| } |
| |
| /** |
| Returns the index of the first occurrence of any of the elements in $(D |
| needles) in $(D haystack). If no element of $(D needles) is found, |
| then $(D -1) is returned. The $(D startIdx) slices $(D haystack) in the |
| following way $(D haystack[startIdx .. $]). $(D startIdx) represents a |
| codeunit index in $(D haystack). If the sequence ending at $(D startIdx) |
| does not represent a well formed codepoint, then a $(REF UTFException, std,utf) |
| may be thrown. |
| |
| Params: |
| haystack = String to search for needles in. |
| needles = Strings to search for in haystack. |
| startIdx = slices haystack like this $(D haystack[startIdx .. $]). If |
| the startIdx is greater equal the length of haystack the functions |
| returns $(D -1). |
| cs = Indicates whether the comparisons are case sensitive. |
| */ |
| ptrdiff_t indexOfAny(Char,Char2)(const(Char)[] haystack, const(Char2)[] needles, |
| in CaseSensitive cs = Yes.caseSensitive) @safe pure |
| if (isSomeChar!Char && isSomeChar!Char2) |
| { |
| return indexOfAnyNeitherImpl!(true, true)(haystack, needles, cs); |
| } |
| |
| /// Ditto |
| ptrdiff_t indexOfAny(Char,Char2)(const(Char)[] haystack, const(Char2)[] needles, |
| in size_t startIdx, in CaseSensitive cs = Yes.caseSensitive) @safe pure |
| if (isSomeChar!Char && isSomeChar!Char2) |
| { |
| if (startIdx < haystack.length) |
| { |
| ptrdiff_t foundIdx = indexOfAny(haystack[startIdx .. $], needles, cs); |
| if (foundIdx != -1) |
| { |
| return foundIdx + cast(ptrdiff_t) startIdx; |
| } |
| } |
| |
| return -1; |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| import std.conv : to; |
| |
| ptrdiff_t i = "helloWorld".indexOfAny("Wr"); |
| assert(i == 5); |
| i = "öällo world".indexOfAny("lo "); |
| assert(i == 4, to!string(i)); |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| import std.conv : to; |
| |
| ptrdiff_t i = "helloWorld".indexOfAny("Wr", 4); |
| assert(i == 5); |
| |
| i = "Foo öällo world".indexOfAny("lh", 3); |
| assert(i == 8, to!string(i)); |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| auto r = to!S("").indexOfAny("hello"); |
| assert(r == -1, to!string(r)); |
| |
| r = to!S("hello").indexOfAny(""); |
| assert(r == -1, to!string(r)); |
| |
| r = to!S("").indexOfAny(""); |
| assert(r == -1, to!string(r)); |
| } |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| import std.exception : assertCTFEable; |
| |
| assertCTFEable!( |
| { |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| foreach (T; AliasSeq!(string, wstring, dstring)) |
| (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 |
| assert(indexOfAny(cast(S) null, to!T("a")) == -1); |
| assert(indexOfAny(to!S("def"), to!T("rsa")) == -1); |
| assert(indexOfAny(to!S("abba"), to!T("a")) == 0); |
| assert(indexOfAny(to!S("def"), to!T("f")) == 2); |
| assert(indexOfAny(to!S("dfefffg"), to!T("fgh")) == 1); |
| assert(indexOfAny(to!S("dfeffgfff"), to!T("feg")) == 1); |
| |
| assert(indexOfAny(to!S("zfeffgfff"), to!T("ACDC"), |
| No.caseSensitive) == -1); |
| assert(indexOfAny(to!S("def"), to!T("MI6"), |
| No.caseSensitive) == -1); |
| assert(indexOfAny(to!S("abba"), to!T("DEA"), |
| No.caseSensitive) == 0); |
| assert(indexOfAny(to!S("def"), to!T("FBI"), No.caseSensitive) == 2); |
| assert(indexOfAny(to!S("dfefffg"), to!T("NSA"), No.caseSensitive) |
| == -1); |
| assert(indexOfAny(to!S("dfeffgfff"), to!T("BND"), |
| No.caseSensitive) == 0); |
| assert(indexOfAny(to!S("dfeffgfff"), to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"), |
| No.caseSensitive) == 0); |
| |
| assert(indexOfAny("\u0100", to!T("\u0100"), No.caseSensitive) == 0); |
| }(); |
| } |
| } |
| ); |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| import std.traits : EnumMembers; |
| |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| foreach (T; AliasSeq!(string, wstring, dstring)) |
| (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 |
| assert(indexOfAny(cast(S) null, to!T("a"), 1337) == -1); |
| assert(indexOfAny(to!S("def"), to!T("AaF"), 0) == -1); |
| assert(indexOfAny(to!S("abba"), to!T("NSa"), 2) == 3); |
| assert(indexOfAny(to!S("def"), to!T("fbi"), 1) == 2); |
| assert(indexOfAny(to!S("dfefffg"), to!T("foo"), 2) == 3); |
| assert(indexOfAny(to!S("dfeffgfff"), to!T("fsb"), 5) == 6); |
| |
| assert(indexOfAny(to!S("dfeffgfff"), to!T("NDS"), 1, |
| No.caseSensitive) == -1); |
| assert(indexOfAny(to!S("def"), to!T("DRS"), 2, |
| No.caseSensitive) == -1); |
| assert(indexOfAny(to!S("abba"), to!T("SI"), 3, |
| No.caseSensitive) == -1); |
| assert(indexOfAny(to!S("deO"), to!T("ASIO"), 1, |
| No.caseSensitive) == 2); |
| assert(indexOfAny(to!S("dfefffg"), to!T("fbh"), 2, |
| No.caseSensitive) == 3); |
| assert(indexOfAny(to!S("dfeffgfff"), to!T("fEe"), 4, |
| No.caseSensitive) == 4); |
| assert(indexOfAny(to!S("dfeffgffföä"), to!T("föä"), 9, |
| No.caseSensitive) == 9); |
| |
| assert(indexOfAny("\u0100", to!T("\u0100"), 0, |
| No.caseSensitive) == 0); |
| }(); |
| |
| foreach (cs; EnumMembers!CaseSensitive) |
| { |
| assert(indexOfAny("hello\U00010143\u0100\U00010143", |
| to!S("e\u0100"), 3, cs) == 9); |
| assert(indexOfAny("hello\U00010143\u0100\U00010143"w, |
| to!S("h\u0100"), 3, cs) == 7); |
| assert(indexOfAny("hello\U00010143\u0100\U00010143"d, |
| to!S("l\u0100"), 5, cs) == 6); |
| } |
| } |
| } |
| |
| /** |
| Returns the index of the last occurrence of any of the elements in $(D |
| needles) in $(D haystack). If no element of $(D needles) is found, |
| then $(D -1) is returned. The $(D stopIdx) slices $(D haystack) in the |
| following way $(D s[0 .. stopIdx]). $(D stopIdx) represents a codeunit |
| index in $(D haystack). If the sequence ending at $(D startIdx) does not |
| represent a well formed codepoint, then a $(REF UTFException, std,utf) may be |
| thrown. |
| |
| Params: |
| haystack = String to search for needles in. |
| needles = Strings to search for in haystack. |
| stopIdx = slices haystack like this $(D haystack[0 .. stopIdx]). If |
| the stopIdx is greater equal the length of haystack the functions |
| returns $(D -1). |
| cs = Indicates whether the comparisons are case sensitive. |
| */ |
| ptrdiff_t lastIndexOfAny(Char,Char2)(const(Char)[] haystack, |
| const(Char2)[] needles, in CaseSensitive cs = Yes.caseSensitive) |
| @safe pure |
| if (isSomeChar!Char && isSomeChar!Char2) |
| { |
| return indexOfAnyNeitherImpl!(false, true)(haystack, needles, cs); |
| } |
| |
| /// Ditto |
| ptrdiff_t lastIndexOfAny(Char,Char2)(const(Char)[] haystack, |
| const(Char2)[] needles, in size_t stopIdx, |
| in CaseSensitive cs = Yes.caseSensitive) @safe pure |
| if (isSomeChar!Char && isSomeChar!Char2) |
| { |
| if (stopIdx <= haystack.length) |
| { |
| return lastIndexOfAny(haystack[0u .. stopIdx], needles, cs); |
| } |
| |
| return -1; |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| ptrdiff_t i = "helloWorld".lastIndexOfAny("Wlo"); |
| assert(i == 8); |
| |
| i = "Foo öäöllo world".lastIndexOfAny("öF"); |
| assert(i == 8); |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| import std.conv : to; |
| |
| ptrdiff_t i = "helloWorld".lastIndexOfAny("Wlo", 4); |
| assert(i == 3); |
| |
| i = "Foo öäöllo world".lastIndexOfAny("öF", 3); |
| assert(i == 0); |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| auto r = to!S("").lastIndexOfAny("hello"); |
| assert(r == -1, to!string(r)); |
| |
| r = to!S("hello").lastIndexOfAny(""); |
| assert(r == -1, to!string(r)); |
| |
| r = to!S("").lastIndexOfAny(""); |
| assert(r == -1, to!string(r)); |
| } |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| import std.exception : assertCTFEable; |
| |
| assertCTFEable!( |
| { |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| foreach (T; AliasSeq!(string, wstring, dstring)) |
| (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 |
| assert(lastIndexOfAny(cast(S) null, to!T("a")) == -1); |
| assert(lastIndexOfAny(to!S("def"), to!T("rsa")) == -1); |
| assert(lastIndexOfAny(to!S("abba"), to!T("a")) == 3); |
| assert(lastIndexOfAny(to!S("def"), to!T("f")) == 2); |
| assert(lastIndexOfAny(to!S("dfefffg"), to!T("fgh")) == 6); |
| |
| ptrdiff_t oeIdx = 9; |
| if (is(S == wstring) || is(S == dstring)) |
| { |
| oeIdx = 8; |
| } |
| |
| auto foundOeIdx = lastIndexOfAny(to!S("dfeffgföf"), to!T("feg")); |
| assert(foundOeIdx == oeIdx, to!string(foundOeIdx)); |
| |
| assert(lastIndexOfAny(to!S("zfeffgfff"), to!T("ACDC"), |
| No.caseSensitive) == -1); |
| assert(lastIndexOfAny(to!S("def"), to!T("MI6"), |
| No.caseSensitive) == -1); |
| assert(lastIndexOfAny(to!S("abba"), to!T("DEA"), |
| No.caseSensitive) == 3); |
| assert(lastIndexOfAny(to!S("def"), to!T("FBI"), |
| No.caseSensitive) == 2); |
| assert(lastIndexOfAny(to!S("dfefffg"), to!T("NSA"), |
| No.caseSensitive) == -1); |
| |
| oeIdx = 2; |
| if (is(S == wstring) || is(S == dstring)) |
| { |
| oeIdx = 1; |
| } |
| assert(lastIndexOfAny(to!S("ödfeffgfff"), to!T("BND"), |
| No.caseSensitive) == oeIdx); |
| |
| assert(lastIndexOfAny("\u0100", to!T("\u0100"), |
| No.caseSensitive) == 0); |
| }(); |
| } |
| } |
| ); |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| import std.exception : assertCTFEable; |
| |
| assertCTFEable!( |
| { |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| foreach (T; AliasSeq!(string, wstring, dstring)) |
| (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 |
| enum typeStr = S.stringof ~ " " ~ T.stringof; |
| |
| assert(lastIndexOfAny(cast(S) null, to!T("a"), 1337) == -1, |
| typeStr); |
| assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("c"), 7) == 6, |
| typeStr); |
| assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("cd"), 5) == 3, |
| typeStr); |
| assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("ef"), 6) == 5, |
| typeStr); |
| assert(lastIndexOfAny(to!S("abcdefCdef"), to!T("c"), 8) == 2, |
| typeStr); |
| assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("x"), 7) == -1, |
| typeStr); |
| assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("xy"), 4) == -1, |
| typeStr); |
| assert(lastIndexOfAny(to!S("öabcdefcdef"), to!T("ö"), 2) == 0, |
| typeStr); |
| |
| assert(lastIndexOfAny(cast(S) null, to!T("a"), 1337, |
| No.caseSensitive) == -1, typeStr); |
| assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("C"), 7, |
| No.caseSensitive) == 6, typeStr); |
| assert(lastIndexOfAny(to!S("ABCDEFCDEF"), to!T("cd"), 5, |
| No.caseSensitive) == 3, typeStr); |
| assert(lastIndexOfAny(to!S("abcdefcdef"), to!T("EF"), 6, |
| No.caseSensitive) == 5, typeStr); |
| assert(lastIndexOfAny(to!S("ABCDEFcDEF"), to!T("C"), 8, |
| No.caseSensitive) == 6, typeStr); |
| assert(lastIndexOfAny(to!S("ABCDEFCDEF"), to!T("x"), 7, |
| No.caseSensitive) == -1, typeStr); |
| assert(lastIndexOfAny(to!S("abCdefcdef"), to!T("XY"), 4, |
| No.caseSensitive) == -1, typeStr); |
| assert(lastIndexOfAny(to!S("ÖABCDEFCDEF"), to!T("ö"), 2, |
| No.caseSensitive) == 0, typeStr); |
| }(); |
| } |
| } |
| ); |
| } |
| |
| /** |
| Returns the index of the first occurrence of any character not an elements |
| in $(D needles) in $(D haystack). If all element of $(D haystack) are |
| element of $(D needles) $(D -1) is returned. |
| |
| Params: |
| haystack = String to search for needles in. |
| needles = Strings to search for in haystack. |
| startIdx = slices haystack like this $(D haystack[startIdx .. $]). If |
| the startIdx is greater equal the length of haystack the functions |
| returns $(D -1). |
| cs = Indicates whether the comparisons are case sensitive. |
| */ |
| ptrdiff_t indexOfNeither(Char,Char2)(const(Char)[] haystack, |
| const(Char2)[] needles, in CaseSensitive cs = Yes.caseSensitive) |
| @safe pure |
| if (isSomeChar!Char && isSomeChar!Char2) |
| { |
| return indexOfAnyNeitherImpl!(true, false)(haystack, needles, cs); |
| } |
| |
| /// Ditto |
| ptrdiff_t indexOfNeither(Char,Char2)(const(Char)[] haystack, |
| const(Char2)[] needles, in size_t startIdx, |
| in CaseSensitive cs = Yes.caseSensitive) |
| @safe pure |
| if (isSomeChar!Char && isSomeChar!Char2) |
| { |
| if (startIdx < haystack.length) |
| { |
| ptrdiff_t foundIdx = indexOfAnyNeitherImpl!(true, false)( |
| haystack[startIdx .. $], needles, cs); |
| if (foundIdx != -1) |
| { |
| return foundIdx + cast(ptrdiff_t) startIdx; |
| } |
| } |
| return -1; |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| assert(indexOfNeither("abba", "a", 2) == 2); |
| assert(indexOfNeither("def", "de", 1) == 2); |
| assert(indexOfNeither("dfefffg", "dfe", 4) == 6); |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| assert(indexOfNeither("def", "a") == 0); |
| assert(indexOfNeither("def", "de") == 2); |
| assert(indexOfNeither("dfefffg", "dfe") == 6); |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| auto r = to!S("").indexOfNeither("hello"); |
| assert(r == -1, to!string(r)); |
| |
| r = to!S("hello").indexOfNeither(""); |
| assert(r == 0, to!string(r)); |
| |
| r = to!S("").indexOfNeither(""); |
| assert(r == -1, to!string(r)); |
| } |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| import std.exception : assertCTFEable; |
| |
| assertCTFEable!( |
| { |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| foreach (T; AliasSeq!(string, wstring, dstring)) |
| (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 |
| assert(indexOfNeither(cast(S) null, to!T("a")) == -1); |
| assert(indexOfNeither("abba", "a") == 1); |
| |
| assert(indexOfNeither(to!S("dfeffgfff"), to!T("a"), |
| No.caseSensitive) == 0); |
| assert(indexOfNeither(to!S("def"), to!T("D"), |
| No.caseSensitive) == 1); |
| assert(indexOfNeither(to!S("ABca"), to!T("a"), |
| No.caseSensitive) == 1); |
| assert(indexOfNeither(to!S("def"), to!T("f"), |
| No.caseSensitive) == 0); |
| assert(indexOfNeither(to!S("DfEfffg"), to!T("dFe"), |
| No.caseSensitive) == 6); |
| if (is(S == string)) |
| { |
| assert(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"), |
| No.caseSensitive) == 8, |
| to!string(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"), |
| No.caseSensitive))); |
| } |
| else |
| { |
| assert(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"), |
| No.caseSensitive) == 7, |
| to!string(indexOfNeither(to!S("äDfEfffg"), to!T("ädFe"), |
| No.caseSensitive))); |
| } |
| }(); |
| } |
| } |
| ); |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| import std.exception : assertCTFEable; |
| |
| assertCTFEable!( |
| { |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| foreach (T; AliasSeq!(string, wstring, dstring)) |
| (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 |
| assert(indexOfNeither(cast(S) null, to!T("a"), 1) == -1); |
| assert(indexOfNeither(to!S("def"), to!T("a"), 1) == 1, |
| to!string(indexOfNeither(to!S("def"), to!T("a"), 1))); |
| |
| assert(indexOfNeither(to!S("dfeffgfff"), to!T("a"), 4, |
| No.caseSensitive) == 4); |
| assert(indexOfNeither(to!S("def"), to!T("D"), 2, |
| No.caseSensitive) == 2); |
| assert(indexOfNeither(to!S("ABca"), to!T("a"), 3, |
| No.caseSensitive) == -1); |
| assert(indexOfNeither(to!S("def"), to!T("tzf"), 2, |
| No.caseSensitive) == -1); |
| assert(indexOfNeither(to!S("DfEfffg"), to!T("dFe"), 5, |
| No.caseSensitive) == 6); |
| if (is(S == string)) |
| { |
| assert(indexOfNeither(to!S("öDfEfffg"), to!T("äDi"), 2, |
| No.caseSensitive) == 3, to!string(indexOfNeither( |
| to!S("öDfEfffg"), to!T("äDi"), 2, No.caseSensitive))); |
| } |
| else |
| { |
| assert(indexOfNeither(to!S("öDfEfffg"), to!T("äDi"), 2, |
| No.caseSensitive) == 2, to!string(indexOfNeither( |
| to!S("öDfEfffg"), to!T("äDi"), 2, No.caseSensitive))); |
| } |
| }(); |
| } |
| } |
| ); |
| } |
| |
| /** |
| Returns the last index of the first occurence of any character that is not |
| an elements in $(D needles) in $(D haystack). If all element of |
| $(D haystack) are element of $(D needles) $(D -1) is returned. |
| |
| Params: |
| haystack = String to search for needles in. |
| needles = Strings to search for in haystack. |
| stopIdx = slices haystack like this $(D haystack[0 .. stopIdx]) If |
| the stopIdx is greater equal the length of haystack the functions |
| returns $(D -1). |
| cs = Indicates whether the comparisons are case sensitive. |
| */ |
| ptrdiff_t lastIndexOfNeither(Char,Char2)(const(Char)[] haystack, |
| const(Char2)[] needles, in CaseSensitive cs = Yes.caseSensitive) |
| @safe pure |
| if (isSomeChar!Char && isSomeChar!Char2) |
| { |
| return indexOfAnyNeitherImpl!(false, false)(haystack, needles, cs); |
| } |
| |
| /// Ditto |
| ptrdiff_t lastIndexOfNeither(Char,Char2)(const(Char)[] haystack, |
| const(Char2)[] needles, in size_t stopIdx, |
| in CaseSensitive cs = Yes.caseSensitive) |
| @safe pure |
| if (isSomeChar!Char && isSomeChar!Char2) |
| { |
| if (stopIdx < haystack.length) |
| { |
| return indexOfAnyNeitherImpl!(false, false)(haystack[0 .. stopIdx], |
| needles, cs); |
| } |
| return -1; |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| assert(lastIndexOfNeither("abba", "a") == 2); |
| assert(lastIndexOfNeither("def", "f") == 1); |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| assert(lastIndexOfNeither("def", "rsa", 3) == -1); |
| assert(lastIndexOfNeither("abba", "a", 2) == 1); |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| auto r = to!S("").lastIndexOfNeither("hello"); |
| assert(r == -1, to!string(r)); |
| |
| r = to!S("hello").lastIndexOfNeither(""); |
| assert(r == 4, to!string(r)); |
| |
| r = to!S("").lastIndexOfNeither(""); |
| assert(r == -1, to!string(r)); |
| } |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| import std.exception : assertCTFEable; |
| |
| assertCTFEable!( |
| { |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| foreach (T; AliasSeq!(string, wstring, dstring)) |
| (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 |
| assert(lastIndexOfNeither(cast(S) null, to!T("a")) == -1); |
| assert(lastIndexOfNeither(to!S("def"), to!T("rsa")) == 2); |
| assert(lastIndexOfNeither(to!S("dfefffg"), to!T("fgh")) == 2); |
| |
| ptrdiff_t oeIdx = 8; |
| if (is(S == string)) |
| { |
| oeIdx = 9; |
| } |
| |
| auto foundOeIdx = lastIndexOfNeither(to!S("ödfefegff"), to!T("zeg")); |
| assert(foundOeIdx == oeIdx, to!string(foundOeIdx)); |
| |
| assert(lastIndexOfNeither(to!S("zfeffgfsb"), to!T("FSB"), |
| No.caseSensitive) == 5); |
| assert(lastIndexOfNeither(to!S("def"), to!T("MI6"), |
| No.caseSensitive) == 2, to!string(lastIndexOfNeither(to!S("def"), |
| to!T("MI6"), No.caseSensitive))); |
| assert(lastIndexOfNeither(to!S("abbadeafsb"), to!T("fSb"), |
| No.caseSensitive) == 6, to!string(lastIndexOfNeither( |
| to!S("abbadeafsb"), to!T("fSb"), No.caseSensitive))); |
| assert(lastIndexOfNeither(to!S("defbi"), to!T("FBI"), |
| No.caseSensitive) == 1); |
| assert(lastIndexOfNeither(to!S("dfefffg"), to!T("NSA"), |
| No.caseSensitive) == 6); |
| assert(lastIndexOfNeither(to!S("dfeffgfffö"), to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"), |
| No.caseSensitive) == 8, to!string(lastIndexOfNeither(to!S("dfeffgfffö"), |
| to!T("BNDabCHIJKQEPÖÖSYXÄ??ß"), No.caseSensitive))); |
| }(); |
| } |
| } |
| ); |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| import std.exception : assertCTFEable; |
| |
| assertCTFEable!( |
| { |
| foreach (S; AliasSeq!(string, wstring, dstring)) |
| { |
| foreach (T; AliasSeq!(string, wstring, dstring)) |
| (){ // avoid slow optimizations for large functions @@@BUG@@@ 2396 |
| assert(lastIndexOfNeither(cast(S) null, to!T("a"), 1337) == -1); |
| assert(lastIndexOfNeither(to!S("def"), to!T("f")) == 1); |
| assert(lastIndexOfNeither(to!S("dfefffg"), to!T("fgh")) == 2); |
| |
| ptrdiff_t oeIdx = 4; |
| if (is(S == string)) |
| { |
| oeIdx = 5; |
| } |
| |
| auto foundOeIdx = lastIndexOfNeither(to!S("ödfefegff"), to!T("zeg"), |
| 7); |
| assert(foundOeIdx == oeIdx, to!string(foundOeIdx)); |
| |
| assert(lastIndexOfNeither(to!S("zfeffgfsb"), to!T("FSB"), 6, |
| No.caseSensitive) == 5); |
| assert(lastIndexOfNeither(to!S("def"), to!T("MI6"), 2, |
| No.caseSensitive) == 1, to!string(lastIndexOfNeither(to!S("def"), |
| to!T("MI6"), 2, No.caseSensitive))); |
| assert(lastIndexOfNeither(to!S("abbadeafsb"), to!T("fSb"), 6, |
| No.caseSensitive) == 5, to!string(lastIndexOfNeither( |
| to!S("abbadeafsb"), to!T("fSb"), 6, No.caseSensitive))); |
| assert(lastIndexOfNeither(to!S("defbi"), to!T("FBI"), 3, |
| No.caseSensitive) == 1); |
| assert(lastIndexOfNeither(to!S("dfefffg"), to!T("NSA"), 2, |
| No.caseSensitive) == 1, to!string(lastIndexOfNeither( |
| to!S("dfefffg"), to!T("NSA"), 2, No.caseSensitive))); |
| }(); |
| } |
| } |
| ); |
| } |
| |
| /** |
| * Returns the _representation of a string, which has the same type |
| * as the string except the character type is replaced by $(D ubyte), |
| * $(D ushort), or $(D uint) depending on the character width. |
| * |
| * Params: |
| * s = The string to return the _representation of. |
| * |
| * Returns: |
| * The _representation of the passed string. |
| */ |
| auto representation(Char)(Char[] s) @safe pure nothrow @nogc |
| if (isSomeChar!Char) |
| { |
| import std.traits : ModifyTypePreservingTQ; |
| alias ToRepType(T) = AliasSeq!(ubyte, ushort, uint)[T.sizeof / 2]; |
| return cast(ModifyTypePreservingTQ!(ToRepType, Char)[])s; |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| string s = "hello"; |
| static assert(is(typeof(representation(s)) == immutable(ubyte)[])); |
| assert(representation(s) is cast(immutable(ubyte)[]) s); |
| assert(representation(s) == [0x68, 0x65, 0x6c, 0x6c, 0x6f]); |
| } |
| |
| @system pure unittest |
| { |
| import std.exception : assertCTFEable; |
| import std.traits : Fields; |
| import std.typecons : Tuple; |
| |
| assertCTFEable!( |
| { |
| void test(Char, T)(Char[] str) |
| { |
| static assert(is(typeof(representation(str)) == T[])); |
| assert(representation(str) is cast(T[]) str); |
| } |
| |
| foreach (Type; AliasSeq!(Tuple!(char , ubyte ), |
| Tuple!(wchar, ushort), |
| Tuple!(dchar, uint ))) |
| { |
| alias Char = Fields!Type[0]; |
| alias Int = Fields!Type[1]; |
| enum immutable(Char)[] hello = "hello"; |
| |
| test!( immutable Char, immutable Int)(hello); |
| test!( const Char, const Int)(hello); |
| test!( Char, Int)(hello.dup); |
| test!( shared Char, shared Int)(cast(shared) hello.dup); |
| test!(const shared Char, const shared Int)(hello); |
| } |
| }); |
| } |
| |
| |
| /** |
| * Capitalize the first character of $(D s) and convert the rest of $(D s) to |
| * lowercase. |
| * |
| * Params: |
| * input = The string to _capitalize. |
| * |
| * Returns: |
| * The capitalized string. |
| * |
| * See_Also: |
| * $(REF asCapitalized, std,uni) for a lazy range version that doesn't allocate memory |
| */ |
| S capitalize(S)(S input) @trusted pure |
| if (isSomeString!S) |
| { |
| import std.array : array; |
| import std.uni : asCapitalized; |
| import std.utf : byUTF; |
| |
| return input.asCapitalized.byUTF!(ElementEncodingType!(S)).array; |
| } |
| |
| /// |
| pure @safe unittest |
| { |
| assert(capitalize("hello") == "Hello"); |
| assert(capitalize("World") == "World"); |
| } |
| |
| auto capitalize(S)(auto ref S s) |
| if (!isSomeString!S && is(StringTypeOf!S)) |
| { |
| return capitalize!(StringTypeOf!S)(s); |
| } |
| |
| @safe pure unittest |
| { |
| assert(testAliasedString!capitalize("hello")); |
| } |
| |
| @safe pure unittest |
| { |
| import std.algorithm.comparison : cmp; |
| import std.conv : to; |
| import std.exception : assertCTFEable; |
| |
| assertCTFEable!( |
| { |
| foreach (S; AliasSeq!(string, wstring, dstring, char[], wchar[], dchar[])) |
| { |
| S s1 = to!S("FoL"); |
| S s2; |
| |
| s2 = capitalize(s1); |
| assert(cmp(s2, "Fol") == 0); |
| assert(s2 !is s1); |
| |
| s2 = capitalize(s1[0 .. 2]); |
| assert(cmp(s2, "Fo") == 0); |
| |
| s1 = to!S("fOl"); |
| s2 = capitalize(s1); |
| assert(cmp(s2, "Fol") == 0); |
| assert(s2 !is s1); |
| s1 = to!S("\u0131 \u0130"); |
| s2 = capitalize(s1); |
| assert(cmp(s2, "\u0049 i\u0307") == 0); |
| assert(s2 !is s1); |
| |
| s1 = to!S("\u017F \u0049"); |
| s2 = capitalize(s1); |
| assert(cmp(s2, "\u0053 \u0069") == 0); |
| assert(s2 !is s1); |
| } |
| }); |
| } |
| |
| /++ |
| Split $(D s) into an array of lines according to the unicode standard using |
| $(D '\r'), $(D '\n'), $(D "\r\n"), $(REF lineSep, std,uni), |
| $(REF paraSep, std,uni), $(D U+0085) (NEL), $(D '\v') and $(D '\f') |
| as delimiters. If $(D keepTerm) is set to $(D KeepTerminator.yes), then the |
| delimiter is included in the strings returned. |
| |
| Does not throw on invalid UTF; such is simply passed unchanged |
| to the output. |
| |
| Allocates memory; use $(LREF lineSplitter) for an alternative that |
| does not. |
| |
| Adheres to $(HTTP www.unicode.org/versions/Unicode7.0.0/ch05.pdf, Unicode 7.0). |
| |
| Params: |
| s = a string of $(D chars), $(D wchars), or $(D dchars), or any custom |
| type that casts to a $(D string) type |
| keepTerm = whether delimiter is included or not in the results |
| Returns: |
| array of strings, each element is a line that is a slice of $(D s) |
| See_Also: |
| $(LREF lineSplitter) |
| $(REF splitter, std,algorithm) |
| $(REF splitter, std,regex) |
| +/ |
| alias KeepTerminator = Flag!"keepTerminator"; |
| |
| /// ditto |
| S[] splitLines(S)(S s, in KeepTerminator keepTerm = No.keepTerminator) @safe pure |
| if (isSomeString!S) |
| { |
| import std.array : appender; |
| import std.uni : lineSep, paraSep; |
| |
| size_t iStart = 0; |
| auto retval = appender!(S[])(); |
| |
| for (size_t i; i < s.length; ++i) |
| { |
| switch (s[i]) |
| { |
| case '\v', '\f', '\n': |
| retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator)]); |
| iStart = i + 1; |
| break; |
| |
| case '\r': |
| if (i + 1 < s.length && s[i + 1] == '\n') |
| { |
| retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator) * 2]); |
| iStart = i + 2; |
| ++i; |
| } |
| else |
| { |
| goto case '\n'; |
| } |
| break; |
| |
| static if (s[i].sizeof == 1) |
| { |
| /* Manually decode: |
| * lineSep is E2 80 A8 |
| * paraSep is E2 80 A9 |
| */ |
| case 0xE2: |
| if (i + 2 < s.length && |
| s[i + 1] == 0x80 && |
| (s[i + 2] == 0xA8 || s[i + 2] == 0xA9) |
| ) |
| { |
| retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator) * 3]); |
| iStart = i + 3; |
| i += 2; |
| } |
| else |
| goto default; |
| break; |
| /* Manually decode: |
| * NEL is C2 85 |
| */ |
| case 0xC2: |
| if (i + 1 < s.length && s[i + 1] == 0x85) |
| { |
| retval.put(s[iStart .. i + (keepTerm == Yes.keepTerminator) * 2]); |
| iStart = i + 2; |
| i += 1; |
| } |
| else |
| goto default; |
| break; |
| } |
| else |
| { |
| case lineSep: |
| case paraSep: |
| case '\u0085': |
| goto case '\n'; |
| } |
| |
| default: |
| break; |
| } |
| } |
| |
| if (iStart != s.length) |
| retval.put(s[iStart .. $]); |
| |
| return retval.data; |
| } |
| |
| /// |
| @safe pure nothrow unittest |
| { |
| string s = "Hello\nmy\rname\nis"; |
| assert(splitLines(s) == ["Hello", "my", "name", "is"]); |
| } |
| |
| @safe pure nothrow unittest |
| { |
| string s = "a\xC2\x86b"; |
| assert(splitLines(s) == [s]); |
| } |
| |
| auto splitLines(S)(auto ref S s, in KeepTerminator keepTerm = No.keepTerminator) |
| if (!isSomeString!S && is(StringTypeOf!S)) |
| { |
| return splitLines!(StringTypeOf!S)(s, keepTerm); |
| } |
| |
| @safe pure nothrow unittest |
| { |
| assert(testAliasedString!splitLines("hello\nworld")); |
| } |
| |
| @safe pure unittest |
| { |
| import std.conv : to; |
| import std.exception : assertCTFEable; |
| |
| assertCTFEable!( |
| { |
| foreach (S; AliasSeq!(char[], wchar[], dchar[], string, wstring, dstring)) |
| { |
| auto s = to!S( |
| "\rpeter\n\rpaul\r\njerry\u2028ice\u2029cream\n\nsunday\n" ~ |
| "mon\u2030day\nschadenfreude\vkindergarten\f\vcookies\u0085" |
| ); |
| auto lines = splitLines(s); |
| assert(lines.length == 14); |
| assert(lines[0] == ""); |
| assert(lines[1] == "peter"); |
| assert(lines[2] == ""); |
| assert(lines[3] == "paul"); |
| assert(lines[4] == "jerry"); |
| assert(lines[5] == "ice"); |
| assert(lines[6] == "cream"); |
| assert(lines[7] == ""); |
| assert(lines[8] == "sunday"); |
| assert(lines[9] == "mon\u2030day"); |
| assert(lines[10] == "schadenfreude"); |
| assert(lines[11] == "kindergarten"); |
| assert(lines[12] == ""); |
| assert(lines[13] == "cookies"); |
| |
| |
| ubyte[] u = ['a', 0xFF, 0x12, 'b']; // invalid UTF |
| auto ulines = splitLines(cast(char[]) u); |
| assert(cast(ubyte[])(ulines[0]) == u); |
| |
| lines = splitLines(s, Yes.keepTerminator); |
| assert(lines.length == 14); |
| assert(lines[0] == "\r"); |
| assert(lines[1] == "peter\n"); |
| assert(lines[2] == "\r"); |
| assert(lines[3] == "paul\r\n"); |
| assert(lines[4] == "jerry\u2028"); |
| assert(lines[5] == "ice\u2029"); |
| assert(lines[6] == "cream\n"); |
| assert(lines[7] == "\n"); |
| assert(lines[8] == "sunday\n"); |
| assert(lines[9] == "mon\u2030day\n"); |
| assert(lines[10] == "schadenfreude\v"); |
| assert(lines[11] == "kindergarten\f"); |
| assert(lines[12] == "\v"); |
| assert(lines[13] == "cookies\u0085"); |
| |
| s.popBack(); // Lop-off trailing \n |
| lines = splitLines(s); |
| assert(lines.length == 14); |
| assert(lines[9] == "mon\u2030day"); |
| |
| lines = splitLines(s, Yes.keepTerminator); |
| assert(lines.length == 14); |
| assert(lines[13] == "cookies"); |
| } |
| }); |
| } |
| |
| private struct LineSplitter(KeepTerminator keepTerm = No.keepTerminator, Range) |
| { |
| import std.conv : unsigned; |
| import std.uni : lineSep, paraSep; |
| private: |
| Range _input; |
| |
| alias IndexType = typeof(unsigned(_input.length)); |
| enum IndexType _unComputed = IndexType.max; |
| IndexType iStart = _unComputed; |
| IndexType iEnd = 0; |
| IndexType iNext = 0; |
| |
| public: |
| this(Range input) |
| { |
| _input = input; |
| } |
| |
| static if (isInfinite!Range) |
| { |
| enum bool empty = false; |
| } |
| else |
| { |
| @property bool empty() |
| { |
| return iStart == _unComputed && iNext == _input.length; |
| } |
| } |
| |
| @property typeof(_input) front() |
| { |
| if (iStart == _unComputed) |
| { |
| iStart = iNext; |
| Loop: |
| for (IndexType i = iNext; ; ++i) |
| { |
| if (i |