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