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