1 /** Array-only overloads of Phobos algorithms. 2 * 3 * Functions are when possible `@safe pure nothrow @nogc`. 4 * Haystack parameter is when possible and relevant `scope return inout(T)[]` and DIP-1000-compliant. 5 * Needle parameter is either `scope const(T)[]` or `scope const T[]`. 6 * 7 * Provides more than twice as fast compilation for `char`-arrays (`string`s). 8 * 9 * See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org 10 * See_Also: https://forum.dlang.org/thread/ybamybeakxwxwleebnwb@forum.dlang.org?page=1 11 * 12 * TODO Merge into separate array-specializations of Phobos algorithms for less template bloat in Phobos. 13 */ 14 module nxt.array_algorithm; 15 16 // version = unittestAsBetterC; // Run_As: dmd -betterC -unittest -run $(__FILE__).d 17 18 /** Array-specialization of `startsWith` with default predicate. 19 * 20 * See_Also: https://d.godbolt.org/z/ejEmrK 21 */ 22 bool startsWith(T)(scope const T[] haystack, 23 scope const T[] needle) @trusted 24 { 25 if (haystack.length < needle.length) { return false; } 26 return haystack.ptr[0 .. needle.length] == needle; 27 } 28 /// ditto 29 bool startsWith(T)(scope const T[] haystack, 30 scope const T needle) @trusted 31 { 32 static if (is(T == char)) { assert(needle < 128); } // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org 33 if (haystack.length == 0) { return false; } 34 return haystack.ptr[0] == needle; 35 } 36 37 /// 38 @safe pure nothrow @nogc unittest 39 { 40 auto x = "beta version"; 41 assert(x.startsWith("beta")); 42 assert(x.startsWith('b')); 43 assert(!x.startsWith("_")); 44 } 45 46 /** Array-specialization of `all` with element needle. */ 47 bool all(T)(scope const T[] haystack, 48 scope const T needle) @trusted 49 { 50 static if (is(T == char)) { assert(needle < 128); } // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org 51 foreach (const offset; 0 .. haystack.length) 52 { 53 if (haystack.ptr[offset] != needle) 54 { 55 return false; 56 } 57 } 58 return true; 59 } 60 61 /// 62 @safe pure nothrow @nogc unittest 63 { 64 assert("".all('a')); // matches behaviour of `std.algorithm.searching.any` 65 assert("aaa".all('a')); 66 assert(!"aa_".all('a')); 67 } 68 69 /** Array-specialization of `any` with element needle. */ 70 bool any(T)(scope const T[] haystack, 71 scope const T needle) @trusted 72 { 73 static if (is(T == char)) { assert(needle < 128); } // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org 74 foreach (const offset; 0 .. haystack.length) 75 { 76 if (haystack.ptr[offset] == needle) 77 { 78 return true; 79 } 80 } 81 return false; 82 } 83 84 /// 85 @safe pure nothrow @nogc unittest 86 { 87 assert(!"".any('a')); // matches behaviour of `std.algorithm.searching.any` 88 assert("aaa".any('a')); 89 assert("aa_".any('a')); 90 assert(!"_".any('a')); 91 } 92 93 /** Array-specialization of `endsWith` with default predicate. */ 94 bool endsWith(T)(scope const T[] haystack, 95 scope const T[] needle) @trusted 96 { 97 if (haystack.length < needle.length) { return false; } 98 return haystack.ptr[haystack.length - needle.length .. haystack.length] == needle; 99 } 100 /// ditto 101 bool endsWith(T)(scope const T[] haystack, 102 scope const T needle) @trusted 103 { 104 static if (is(T == char)) { assert(needle < 128); } // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org 105 if (haystack.length == 0) { return false; } 106 return haystack.ptr[haystack.length - 1] == needle; 107 } 108 109 /// 110 @safe pure nothrow @nogc unittest 111 { 112 auto x = "beta version"; 113 assert(x.endsWith("version")); 114 assert(x.endsWith('n')); 115 assert(!x.startsWith("_")); 116 } 117 118 /** Array-specialization of `findSkip` with default predicate. 119 */ 120 auto findSkip(T)(scope ref inout(T)[] haystack, 121 scope const T[] needle) @trusted 122 { 123 const index = haystack.indexOf(needle); 124 if (index != -1) 125 { 126 haystack = haystack.ptr[index + needle.length .. haystack.length]; 127 return true; 128 } 129 return false; 130 } 131 /// ditto 132 auto findSkip(T)(scope ref inout(T)[] haystack, 133 scope const T needle) @trusted 134 { 135 const index = haystack.indexOf(needle); 136 if (index != -1) 137 { 138 haystack = haystack.ptr[index + 1 .. haystack.length]; 139 return true; 140 } 141 return false; 142 } 143 144 /// 145 @safe pure nothrow @nogc unittest 146 { 147 const auto x = "abc"; 148 { 149 string y = x; 150 const bool ok = y.findSkip("_"); 151 assert(!ok); 152 assert(y is x); 153 } 154 { 155 string y = x; 156 const bool ok = y.findSkip("a"); 157 assert(ok); 158 assert(y == x[1 .. $]); 159 } 160 { 161 string y = x; 162 const bool ok = y.findSkip("c"); 163 assert(ok); 164 assert(y is x[$ .. $]); 165 } 166 auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSkip(" "); } 167 static if (isDIP1000) static assert(!__traits(compiles, { auto _ = f(); })); 168 } 169 170 /// 171 @safe pure nothrow @nogc unittest 172 { 173 const auto x = "abc"; 174 { 175 string y = x; 176 const bool ok = y.findSkip('_'); 177 assert(!ok); 178 assert(y is x); 179 } 180 { 181 string y = x; 182 const bool ok = y.findSkip('a'); 183 assert(ok); 184 assert(y == x[1 .. $]); 185 } 186 { 187 string y = x; 188 const bool ok = y.findSkip('c'); 189 assert(ok); 190 assert(y is x[$ .. $]); 191 } 192 auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSkip(' '); } 193 static if (isDIP1000) static assert(!__traits(compiles, { auto _ = f(); })); 194 } 195 196 /** Array-specialization of `findSkip` with default predicate that finds the last skip. 197 */ 198 auto findLastSkip(T)(scope ref inout(T)[] haystack, 199 scope const T[] needle) @trusted 200 { 201 const index = haystack.lastIndexOf(needle); 202 if (index != -1) 203 { 204 haystack = haystack.ptr[index + needle.length .. haystack.length]; 205 return true; 206 } 207 return false; 208 } 209 /// 210 auto findLastSkip(T)(scope ref inout(T)[] haystack, 211 scope const T needle) @trusted 212 { 213 const index = haystack.lastIndexOf(needle); 214 if (index != -1) 215 { 216 haystack = haystack.ptr[index + 1 .. haystack.length]; 217 return true; 218 } 219 return false; 220 } 221 222 /// 223 @safe pure nothrow @nogc unittest 224 { 225 const auto x = "abacc"; 226 { 227 string y = x; 228 const bool ok = y.findLastSkip("_"); 229 assert(!ok); 230 assert(y is x); 231 } 232 { 233 string y = x; 234 const bool ok = y.findLastSkip("a"); 235 assert(ok); 236 assert(y == x[3 .. $]); 237 } 238 { 239 string y = x; 240 const bool ok = y.findLastSkip("c"); 241 assert(ok); 242 assert(y is x[$ .. $]); 243 } 244 auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findLastSkip(" "); } 245 static if (isDIP1000) static assert(!__traits(compiles, { auto _ = f(); })); 246 } 247 248 /// 249 @safe pure nothrow @nogc unittest 250 { 251 const auto x = "abacc"; 252 { 253 string y = x; 254 const bool ok = y.findLastSkip('_'); 255 assert(!ok); 256 assert(y is x); 257 } 258 { 259 string y = x; 260 const bool ok = y.findLastSkip('a'); 261 assert(ok); 262 assert(y == x[3 .. $]); 263 } 264 { 265 string y = x; 266 const bool ok = y.findLastSkip('c'); 267 assert(ok); 268 assert(y is x[$ .. $]); 269 } 270 auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findLastSkip(' '); } 271 static if (isDIP1000) static assert(!__traits(compiles, { auto _ = f(); })); 272 } 273 274 /** Array-specialization of `skipOver` with default predicate. 275 * 276 * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org 277 */ 278 bool skipOver(T)(scope ref inout(T)[] haystack, 279 scope const T[] needle) @trusted 280 { 281 if (!startsWith(haystack, needle)) { return false; } 282 haystack = haystack.ptr[needle.length .. haystack.length]; 283 return true; 284 } 285 /// ditto 286 bool skipOver(T)(scope ref inout(T)[] haystack, 287 scope const T needle) @trusted 288 { 289 if (!startsWith(haystack, needle)) { return false; } 290 haystack = haystack.ptr[1 .. haystack.length]; 291 return true; 292 } 293 294 /// 295 @safe pure nothrow @nogc unittest 296 { 297 string x = "beta version"; 298 assert(x.skipOver("beta")); 299 assert(x == " version"); 300 assert(x.skipOver(' ')); 301 assert(x == "version"); 302 } 303 304 /// constness of haystack and needle 305 @safe pure nothrow @nogc unittest 306 { 307 { 308 const(char)[] haystack; 309 string needle; 310 assert(haystack.skipOver(needle)); 311 } 312 { 313 const(char)[] haystack; 314 const(char)[] needle; 315 assert(haystack.skipOver(needle)); 316 } 317 { 318 const(char)[] haystack; 319 char[] needle; 320 assert(haystack.skipOver(needle)); 321 } 322 } 323 324 /** Array-specialization of `skipOverBack` with default predicate. 325 * 326 * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org 327 */ 328 bool skipOverBack(T)(scope ref inout(T)[] haystack, 329 scope const T[] needle) @trusted 330 { 331 if (!endsWith(haystack, needle)) { return false; } 332 haystack = haystack.ptr[0 .. haystack.length - needle.length]; 333 return true; 334 } 335 /// ditto 336 bool skipOverBack(T)(scope ref inout(T)[] haystack, 337 scope const T needle) @trusted 338 { 339 if (!endsWith(haystack, needle)) { return false; } 340 haystack = haystack.ptr[0 .. haystack.length - 1]; 341 return true; 342 } 343 344 /// 345 @safe pure nothrow @nogc unittest 346 { 347 string x = "beta version"; 348 assert(x.skipOverBack(" version")); 349 assert(x == "beta"); 350 assert(x.skipOverBack('a')); 351 assert(x == "bet"); 352 } 353 354 /** Array-specialization of `stripLeft` with default predicate. 355 * 356 * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org 357 */ 358 inout(T)[] stripLeft(T)(scope return inout(T)[] haystack, 359 scope const T needle) @trusted 360 { 361 static if (is(T == char)) { assert(needle < 128); } // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org 362 size_t offset = 0; 363 while (offset != haystack.length && 364 haystack.ptr[offset] == needle) // TODO elide range-check 365 { 366 offset += 1; 367 } 368 return haystack.ptr[offset .. haystack.length]; 369 } 370 /// ditto 371 inout(char)[] stripLeft()(scope return inout(char)[] haystack) @safe pure nothrow @nogc // template-lazy 372 { 373 return haystack.stripLeft(' '); 374 } 375 376 /// 377 @safe pure nothrow @nogc unittest 378 { 379 assert("beta".stripLeft(' ') == "beta"); 380 assert(" beta".stripLeft(' ') == "beta"); 381 assert(" beta".stripLeft(' ') == "beta"); 382 assert(" beta".stripLeft(' ') == "beta"); 383 assert(" beta".stripLeft() == "beta"); 384 assert(" _ beta _ ".stripLeft(' ') == "_ beta _ "); 385 assert(" _ beta _ ".stripLeft(' ') == "_ beta _ "); 386 387 char[] f()() @safe pure nothrow { char[1] x = "_"; return x[].stripLeft(' '); } 388 static if (isDIP1000) static assert(!__traits(compiles, { auto _ = f(); })); 389 } 390 391 /** Array-specialization of `stripRight` with default predicate. 392 * 393 * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org 394 */ 395 inout(T)[] stripRight(T)(scope return inout(T)[] haystack, 396 scope const T needle) @trusted 397 { 398 static if (is(T == char)) { assert(needle < 128); } // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org 399 size_t offset = haystack.length; 400 while (offset != 0 && 401 haystack.ptr[offset - 1] == needle) // TODO elide range-check 402 { 403 offset -= 1; 404 } 405 return haystack.ptr[0 .. offset]; 406 } 407 /// ditto 408 inout(char)[] stripRight()(scope return inout(char)[] haystack) @safe pure nothrow @nogc // template-lazy 409 { 410 return haystack.stripRight(' '); 411 } 412 413 /// 414 @safe pure nothrow @nogc unittest 415 { 416 assert("beta".stripRight(' ') == "beta"); 417 assert("beta ".stripRight(' ') == "beta"); 418 assert("beta ".stripRight(' ') == "beta"); 419 assert("beta ".stripRight(' ') == "beta"); 420 assert("beta ".stripRight() == "beta"); 421 assert(" _ beta _ ".stripRight(' ') == " _ beta _"); 422 assert(" _ beta _ ".stripRight(' ') == " _ beta _"); 423 424 char[] f()() @safe pure nothrow { char[1] x = "_"; return x[].stripRight(' '); } 425 static if (isDIP1000) static assert(!__traits(compiles, { auto _ = f(); })); 426 } 427 428 /** Array-specialization of `strip` with default predicate. 429 * 430 * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org 431 */ 432 inout(T)[] strip(T)(scope return inout(T)[] haystack, 433 scope const T needle) @trusted 434 { 435 static if (is(T == char)) { assert(needle < 128); } // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org 436 437 size_t leftOffset = 0; 438 while (leftOffset != haystack.length && 439 haystack.ptr[leftOffset] == needle) // TODO elide range-check 440 { 441 leftOffset += 1; 442 } 443 444 size_t rightOffset = haystack.length; 445 while (rightOffset != leftOffset && 446 haystack.ptr[rightOffset - 1] == needle) // TODO elide range-check 447 { 448 rightOffset -= 1; 449 } 450 451 return haystack.ptr[leftOffset .. rightOffset]; 452 } 453 /// ditto 454 inout(char)[] strip()(scope return inout(char)[] haystack) @safe pure nothrow @nogc // template-lazy 455 { 456 return haystack.strip(' '); 457 } 458 459 /// 460 @safe pure nothrow @nogc unittest 461 { 462 assert("beta".strip(' ') == "beta"); 463 assert(" beta ".strip(' ') == "beta"); 464 assert(" beta ".strip(' ') == "beta"); 465 assert(" beta ".strip(' ') == "beta"); 466 assert(" _ beta _ ".strip(' ') == "_ beta _"); 467 assert(" _ beta _ ".strip(' ') == "_ beta _"); 468 469 char[] f()() @safe pure nothrow { char[1] x = "_"; return x[].strip(' '); } 470 static if (isDIP1000) static assert(!__traits(compiles, { auto _ = f(); })); 471 } 472 473 /// 474 @safe pure nothrow @nogc unittest 475 { 476 const ubyte[3] x = [0, 42, 0]; 477 assert(x.strip(0) == x[1 .. 2]); 478 } 479 480 /** Array-specialization of `count` with default predicate. 481 * 482 * TODO Add optimized implementation for needles with length >= 483 * `largeNeedleLength` with no repeat of elements. 484 * 485 * TODO reuse `return haystack.indexOf(needle) != -1` in both overloads 486 */ 487 bool canFind(T)(scope const T[] haystack, 488 scope const T[] needle) @trusted 489 { 490 // enum largeNeedleLength = 4; 491 assert(needle.length, "Cannot count occurrences of an empty range"); 492 if (haystack.length < needle.length) { return false; } 493 foreach (const offset; 0 .. haystack.length - needle.length + 1) 494 { 495 if (haystack.ptr[offset .. offset + needle.length] == needle) 496 { 497 return true; 498 } 499 } 500 return false; 501 } 502 /// ditto 503 bool canFind(T)(scope const T[] haystack, 504 scope const T needle) @trusted 505 { 506 static if (is(T == char)) { assert(needle < 128); } // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org 507 if (haystack.length == 0) { return false; } 508 foreach (const ref element; haystack) 509 { 510 if (element == needle) 511 { 512 return true; 513 } 514 } 515 return false; 516 } 517 518 /// 519 @safe pure nothrow @nogc unittest 520 { 521 assert(!"".canFind("_")); 522 assert(!"a".canFind("_")); 523 assert("a".canFind("a")); 524 assert(!"a".canFind("ab")); 525 assert("ab".canFind("a")); 526 assert("ab".canFind("b")); 527 assert("ab".canFind("ab")); 528 assert(!"a".canFind("ab")); 529 assert(!"b".canFind("ab")); 530 } 531 532 /// 533 @safe pure nothrow @nogc unittest 534 { 535 assert(!"".canFind('_')); 536 assert(!"a".canFind('_')); 537 assert("a".canFind('a')); 538 assert("a".canFind('a')); 539 assert("ab".canFind('a')); 540 assert("ab".canFind('b')); 541 } 542 543 /** Array-specialization of `count` with default predicate. 544 */ 545 size_t count(T)(scope const T[] haystack, 546 scope const T[] needle) @trusted 547 { 548 assert(needle.length, "Cannot count occurrences of an empty range"); 549 size_t result = 0; 550 if (haystack.length < needle.length) { return false; } 551 foreach (const offset; 0 .. haystack.length - needle.length + 1) 552 { 553 result += haystack.ptr[offset .. offset + needle.length] == needle ? 1 : 0; 554 } 555 return result; 556 } 557 /// ditto 558 size_t count(T)(scope const T[] haystack, 559 scope const T needle) 560 { 561 static if (is(T == char)) { assert(needle < 128); } // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org 562 size_t result; 563 foreach (const ref element; haystack) 564 { 565 result += element == needle ? 1 : 0; 566 } 567 return result; 568 } 569 570 /// 571 @safe pure nothrow @nogc unittest 572 { 573 // import std.algorithm.searching : count; 574 assert("".count("_") == 0); 575 assert("".count(" ") == 0); 576 assert(" ".count(" ") == 1); 577 assert("abc_abc".count("a") == 2); 578 assert("abc_abc".count("abc") == 2); 579 assert("_a_a_".count("_") == 3); 580 assert("_aaa_".count("a") == 3); 581 // assert("".count("") == 0); 582 // assert("_a_a_".count("") == 5); 583 } 584 585 /// 586 @safe pure nothrow @nogc unittest 587 { 588 assert("".count('_') == 0); 589 assert("abc_abc".count('a') == 2); 590 assert("_abc_abc_".count('_') == 3); 591 } 592 593 /** Array-specialization of `count` with default predicate and no needle. 594 */ 595 size_t count(T)(scope const T[] haystack) 596 { 597 version(D_Coverage) {} else pragma(inline, true); 598 return haystack.length; 599 } 600 601 /// 602 @safe pure nothrow @nogc unittest 603 { 604 assert("abc_abc".count == 7); 605 } 606 607 /** Array-specialization of `indexOf` with default predicate. 608 * 609 * TODO Add optimized implementation for needles with length >= 610 * `largeNeedleLength` with no repeat of elements. 611 */ 612 ptrdiff_t indexOf(T)(scope inout(T)[] haystack, 613 scope const(T)[] needle) @trusted 614 { 615 // enum largeNeedleLength = 4; 616 if (haystack.length < needle.length) { return -1; } 617 foreach (const offset; 0 .. haystack.length - needle.length + 1) 618 { 619 if (haystack.ptr[offset .. offset + needle.length] == needle) 620 { 621 return offset; 622 } 623 } 624 return -1; 625 } 626 /// ditto 627 ptrdiff_t indexOf(T)(scope inout(T)[] haystack, 628 scope const T needle) 629 { 630 static if (is(T == char)) { assert(needle < 128); } // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org 631 foreach (const offset, const ref element; haystack) 632 { 633 if (element == needle) 634 { 635 return offset; 636 } 637 } 638 return -1; 639 } 640 641 /// 642 @safe pure nothrow @nogc unittest 643 { 644 assert("_abc_abc_".indexOf("abc") == 1); 645 assert("__abc_".indexOf("abc") == 2); 646 assert("a".indexOf("a") == 0); 647 assert("abc".indexOf("abc") == 0); 648 assert("_".indexOf("a") == -1); 649 assert("_".indexOf("__") == -1); 650 assert("__".indexOf("a") == -1); 651 } 652 653 /// 654 @safe pure nothrow @nogc unittest 655 { 656 assert("_".indexOf('a') == -1); 657 assert("a".indexOf('a') == 0); 658 assert("_a".indexOf('a') == 1); 659 assert("__a".indexOf('a') == 2); 660 } 661 662 /** Array-specialization of `lastIndexOf` with default predicate. 663 */ 664 ptrdiff_t lastIndexOf(T)(scope inout(T)[] haystack, 665 scope const(T)[] needle) @trusted 666 { 667 if (haystack.length < needle.length) { return -1; } 668 foreach_reverse (const offset; 0 .. haystack.length - needle.length + 1) 669 { 670 if (haystack.ptr[offset .. offset + needle.length] == needle) 671 { 672 return offset; 673 } 674 } 675 return -1; 676 } 677 /// ditto 678 ptrdiff_t lastIndexOf(T)(scope inout(T)[] haystack, 679 scope const T needle) 680 { 681 static if (is(T == char)) { assert(needle < 128); } // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org 682 foreach_reverse (const offset, const ref element; haystack) 683 { 684 if (element == needle) 685 { 686 return offset; 687 } 688 } 689 return -1; 690 } 691 692 /// 693 @safe pure nothrow @nogc unittest 694 { 695 assert("_abc_abc_".lastIndexOf("abc") == 5); 696 assert("__abc_".lastIndexOf("abc") == 2); 697 assert("a".lastIndexOf("a") == 0); 698 assert("aa".lastIndexOf("a") == 1); 699 assert("abc".lastIndexOf("abc") == 0); 700 assert("_".lastIndexOf("a") == -1); 701 assert("_".lastIndexOf("__") == -1); 702 assert("__".lastIndexOf("a") == -1); 703 } 704 705 /// 706 @safe pure nothrow @nogc unittest 707 { 708 assert("_".lastIndexOf('a') == -1); 709 assert("a".lastIndexOf('a') == 0); 710 assert("_a".lastIndexOf('a') == 1); 711 assert("__a".lastIndexOf('a') == 2); 712 assert("a__a".lastIndexOf('a') == 3); 713 } 714 715 /** Array-specialization of `findSplit` with default predicate. 716 * 717 * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org 718 * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org 719 */ 720 auto findSplit(T)(scope return inout(T)[] haystack, 721 scope const(T)[] needle) 722 { 723 static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly 724 { 725 private T[] _haystack; 726 private size_t _offset; // hit offset 727 private size_t _length; // hit length 728 729 inout(T)[] pre() @trusted inout 730 { 731 return _haystack.ptr[0 .. _offset]; 732 } 733 734 inout(T)[] separator() @trusted inout 735 { 736 return _haystack.ptr[_offset .. _offset + _length]; 737 } 738 739 inout(T)[] post() @trusted inout 740 { 741 return _haystack.ptr[_offset + _length .. _haystack.length]; 742 } 743 744 bool opCast(T : bool)() @safe const 745 { 746 return _haystack.length != _offset; 747 } 748 } 749 750 assert(needle.length, "Cannot find occurrence of an empty range"); 751 const index = haystack.indexOf(needle); 752 if (index >= 0) 753 { 754 return inout(Result)(haystack, index, needle.length); 755 } 756 return inout(Result)(haystack, haystack.length, 0); // miss 757 } 758 /// ditto 759 auto findSplit(T)(scope return inout(T)[] haystack, 760 scope const T needle) 761 { 762 static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly 763 { 764 private T[] _haystack; 765 private size_t _offset; // hit offset 766 767 inout(T)[] pre() @trusted inout 768 { 769 return _haystack.ptr[0 .. _offset]; 770 } 771 772 inout(T)[] separator() @trusted inout 773 { 774 if (empty) { return _haystack[$ .. $]; } 775 return _haystack.ptr[_offset .. _offset + 1]; 776 } 777 778 inout(T)[] post() @trusted inout 779 { 780 if (empty) { return _haystack[$ .. $]; } 781 return _haystack.ptr[_offset + 1 .. _haystack.length]; 782 } 783 784 bool opCast(T : bool)() const 785 { 786 return !empty; 787 } 788 789 private @property bool empty() const 790 { 791 return _haystack.length == _offset; 792 } 793 } 794 795 static if (is(T == char)) { assert(needle < 128); } // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org 796 const index = haystack.indexOf(needle); 797 if (index >= 0) 798 { 799 return inout(Result)(haystack, index); 800 } 801 return inout(Result)(haystack, haystack.length); 802 } 803 804 /// 805 @safe pure nothrow @nogc unittest 806 { 807 const h = "a**b"; 808 const r = h.findSplit("**"); 809 assert(r); 810 assert(r.pre is h[0 .. 1]); 811 assert(r.separator is h[1 .. 3]); 812 assert(r.post is h[3 .. 4]); 813 814 auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplit(" "); } 815 static if (isDIP1000) static assert(!__traits(compiles, { auto _ = f(); })); 816 } 817 818 /// 819 @safe pure nothrow @nogc unittest 820 { 821 const h = "a**b"; 822 const r = h.findSplit("_"); 823 static assert(r.sizeof == 2 * 2 * size_t.sizeof); 824 assert(!r); 825 assert(r.pre is h); 826 assert(r.separator is h[$ .. $]); 827 assert(r.post is h[$ .. $]); 828 } 829 830 /// 831 version(none) 832 @safe pure nothrow @nogc unittest 833 { 834 import std.algorithm.searching : findSplit; 835 const h = "a**b"; 836 const r = h.findSplit("_"); 837 static assert(r.sizeof == 3 * 2 * size_t.sizeof); 838 assert(!r); 839 assert(r[0] is h); 840 assert(r[1] is h[$ .. $]); 841 assert(r[2] is h[$ .. $]); 842 } 843 844 /// 845 @safe pure nothrow @nogc unittest 846 { 847 const r = "a*b".findSplit('*'); 848 static assert(r.sizeof == 3 * size_t.sizeof); 849 assert(r); 850 assert(r.pre == "a"); 851 assert(r.separator == "*"); 852 assert(r.post == "b"); 853 854 auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplit(' '); } 855 static if (isDIP1000) static assert(!__traits(compiles, { auto _ = f(); })); 856 } 857 858 /// DIP-1000 scope analysis 859 @safe pure nothrow @nogc unittest 860 { 861 char[] f() @safe pure nothrow 862 { 863 char[3] haystack = "a*b"; 864 auto r = haystack[].findSplit('*'); 865 static assert(is(typeof(r.pre()) == char[])); 866 return r.pre(); // TODO this should fail 867 } 868 } 869 870 /** Array-specialization of `findLastSplit` with default predicate. 871 */ 872 auto findLastSplit(T)(scope return inout(T)[] haystack, 873 scope const(T)[] needle) 874 { 875 static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly 876 { 877 private T[] _haystack; 878 private size_t _offset; // hit offset 879 private size_t _length; // hit length 880 881 inout(T)[] pre() @trusted inout 882 { 883 return _haystack.ptr[0 .. _offset]; 884 } 885 886 inout(T)[] separator() @trusted inout 887 { 888 return _haystack.ptr[_offset .. _offset + _length]; 889 } 890 891 inout(T)[] post() @trusted inout 892 { 893 return _haystack.ptr[_offset + _length .. _haystack.length]; 894 } 895 896 bool opCast(T : bool)() @safe const 897 { 898 return _haystack.length != _offset; 899 } 900 } 901 902 assert(needle.length, "Cannot find occurrence of an empty range"); 903 const index = haystack.lastIndexOf(needle); 904 if (index >= 0) 905 { 906 return inout(Result)(haystack, index, needle.length); 907 } 908 return inout(Result)(haystack, haystack.length, 0); // miss 909 } 910 /// ditto 911 auto findLastSplit(T)(scope return inout(T)[] haystack, 912 scope const T needle) 913 { 914 static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly 915 { 916 private T[] _haystack; 917 private size_t _offset; // hit offset 918 919 inout(T)[] pre() @trusted inout 920 { 921 return _haystack.ptr[0 .. _offset]; 922 } 923 924 inout(T)[] separator() @trusted inout 925 { 926 if (empty) { return _haystack[$ .. $]; } 927 return _haystack.ptr[_offset .. _offset + 1]; 928 } 929 930 inout(T)[] post() @trusted inout 931 { 932 if (empty) { return _haystack[$ .. $]; } 933 return _haystack.ptr[_offset + 1 .. _haystack.length]; 934 } 935 936 bool opCast(T : bool)() const 937 { 938 return !empty; 939 } 940 941 private @property bool empty() const 942 { 943 return _haystack.length == _offset; 944 } 945 } 946 947 static if (is(T == char)) { assert(needle < 128); } // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org 948 const index = haystack.lastIndexOf(needle); 949 if (index >= 0) 950 { 951 return inout(Result)(haystack, index); 952 } 953 return inout(Result)(haystack, haystack.length); 954 } 955 956 /// 957 @safe pure nothrow @nogc unittest 958 { 959 const h = "a**b**c"; 960 const r = h.findLastSplit("**"); 961 assert(r); 962 assert(r.pre is h[0 .. 4]); 963 assert(r.separator is h[4 .. 6]); 964 assert(r.post is h[6 .. 7]); 965 966 auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findLastSplit(" "); } 967 static if (isDIP1000) static assert(!__traits(compiles, { auto _ = f(); })); 968 } 969 970 /// 971 @safe pure nothrow @nogc unittest 972 { 973 const h = "a**b**c"; 974 const r = h.findLastSplit("_"); 975 static assert(r.sizeof == 2 * 2 * size_t.sizeof); 976 assert(!r); 977 assert(r.pre is h); 978 assert(r.separator is h[$ .. $]); 979 assert(r.post is h[$ .. $]); 980 } 981 982 /// 983 @safe pure nothrow @nogc unittest 984 { 985 const r = "a*b*c".findLastSplit('*'); 986 static assert(r.sizeof == 3 * size_t.sizeof); 987 assert(r); 988 assert(r.pre == "a*b"); 989 assert(r.separator == "*"); 990 assert(r.post == "c"); 991 992 auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findLastSplit(' '); } 993 static if (isDIP1000) static assert(!__traits(compiles, { auto _ = f(); })); 994 } 995 996 /// DIP-1000 scope analysis 997 @safe pure nothrow @nogc unittest 998 { 999 char[] f() @safe pure nothrow 1000 { 1001 char[3] haystack = "a*b"; 1002 auto r = haystack[].findLastSplit('*'); 1003 static assert(is(typeof(r.pre()) == char[])); 1004 return r.pre(); // TODO this should fail 1005 } 1006 } 1007 1008 /** Array-specialization of `findSplitBefore` with default predicate. 1009 * 1010 * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org 1011 * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org 1012 */ 1013 auto findSplitBefore(T)(scope return inout(T)[] haystack, 1014 scope const T needle) 1015 { 1016 static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly 1017 { 1018 private T[] _haystack; 1019 private size_t _offset; 1020 1021 pragma(inline, true): 1022 1023 inout(T)[] pre() @trusted inout 1024 { 1025 return _haystack.ptr[0 .. _offset]; 1026 } 1027 1028 inout(T)[] post() @trusted inout 1029 { 1030 if (empty) { return _haystack[$ .. $]; } 1031 return _haystack.ptr[_offset .. _haystack.length]; 1032 } 1033 1034 bool opCast(T : bool)() const 1035 { 1036 return !empty; 1037 } 1038 1039 private @property bool empty() const 1040 { 1041 return _haystack.length == _offset; 1042 } 1043 } 1044 1045 static if (is(T == char)) { assert(needle < 128); } // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org 1046 foreach (const offset, const ref element; haystack) 1047 { 1048 if (element == needle) 1049 { 1050 return inout(Result)(haystack, offset); 1051 } 1052 } 1053 return inout(Result)(haystack, haystack.length); 1054 } 1055 1056 /// 1057 @safe pure nothrow @nogc unittest 1058 { 1059 char[] haystack; 1060 auto r = haystack.findSplitBefore('_'); 1061 static assert(is(typeof(r.pre()) == char[])); 1062 static assert(is(typeof(r.post()) == char[])); 1063 assert(!r); 1064 assert(!r.pre); 1065 assert(!r.post); 1066 1067 auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplitBefore(' '); } 1068 static if (isDIP1000) static assert(!__traits(compiles, { auto _ = f(); })); 1069 } 1070 1071 /// 1072 @safe pure nothrow @nogc unittest 1073 { 1074 const(char)[] haystack; 1075 auto r = haystack.findSplitBefore('_'); 1076 static assert(is(typeof(r.pre()) == const(char)[])); 1077 static assert(is(typeof(r.post()) == const(char)[])); 1078 assert(!r); 1079 assert(!r.pre); 1080 assert(!r.post); 1081 } 1082 1083 /// 1084 @safe pure nothrow @nogc unittest 1085 { 1086 const r = "a*b".findSplitBefore('*'); 1087 assert(r); 1088 assert(r.pre == "a"); 1089 assert(r.post == "*b"); 1090 } 1091 1092 /// 1093 @safe pure nothrow @nogc unittest 1094 { 1095 const r = "a*b".findSplitBefore('_'); 1096 assert(!r); 1097 assert(r.pre == "a*b"); 1098 assert(r.post == ""); 1099 } 1100 1101 /** Array-specialization of `findSplitBefore` with explicit needle-only predicate `needlePred`. 1102 * 1103 * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org 1104 * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org 1105 */ 1106 auto findSplitBefore(alias needlePred, T)(scope return inout(T)[] haystack) 1107 { 1108 static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly 1109 { 1110 private T[] _haystack; 1111 private size_t _offset; 1112 1113 pragma(inline, true): 1114 1115 inout(T)[] pre() @trusted inout 1116 { 1117 return _haystack.ptr[0 .. _offset]; 1118 } 1119 1120 inout(T)[] post() @trusted inout 1121 { 1122 if (empty) { return _haystack[$ .. $]; } 1123 return _haystack.ptr[_offset .. _haystack.length]; 1124 } 1125 1126 bool opCast(T : bool)() const 1127 { 1128 return !empty; 1129 } 1130 1131 private @property bool empty() const 1132 { 1133 return _haystack.length == _offset; 1134 } 1135 } 1136 1137 foreach (const offset, const ref element; haystack) 1138 { 1139 if (needlePred(element)) 1140 { 1141 return inout(Result)(haystack, offset); 1142 } 1143 } 1144 return inout(Result)(haystack, haystack.length); 1145 } 1146 1147 /// 1148 @safe pure nothrow @nogc unittest 1149 { 1150 char[] haystack; 1151 auto r = haystack.findSplitBefore!(_ => _ == '_'); 1152 static assert(is(typeof(r.pre()) == char[])); 1153 static assert(is(typeof(r.post()) == char[])); 1154 assert(!r); 1155 assert(!r.pre); 1156 assert(!r.post); 1157 1158 auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplitBefore!(_ => _ == ' '); } 1159 static if (isDIP1000) static assert(!__traits(compiles, { auto _ = f(); })); 1160 } 1161 1162 /// 1163 @safe pure nothrow @nogc unittest 1164 { 1165 const(char)[] haystack; 1166 auto r = haystack.findSplitBefore!(_ => _ == '_'); 1167 static assert(is(typeof(r.pre()) == const(char)[])); 1168 static assert(is(typeof(r.post()) == const(char)[])); 1169 assert(!r); 1170 assert(!r.pre); 1171 assert(!r.post); 1172 } 1173 1174 /// 1175 @safe pure nothrow @nogc unittest 1176 { 1177 const r = "a*b".findSplitBefore!(_ => _ == '*'); 1178 assert(r); 1179 assert(r.pre == "a"); 1180 assert(r.post == "*b"); 1181 } 1182 1183 /// 1184 @safe pure nothrow @nogc unittest 1185 { 1186 const r = "a*b".findSplitBefore!(_ => _ == '*' || _ == '+'); 1187 assert(r); 1188 assert(r.pre == "a"); 1189 assert(r.post == "*b"); 1190 } 1191 1192 /// 1193 @safe pure nothrow @nogc unittest 1194 { 1195 const r = "a+b".findSplitBefore!(_ => _ == '*' || _ == '+'); 1196 assert(r); 1197 assert(r.pre == "a"); 1198 assert(r.post == "+b"); 1199 } 1200 1201 /// 1202 @safe pure nothrow @nogc unittest 1203 { 1204 const r = "a*b".findSplitBefore!(_ => _ == '_'); 1205 assert(!r); 1206 assert(r.pre == "a*b"); 1207 assert(r.post == ""); 1208 } 1209 1210 /** Array-specialization of `findSplitAfter` with default predicate. 1211 * 1212 * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org 1213 * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org 1214 */ 1215 auto findSplitAfter(T)(scope return inout(T)[] haystack, 1216 scope const T needle) @trusted 1217 { 1218 static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly 1219 { 1220 private T[] _haystack; 1221 private size_t _offset; 1222 1223 pragma(inline, true): 1224 1225 inout(T)[] pre() @trusted inout 1226 { 1227 if (empty) { return _haystack[$ .. $]; } 1228 return _haystack.ptr[0 .. _offset + 1]; 1229 } 1230 1231 inout(T)[] post() @trusted inout 1232 { 1233 if (empty) { return _haystack[0 .. $]; } 1234 return _haystack.ptr[_offset + 1 .. _haystack.length]; 1235 } 1236 1237 bool opCast(T : bool)() const 1238 { 1239 return !empty; 1240 } 1241 1242 private @property bool empty() const 1243 { 1244 return _haystack.length == _offset; 1245 } 1246 } 1247 1248 static if (is(T == char)) { assert(needle < 128); } // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org 1249 foreach (const offset, const ref element; haystack) 1250 { 1251 if (element == needle) 1252 { 1253 return inout(Result)(haystack, offset); 1254 } 1255 } 1256 return inout(Result)(haystack, haystack.length); 1257 } 1258 1259 /// 1260 @safe pure nothrow @nogc unittest 1261 { 1262 char[] haystack; 1263 auto r = haystack.findSplitAfter('_'); 1264 static assert(is(typeof(r.pre()) == char[])); 1265 static assert(is(typeof(r.post()) == char[])); 1266 assert(!r); 1267 assert(!r.pre); 1268 assert(!r.post); 1269 1270 auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplitAfter(' '); } 1271 static if (isDIP1000) static assert(!__traits(compiles, { auto _ = f(); })); 1272 } 1273 1274 /// 1275 @safe pure nothrow @nogc unittest 1276 { 1277 const(char)[] haystack; 1278 auto r = haystack.findSplitAfter('_'); 1279 static assert(is(typeof(r.pre()) == const(char)[])); 1280 static assert(is(typeof(r.post()) == const(char)[])); 1281 assert(!r); 1282 assert(!r.pre); 1283 assert(!r.post); 1284 } 1285 1286 /// 1287 @safe pure nothrow @nogc unittest 1288 { 1289 auto r = "a*b".findSplitAfter('*'); 1290 static assert(is(typeof(r.pre()) == string)); 1291 static assert(is(typeof(r.post()) == string)); 1292 assert(r); 1293 assert(r.pre == "a*"); 1294 assert(r.post == "b"); 1295 } 1296 1297 /** Array-specialization of `findLastSplitAfter` with default predicate. 1298 * 1299 * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org 1300 * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org 1301 */ 1302 auto findLastSplitAfter(T)(scope return inout(T)[] haystack, 1303 scope const T needle) @trusted 1304 { 1305 static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly 1306 { 1307 private T[] _haystack; 1308 private size_t _offset; 1309 1310 pragma(inline, true): 1311 1312 inout(T)[] pre() @trusted inout 1313 { 1314 if (empty) { return _haystack[$ .. $]; } 1315 return _haystack.ptr[0 .. _offset + 1]; 1316 } 1317 1318 inout(T)[] post() @trusted inout 1319 { 1320 if (empty) { return _haystack[0 .. $]; } 1321 return _haystack.ptr[_offset + 1 .. _haystack.length]; 1322 } 1323 1324 bool opCast(T : bool)() const 1325 { 1326 return !empty; 1327 } 1328 1329 private @property bool empty() const 1330 { 1331 return _haystack.length == _offset; 1332 } 1333 } 1334 1335 static if (is(T == char)) { assert(needle < 128); } // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org 1336 const index = haystack.lastIndexOf(needle); 1337 if (index >= 0) 1338 { 1339 return inout(Result)(haystack, index); 1340 } 1341 return inout(Result)(haystack, haystack.length); // miss 1342 } 1343 1344 /// 1345 @safe pure nothrow @nogc unittest 1346 { 1347 char[] haystack; 1348 auto r = haystack.findLastSplitAfter('_'); 1349 static assert(is(typeof(r.pre()) == char[])); 1350 static assert(is(typeof(r.post()) == char[])); 1351 assert(!r); 1352 assert(!r.pre); 1353 assert(!r.post); 1354 1355 auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findLastSplitAfter(' '); } 1356 static if (isDIP1000) static assert(!__traits(compiles, { auto _ = f(); })); 1357 } 1358 1359 /// 1360 @safe pure nothrow @nogc unittest 1361 { 1362 const(char)[] haystack; 1363 auto r = haystack.findLastSplitAfter('_'); 1364 static assert(is(typeof(r.pre()) == const(char)[])); 1365 static assert(is(typeof(r.post()) == const(char)[])); 1366 assert(!r); 1367 assert(!r.pre); 1368 assert(!r.post); 1369 } 1370 1371 /// 1372 @safe pure nothrow @nogc unittest 1373 { 1374 auto r = "a*b*c".findLastSplitAfter('*'); 1375 static assert(is(typeof(r.pre()) == string)); 1376 static assert(is(typeof(r.post()) == string)); 1377 assert(r); 1378 assert(r.pre == "a*b*"); 1379 assert(r.post == "c"); 1380 } 1381 1382 version(unittest) 1383 { 1384 import nxt.dip_traits : isDIP1000; 1385 } 1386 1387 // See_Also: https://dlang.org/spec/betterc.html#unittests 1388 version(unittestAsBetterC) 1389 extern(C) void main() 1390 { 1391 static foreach (u; __traits(getUnitTests, __traits(parent, main))) 1392 { 1393 u(); 1394 } 1395 }