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 }