1 #!/usr/bin/env rdmd-dev-module 2 3 module skip_ex; 4 5 import std.functional : binaryFun; 6 import std.range: back, save, empty, popBack, hasSlicing; 7 import std.algorithm : skipOver; 8 9 // TODO Add variadic (static and dynamic) versions of "(starts|ends)With(Either)?" 10 11 alias skipFronts = skipOver; 12 13 /** 14 If $(D startsWith(r1, r2)), consume the corresponding elements off $(D 15 r1) and return $(D true). Otherwise, leave $(D r1) unchanged and 16 return $(D false). 17 */ 18 bool skipOverBack(alias pred = "a == b", R1, R2)(ref R1 r1, R2 r2) 19 if (is(typeof(binaryFun!pred(r1.back, r2.back)))) 20 { 21 auto r = r1.save; 22 while (!r2.empty && !r.empty && binaryFun!pred(r.back, r2.back)) 23 { 24 r.popBack(); 25 r2.popBack(); 26 } 27 if (r2.empty) 28 r1 = r; 29 return r2.empty; 30 } 31 alias skipBacks = skipOverBack; 32 33 @safe pure unittest 34 { 35 import std.algorithm: equal; 36 37 auto s1 = "Hello world"; 38 assert(!skipOverBack(s1, "Ha")); 39 assert(s1 == "Hello world"); 40 assert(skipOverBack(s1, "world") && s1 == "Hello "); 41 } 42 43 import std.typecons: tuple, Tuple; 44 45 import std.algorithm: startsWith; 46 47 /** Variadic Version of $(D skipOver). 48 Returns: index + 1 into matching $(D needles), 0 otherwise. 49 */ 50 size_t skipOverEither(alias pred = "a == b", Range, Ranges...)(ref Range haystack, 51 Ranges needles) 52 if (Ranges.length >= 2 && 53 is(typeof(startsWith!pred(haystack, needles)))) 54 { 55 import std.algorithm : skipOver; 56 foreach (const ix, needle; needles) 57 if (haystack.skipOver(needle)) 58 return ix + 1; 59 return 0; 60 } 61 62 @safe pure nothrow @nogc unittest 63 { 64 auto x = "beta version"; 65 assert(x.skipOverEither("beta", "be") == 1); 66 assert(x == " version"); 67 } 68 69 /** Skip Over Shortest Matching prefix in $(D needles) that prefixes $(D haystack). 70 TODO Make return value a specific type that has bool conversion so we can 71 call it as 72 if (auto hit = r.skipOverShortestOf(...)) { ... } 73 */ 74 size_t skipOverShortestOf(alias pred = "a == b", 75 Range, 76 Ranges...)(ref Range haystack, 77 Ranges needles) 78 if (Ranges.length >= 2 && 79 is(typeof(startsWith!pred(haystack, needles)))) 80 { 81 const hit = startsWith!pred(haystack, needles); 82 if (hit) 83 { 84 // get needle lengths 85 size_t[needles.length] lengths; 86 foreach (ix, needle; needles) 87 { 88 import std.traits: isSomeString, isSomeChar; 89 import std.range: ElementType; 90 import std.typecons: Unqual; 91 92 alias Needle = Unqual!(typeof(needle)); 93 94 static if (is(Unqual!Range == 95 Needle)) 96 { 97 lengths[ix] = needle.length; 98 } 99 else static if (is(Unqual!(ElementType!Range) == 100 Unqual!(ElementType!Needle))) 101 { 102 lengths[ix] = needle.length; 103 } 104 else static if (isSomeString!Range && 105 isSomeString!Needle) 106 { 107 lengths[ix] = needle.length; 108 } 109 else static if (isSomeChar!(ElementType!Range) && 110 isSomeChar!Needle) 111 { 112 lengths[ix] = 1; 113 } 114 else static if (is(Unqual!(ElementType!Range) == 115 Needle)) 116 { 117 lengths[ix] = 1; 118 } 119 else 120 { 121 static assert(false, 122 "Cannot handle needle of type " ~ Needle.stringof ~ 123 " when haystack has ElementType " ~ (ElementType!Range).stringof); 124 } 125 } 126 127 import std.range: popFrontN; 128 haystack.popFrontN(lengths[hit - 1]); 129 } 130 131 return hit; 132 133 } 134 135 @safe pure unittest 136 { 137 auto x = "beta version"; 138 assert(x.skipOverShortestOf("beta", "be") == 2); 139 assert(x == "ta version"); 140 } 141 142 @safe pure unittest 143 { 144 auto x = "beta version"; 145 assert(x.skipOverShortestOf("be", "beta") == 1); 146 assert(x == "ta version"); 147 } 148 149 @safe pure unittest 150 { 151 auto x = "beta version"; 152 assert(x.skipOverShortestOf('b', "be", "beta") == 1); 153 assert(x == "eta version"); 154 } 155 156 /** Skip Over Longest Matching prefix in $(D needles) that prefixes $(D haystack). */ 157 Tuple!(bool, size_t) skipOverLongestOf(alias pred = "a == b", Range, Ranges...)(ref Range haystack, Ranges needles) 158 { 159 // TODO figure out which needles that are prefixes of other needles by first 160 // sorting them and then use some adjacent filtering algorithm 161 return haystack.skipOverShortestOf(needles); 162 } 163 164 size_t skipOverBackShortestOf(alias pred = "a == b", Range, Ranges...)(ref Range haystack, Ranges needles) 165 // TODO We cannot prove that cast(ubyte[]) of a type that have no directions is safe 166 @trusted 167 if (Ranges.length >= 2 && 168 is(typeof(startsWith!pred(haystack, needles)))) 169 { 170 import std.range: retro, ElementType; 171 import std.traits: hasIndirections; 172 import std.typecons: Unqual; 173 import std.meta: staticMap, AliasSeq; 174 // import traits_ex: allSameType; 175 176 static if ((!hasIndirections!(ElementType!Range))/* && */ 177 /* allSameType!(Unqual!Range, staticMap!(Unqual, Ranges)) */) 178 { 179 auto retroHaystack = (cast(ubyte[])haystack).retro; 180 181 alias Retro(Range) = typeof((ubyte[]).init.retro); 182 AliasSeq!(staticMap!(Retro, Ranges)) retroNeedles; 183 foreach (ix, needle; needles) 184 { 185 retroNeedles[ix] = (cast(ubyte[])needle).retro; 186 } 187 188 const retroHit = retroHaystack.skipOverShortestOf(retroNeedles); 189 haystack = haystack[0.. $ - (haystack.length - retroHaystack.length)]; 190 191 return retroHit; 192 } 193 else 194 { 195 static assert(false, "Unsupported combination of haystack type " ~ Range.stringof ~ 196 " with needle types " ~ Ranges.stringof); 197 } 198 } 199 200 @safe pure nothrow @nogc unittest 201 { 202 auto x = "alpha_beta"; 203 assert(x.skipOverBackShortestOf("x", "beta") == 2); 204 assert(x == "alpha_"); 205 } 206 207 @safe pure nothrow @nogc unittest 208 { 209 auto x = "alpha_beta"; 210 assert(x.skipOverBackShortestOf("a", "beta") == 1); 211 assert(x == "alpha_bet"); 212 } 213 214 /** Drop $(D prefixes) in $(D s). 215 TODO Use multi-argument skipOver when it becomes available http://forum.dlang.org/thread/bug-12335-3@https.d.puremagic.com%2Fissues%2F 216 */ 217 void skipOverPrefixes(R, A)(ref R s, in A prefixes) 218 { 219 foreach (prefix; prefixes) 220 { 221 if (s.length > prefix.length && 222 s.skipOver(prefix)) { break; } 223 } 224 } 225 226 /** Drop $(D suffixes) in $(D s). */ 227 void skipOverSuffixes(R, A)(ref R s, in A suffixes) 228 { 229 foreach (suffix; suffixes) 230 { 231 if (s.length > suffix.length && 232 s.endsWith(suffix)) { s = s[0 .. $ - suffix.length]; break; } 233 } 234 }