1 /** Extensions to skipOver. 2 * 3 * See_Also: https://forum.dlang.org/post/tswdobtabsjarszfkmbt@forum.dlang.org 4 * See_Also: https://forum.dlang.org/post/ybamybeakxwxwleebnwb@forum.dlang.org 5 */ 6 module nxt.skip_ex; 7 8 import std.functional : binaryFun; 9 import std.range.primitives : front, back, save, empty, popBack, hasSlicing, isBidirectionalRange, ElementType; 10 11 version (unittest) 12 { 13 import nxt.array_help : s; 14 } 15 16 /** Skip over the ending portion of `haystack` that matches `needle`, or nothing upon no match. 17 * 18 * See_Also: std.algorithm.searching.skipOver. 19 */ 20 bool skipOverBack(Haystack, Needle)(scope ref Haystack haystack, 21 scope Needle needle) // non`const` because may be range with mutable range primitives 22 if (isBidirectionalRange!Haystack && 23 isBidirectionalRange!Needle && 24 is(typeof(haystack.back == needle.back))) 25 { 26 static if (is(typeof(haystack[] == needle) : bool) && 27 is(typeof(needle.length > haystack.length) : bool) && 28 is(typeof(haystack = haystack[]))) 29 { 30 if (haystack.length >= needle.length && 31 haystack[$ - needle.length .. $] == needle) 32 { 33 haystack = haystack[0 .. haystack.length - needle.length]; 34 return true; 35 } 36 return false; 37 } 38 else 39 { 40 return skipOverBack!((a, b) => a == b)(haystack, needle); 41 } 42 } 43 44 /// 45 bool skipOverBack(alias pred, Haystack, Needle)(scope ref Haystack haystack, 46 scope Needle needle) // non`const` because may be range with mutable range primitives 47 if (isBidirectionalRange!Haystack && 48 isBidirectionalRange!Needle && 49 is(typeof(binaryFun!pred(haystack.back, needle.back)))) /+ TODO: Needle doesn't have to bi-directional if Haystack is RandomAccess and Needle.hasLength +/ 50 { 51 import std.range.primitives : hasLength; 52 static if (hasLength!Haystack && hasLength!Needle) 53 { 54 if (haystack.length < needle.length) { return false; } // fast discardal 55 } 56 auto r = haystack.save; 57 while (!needle.empty && 58 !r.empty && 59 binaryFun!pred(r.back, needle.back)) 60 { 61 r.popBack(); 62 needle.popBack(); 63 } 64 if (needle.empty) 65 { 66 haystack = r; 67 } 68 return needle.empty; 69 } 70 71 /// 72 pure nothrow @safe @nogc unittest { 73 auto s1_ = [1, 2, 3].s; 74 auto s1 = s1_[]; 75 const s2_ = [2, 3].s; 76 const s2 = s2_[]; 77 s1.skipOverBack(s2); 78 assert(s1 == [1].s); 79 s1.skipOverBack(s2); // no effect 80 assert(s1 == [1].s); 81 } 82 83 pure nothrow @safe @nogc unittest { 84 import std.algorithm : equal; 85 auto s1 = "Hello world"; 86 assert(!skipOverBack(s1, "Ha")); 87 assert(s1 == "Hello world"); 88 assert(skipOverBack(s1, "world") && s1 == "Hello "); 89 } 90 91 /** Variadic version of $(D skipOver). 92 * 93 * Returns: index + 1 into matching $(D needles), 0 otherwise. 94 * 95 * TODO: Reuse `skipOver with many needles` or write own array-version of `skipOver` that's faster. 96 */ 97 size_t skipOverAmong(alias pred = "a == b", Range, Ranges...)(scope ref Range haystack, 98 scope Ranges needles) 99 if (Ranges.length >= 2) 100 { 101 import nxt.array_traits : isSameSlices; 102 foreach (const index, const ref needle; needles) 103 { 104 static if (pred == "a == b" && 105 isSameSlices!(Range, Ranges)) // fast 106 { 107 // `nothrow` char[] fast path 108 if (haystack.length >= needle.length && 109 haystack[0 .. needle.length] == needle) /+ TODO: `haystack.ptr` +/ 110 { 111 haystack = haystack[needle.length .. haystack.length]; /+ TODO: `haystack.ptr` +/ 112 return index + 1; 113 } 114 } 115 else 116 { 117 import std.algorithm.searching : skipOver; 118 if (haystack.skipOver(needle)) /+ TODO: nothrow +/ 119 return index + 1; 120 } 121 } 122 return 0; 123 } 124 125 @safe pure nothrow /* TODO: nothrow @nogc */ unittest { 126 import std.algorithm.searching : startsWith; 127 auto x = "beta version"; 128 assert(x.startsWith("beta")); 129 } 130 131 @safe pure /* TODO: nothrow @nogc */ unittest { 132 import std.algorithm.searching : skipOver; 133 auto x = "beta version"; 134 assert(x.skipOver("beta")); 135 assert(x == " version"); 136 } 137 138 pure nothrow @safe @nogc unittest { 139 auto x = "beta version"; 140 assert(x.skipOverAmong("beta", "be") == 1); 141 assert(x == " version"); 142 } 143 144 pure nothrow @safe @nogc unittest { 145 auto x = "beta version"; 146 assert(x.skipOverAmong("be", "_") == 1); 147 } 148 149 pure nothrow @safe @nogc unittest { 150 auto x = "beta version"; 151 assert(x.skipOverAmong("x", "y") == 0); 152 } 153 154 pure nothrow @safe @nogc unittest { 155 156 auto x = "beta version"; 157 assert(x.skipOverAmong("x", "y") == 0); 158 } 159 160 /** Skip Over Shortest Matching prefix in $(D needles) that prefixes $(D haystack). 161 * 162 * TODO: Make return value a specific type that has bool conversion so we can 163 * call it as 164 * if (auto hit = r.skipOverShortestOf(...)) { ... } 165 */ 166 size_t skipOverShortestOf(alias pred = "a == b", 167 Range, 168 Ranges...)(scope ref Range haystack, 169 scope Ranges needles) 170 if (Ranges.length >= 2) 171 { 172 import std.algorithm.searching : startsWith; 173 const hit = startsWith!pred(haystack, needles); 174 if (hit) 175 { 176 // get needle lengths 177 size_t[needles.length] lengths; 178 foreach (const index, ref needle; needles) 179 { 180 import std.traits : isSomeString, isSomeChar; 181 import std.range.primitives : ElementType; 182 import core.internal.traits : Unqual; 183 184 alias Needle = Unqual!(typeof(needle)); 185 186 static if (is(Unqual!Range == 187 Needle)) 188 { 189 lengths[index] = needle.length; 190 } 191 else static if (is(Unqual!(ElementType!Range) == 192 Unqual!(ElementType!Needle))) 193 { 194 lengths[index] = needle.length; 195 } 196 else static if (isSomeString!Range && 197 isSomeString!Needle) 198 { 199 lengths[index] = needle.length; 200 } 201 else static if (isSomeChar!(ElementType!Range) && 202 isSomeChar!Needle) 203 { 204 lengths[index] = 1; 205 } 206 else static if (is(Unqual!(ElementType!Range) == 207 Needle)) 208 { 209 lengths[index] = 1; 210 } 211 else 212 { 213 static assert(0, 214 "Cannot handle needle of type " ~ Needle.stringof ~ 215 " when haystack has ElementType " ~ (ElementType!Range).stringof); 216 } 217 } 218 219 import std.range: popFrontN; 220 haystack.popFrontN(lengths[hit - 1]); 221 } 222 223 return hit; 224 225 } 226 227 pure @safe unittest { 228 auto x = "beta version"; 229 assert(x.skipOverShortestOf("beta", "be") == 2); 230 assert(x == "ta version"); 231 } 232 233 pure @safe unittest { 234 auto x = "beta version"; 235 assert(x.skipOverShortestOf("be", "beta") == 1); 236 assert(x == "ta version"); 237 } 238 239 pure @safe unittest { 240 auto x = "beta version"; 241 assert(x.skipOverShortestOf('b', "be", "beta") == 1); 242 assert(x == "eta version"); 243 } 244 245 /** Skip Over Longest Matching prefix in $(D needles) that prefixes $(D haystack). 246 */ 247 SkipOverLongest skipOverLongestOf(alias pred = "a == b", Range, Ranges...)(scope ref Range haystack, 248 scope Ranges needles) 249 { 250 /+ TODO: figure out which needles that are prefixes of other needles by first +/ 251 // sorting them and then use some adjacent filtering algorithm 252 static assert(0, "TODO: implement"); 253 } 254 255 /** Skip Over Back Shortest Match of `needles` in `haystack`. */ 256 size_t skipOverBackShortestOf(alias pred = "a == b", Range, Ranges...)(scope ref Range haystack, 257 scope Ranges needles) @trusted 258 /+ TODO: We cannot prove that cast(ubyte[]) of a type that have no directions is safe +/ 259 if (Ranges.length >= 2) 260 { 261 import std.range: retro, ElementType; 262 import std.traits: hasIndirections; 263 import core.internal.traits : Unqual; 264 import std.meta: staticMap, AliasSeq; 265 // import nxt.traits_ex: allSame; 266 267 static if ((!hasIndirections!(ElementType!Range))/* && */ 268 /* allSame!(Unqual!Range, staticMap!(Unqual, Ranges)) */) 269 { 270 auto retroHaystack = (cast(ubyte[])haystack).retro; 271 272 alias Retro(Range) = typeof((ubyte[]).init.retro); 273 AliasSeq!(staticMap!(Retro, Ranges)) retroNeedles; 274 foreach (const index, const ref needle; needles) 275 { 276 retroNeedles[index] = (cast(ubyte[])needle).retro; 277 } 278 279 const retroHit = retroHaystack.skipOverShortestOf(retroNeedles); 280 haystack = haystack[0.. $ - (haystack.length - retroHaystack.length)]; 281 282 return retroHit; 283 } 284 else 285 { 286 static assert(0, "Unsupported combination of haystack type " ~ Range.stringof ~ 287 " with needle types " ~ Ranges.stringof); 288 } 289 } 290 291 pure nothrow @safe @nogc unittest { 292 auto x = "alpha_beta"; 293 assert(x.skipOverBackShortestOf("x", "beta") == 2); 294 assert(x == "alpha_"); 295 } 296 297 pure nothrow @safe @nogc unittest { 298 auto x = "alpha_beta"; 299 assert(x.skipOverBackShortestOf("a", "beta") == 1); 300 assert(x == "alpha_bet"); 301 } 302 303 /** Drop $(D prefixes) in $(D s). 304 * 305 * TODO: Use multi-argument skipOver when it becomes available 306 * http://forum.dlang.org/thread/bug-12335-3@https.d.puremagic.com%2Fissues%2F 307 */ 308 void skipOverPrefixes(R, A)(scope ref R s, 309 const scope A prefixes) 310 { 311 import std.algorithm.searching : skipOver; 312 foreach (prefix; prefixes) 313 { 314 if (s.length > prefix.length && 315 s.skipOver(prefix)) 316 { 317 break; 318 } 319 } 320 } 321 322 /** Drop $(D suffixes) in $(D s). 323 */ 324 void skipOverSuffixes(R, A)(scope ref R s, 325 const scope A suffixes) 326 { 327 foreach (suffix; suffixes) 328 { 329 if (s.length > suffix.length && 330 s.endsWith(suffix)) 331 { 332 s = s[0 .. $ - suffix.length]; /+ TODO: .ptr +/ 333 break; 334 } 335 } 336 } 337 338 /** Drop either both prefix `frontPrefix` and suffix `backSuffix` or do nothing. 339 * 340 * Returns: `true` upon drop, `false` otherwise. 341 */ 342 bool skipOverFrontAndBack(alias pred = "a == b", R, E, F)(scope ref R r, 343 scope E frontPrefix, 344 scope F backSuffix) 345 if (isBidirectionalRange!R && 346 is(typeof(binaryFun!pred(ElementType!R.init, E.init))) && 347 is(typeof(binaryFun!pred(ElementType!R.init, F.init)))) 348 { 349 import core.internal.traits : Unqual; 350 import std.traits : isArray; 351 static if (isArray!R && 352 is(Unqual!(typeof(R.init[0])) == E)) // for instance if `R` is `string` and `E` is `char` 353 { 354 if (r.length >= 2 && 355 r[0] == frontPrefix && 356 r[$ - 1] == backSuffix) 357 { 358 r = r[1 .. $ - 1]; 359 return true; 360 } 361 } 362 else 363 { 364 if (r.length >= 2 && /+ TODO: express this requirement in `r` as `hasLength` +/ 365 binaryFun!pred(r.front, frontPrefix) && 366 binaryFun!pred(r.back, backSuffix)) 367 { 368 import std.range.primitives : popBack, popFront; 369 r.popFront(); 370 r.popBack(); 371 return true; 372 } 373 } 374 return false; 375 } 376 377 pure nothrow @safe @nogc unittest { 378 auto expr = `"alpha"`; 379 assert(expr.skipOverFrontAndBack('"', '"')); 380 assert(expr == `alpha`); 381 } 382 383 pure nothrow @safe @nogc unittest { 384 auto expr_ = `"alpha"`; 385 auto expr = expr_; 386 assert(!expr.skipOverFrontAndBack(',', '"')); 387 assert(expr == expr_); 388 } 389 390 pure nothrow @safe @nogc unittest { 391 auto expr_ = `"alpha`; 392 auto expr = expr_; 393 assert(!expr.skipOverFrontAndBack('"', '"')); 394 assert(expr == expr_); 395 } 396 397 pure nothrow @safe @nogc unittest { 398 auto expr_ = `alpha"`; 399 auto expr = expr_; 400 assert(!expr.skipOverFrontAndBack('"', '"')); 401 assert(expr == expr_); 402 }