1// Split-up due to DMD's enormous memory consumption
2
3module std.regex.internal.tests2;
4
5package(std.regex):
6
7import std.conv, std.exception, std.meta, std.range,
8    std.typecons, std.regex;
9
10import std.uni : Escapables; // characters that need escaping
11
12@safe unittest
13{
14    auto cr = ctRegex!("abc");
15    assert(bmatch("abc",cr).hit == "abc");
16    auto cr2 = ctRegex!("ab*c");
17    assert(bmatch("abbbbc",cr2).hit == "abbbbc");
18}
19@safe unittest
20{
21    auto cr3 = ctRegex!("^abc$");
22    assert(bmatch("abc",cr3).hit == "abc");
23    auto cr4 = ctRegex!(`\b(a\B[a-z]b)\b`);
24    assert(array(match("azb",cr4).captures) == ["azb", "azb"]);
25}
26
27@safe unittest
28{
29    auto cr5 = ctRegex!("(?:a{2,4}b{1,3}){1,2}");
30    assert(bmatch("aaabaaaabbb", cr5).hit == "aaabaaaabbb");
31    auto cr6 = ctRegex!("(?:a{2,4}b{1,3}){1,2}?"w);
32    assert(bmatch("aaabaaaabbb"w,  cr6).hit == "aaab"w);
33}
34
35@safe unittest
36{
37    auto cr7 = ctRegex!(`\r.*?$`,"sm");
38    assert(bmatch("abc\r\nxy",  cr7).hit == "\r\nxy");
39    auto greed =  ctRegex!("<packet.*?/packet>");
40    assert(bmatch("<packet>text</packet><packet>text</packet>", greed).hit
41            == "<packet>text</packet>");
42}
43
44@safe unittest
45{
46    import std.algorithm.comparison : equal;
47    auto cr8 = ctRegex!("^(a)(b)?(c*)");
48    auto m8 = bmatch("abcc",cr8);
49    assert(m8);
50    assert(m8.captures[1] == "a");
51    assert(m8.captures[2] == "b");
52    assert(m8.captures[3] == "cc");
53    auto cr9 = ctRegex!("q(a|b)*q");
54    auto m9 = match("xxqababqyy",cr9);
55    assert(m9);
56    assert(equal(bmatch("xxqababqyy",cr9).captures, ["qababq", "b"]));
57}
58
59@safe unittest
60{
61    import std.algorithm.comparison : equal;
62    auto rtr = regex("a|b|c");
63    static ctr = regex("a|b|c");
64    assert(equal(rtr.ir,ctr.ir));
65    //CTFE parser BUG is triggered by group
66    //in the middle of alternation (at least not first and not last)
67    static testCT = regex(`abc|(edf)|xyz`);
68    auto testRT = regex(`abc|(edf)|xyz`);
69    assert(equal(testCT.ir,testRT.ir));
70}
71
72@safe unittest
73{
74    import std.algorithm.comparison : equal;
75    import std.algorithm.iteration : map;
76    enum cx = ctRegex!"(A|B|C)";
77    auto mx = match("B",cx);
78    assert(mx);
79    assert(equal(mx.captures, [ "B", "B"]));
80    enum cx2 = ctRegex!"(A|B)*";
81    assert(match("BAAA",cx2));
82
83    enum cx3 = ctRegex!("a{3,4}","i");
84    auto mx3 = match("AaA",cx3);
85    assert(mx3);
86    assert(mx3.captures[0] == "AaA");
87    enum cx4 = ctRegex!(`^a{3,4}?[a-zA-Z0-9~]{1,2}`,"i");
88    auto mx4 = match("aaaabc", cx4);
89    assert(mx4);
90    assert(mx4.captures[0] == "aaaab");
91    auto cr8 = ctRegex!("(a)(b)?(c*)");
92    auto m8 = bmatch("abcc",cr8);
93    assert(m8);
94    assert(m8.captures[1] == "a");
95    assert(m8.captures[2] == "b");
96    assert(m8.captures[3] == "cc");
97    auto cr9 = ctRegex!(".*$", "gm");
98    auto m9 = match("First\rSecond", cr9);
99    assert(m9);
100    assert(equal(map!"a.hit"(m9), ["First", "", "Second"]));
101}
102
103@safe unittest
104{
105    import std.algorithm.comparison : equal;
106    import std.algorithm.iteration : map;
107//global matching
108    void test_body(alias matchFn)()
109    {
110        string s = "a quick brown fox jumps over a lazy dog";
111        auto r1 = regex("\\b[a-z]+\\b","g");
112        string[] test;
113        foreach (m; matchFn(s, r1))
114            test ~= m.hit;
115        assert(equal(test, [ "a", "quick", "brown", "fox", "jumps", "over", "a", "lazy", "dog"]));
116        auto free_reg = regex(`
117
118            abc
119            \s+
120            "
121            (
122                    [^"]+
123                |   \\ "
124            )+
125            "
126            z
127        `, "x");
128        auto m = match(`abc  "quoted string with \" inside"z`,free_reg);
129        assert(m);
130        string mails = " hey@you.com no@spam.net ";
131        auto rm = regex(`@(?<=\S+@)\S+`,"g");
132        assert(equal(map!"a[0]"(matchFn(mails, rm)), ["@you.com", "@spam.net"]));
133        auto m2 = matchFn("First line\nSecond line",regex(".*$","gm"));
134        assert(equal(map!"a[0]"(m2), ["First line", "", "Second line"]));
135        auto m2a = matchFn("First line\nSecond line",regex(".+$","gm"));
136        assert(equal(map!"a[0]"(m2a), ["First line", "Second line"]));
137        auto m2b = matchFn("First line\nSecond line",regex(".+?$","gm"));
138        assert(equal(map!"a[0]"(m2b), ["First line", "Second line"]));
139        debug(std_regex_test) writeln("!!! FReD FLAGS test done "~matchFn.stringof~" !!!");
140    }
141    test_body!bmatch();
142    test_body!match();
143}
144
145//tests for accumulated std.regex issues and other regressions
146@safe unittest
147{
148    import std.algorithm.comparison : equal;
149    import std.algorithm.iteration : map;
150    void test_body(alias matchFn)()
151    {
152        // https://issues.dlang.org/show_bug.cgi?id=5857
153        //matching goes out of control if ... in (...){x} has .*/.+
154        auto c = matchFn("axxxzayyyyyzd",regex("(a.*z){2}d")).captures;
155        assert(c[0] == "axxxzayyyyyzd");
156        assert(c[1] == "ayyyyyz");
157        auto c2 = matchFn("axxxayyyyyd",regex("(a.*){2}d")).captures;
158        assert(c2[0] == "axxxayyyyyd");
159        assert(c2[1] == "ayyyyy");
160        // https://issues.dlang.org/show_bug.cgi?id=2108
161        //greedy vs non-greedy
162        auto nogreed = regex("<packet.*?/packet>");
163        assert(matchFn("<packet>text</packet><packet>text</packet>", nogreed).hit
164               == "<packet>text</packet>");
165        auto greed =  regex("<packet.*/packet>");
166        assert(matchFn("<packet>text</packet><packet>text</packet>", greed).hit
167               == "<packet>text</packet><packet>text</packet>");
168        // https://issues.dlang.org/show_bug.cgi?id=4574
169        //empty successful match still advances the input
170        string[] pres, posts, hits;
171        foreach (m; matchFn("abcabc", regex("","g")))
172        {
173            pres ~= m.pre;
174            posts ~= m.post;
175            assert(m.hit.empty);
176
177        }
178        auto heads = [
179            "abcabc",
180            "abcab",
181            "abca",
182            "abc",
183            "ab",
184            "a",
185            ""
186        ];
187        auto tails = [
188            "abcabc",
189             "bcabc",
190              "cabc",
191               "abc",
192                "bc",
193                 "c",
194                  ""
195        ];
196        assert(pres == array(retro(heads)));
197        assert(posts == tails);
198        // https://issues.dlang.org/show_bug.cgi?id=6076
199        //regression on .*
200        auto re = regex("c.*|d");
201        auto m = matchFn("mm", re);
202        assert(!m);
203        debug(std_regex_test) writeln("!!! FReD REGRESSION test done "~matchFn.stringof~" !!!");
204        auto rprealloc = regex(`((.){5}.{1,10}){5}`);
205        auto arr = array(repeat('0',100));
206        auto m2 = matchFn(arr, rprealloc);
207        assert(m2);
208        assert(collectException(
209                regex(r"^(import|file|binary|config)\s+([^\(]+)\(?([^\)]*)\)?\s*$")
210                ) is null);
211        foreach (ch; [Escapables])
212        {
213            assert(match(to!string(ch),regex(`[\`~ch~`]`)));
214            assert(!match(to!string(ch),regex(`[^\`~ch~`]`)));
215            assert(match(to!string(ch),regex(`[\`~ch~`-\`~ch~`]`)));
216        }
217        // https://issues.dlang.org/show_bug.cgi?id=7718
218        string strcmd = "./myApp.rb -os OSX -path \"/GIT/Ruby Apps/sec\" -conf 'notimer'";
219        auto reStrCmd = regex (`(".*")|('.*')`, "g");
220        assert(equal(map!"a[0]"(matchFn(strcmd, reStrCmd)),
221                     [`"/GIT/Ruby Apps/sec"`, `'notimer'`]));
222    }
223    test_body!bmatch();
224    test_body!match();
225}
226
227// tests for replace
228@safe unittest
229{
230    void test(alias matchFn)()
231    {
232        import std.uni : toUpper;
233
234        static foreach (i, v; AliasSeq!(string, wstring, dstring))
235        {{
236            auto baz(Cap)(Cap m)
237            if (is(Cap == Captures!(Cap.String)))
238            {
239                return toUpper(m.hit);
240            }
241            alias String = v;
242            assert(std.regex.replace!(matchFn)(to!String("ark rapacity"), regex(to!String("r")), to!String("c"))
243                   == to!String("ack rapacity"));
244            assert(std.regex.replace!(matchFn)(to!String("ark rapacity"), regex(to!String("r"), "g"), to!String("c"))
245                   == to!String("ack capacity"));
246            assert(std.regex.replace!(matchFn)(to!String("noon"), regex(to!String("^n")), to!String("[$&]"))
247                   == to!String("[n]oon"));
248            assert(std.regex.replace!(matchFn)(
249                to!String("test1 test2"), regex(to!String(`\w+`),"g"), to!String("$`:$'")
250            ) == to!String(": test2 test1 :"));
251            auto s = std.regex.replace!(baz!(Captures!(String)))(to!String("Strap a rocket engine on a chicken."),
252                    regex(to!String("[ar]"), "g"));
253            assert(s == "StRAp A Rocket engine on A chicken.");
254        }}
255        debug(std_regex_test) writeln("!!! Replace test done "~matchFn.stringof~"  !!!");
256    }
257    test!(bmatch)();
258    test!(match)();
259}
260
261// tests for splitter
262@safe unittest
263{
264    import std.algorithm.comparison : equal;
265    auto s1 = ", abc, de,     fg, hi, ";
266    auto sp1 = splitter(s1, regex(", *"));
267    auto w1 = ["", "abc", "de", "fg", "hi", ""];
268    assert(equal(sp1, w1));
269
270    auto s2 = ", abc, de,  fg, hi";
271    auto sp2 = splitter(s2, regex(", *"));
272    auto w2 = ["", "abc", "de", "fg", "hi"];
273
274    uint cnt;
275    foreach (e; sp2)
276    {
277        assert(w2[cnt++] == e);
278    }
279    assert(equal(sp2, w2));
280}
281
282@safe unittest
283{
284    char[] s1 = ", abc, de,  fg, hi, ".dup;
285    auto sp2 = splitter(s1, regex(", *"));
286}
287
288@safe unittest
289{
290    import std.algorithm.comparison : equal;
291    auto s1 = ", abc, de,  fg, hi, ";
292    auto w1 = ["", "abc", "de", "fg", "hi", ""];
293    assert(equal(split(s1, regex(", *")), w1[]));
294}
295
296// https://issues.dlang.org/show_bug.cgi?id=7141
297@safe unittest
298{
299    string pattern = `[a\--b]`;
300    assert(match("-", pattern));
301    assert(match("b", pattern));
302    string pattern2 = `[&-z]`;
303    assert(match("b", pattern2));
304}
305
306// https://issues.dlang.org/show_bug.cgi?id=7111
307@safe unittest
308{
309    assert(match("", regex("^")));
310}
311
312// https://issues.dlang.org/show_bug.cgi?id=7300
313@safe unittest
314{
315    assert(!match("a"d, "aa"d));
316}
317
318// https://issues.dlang.org/show_bug.cgi?id=7551
319@safe unittest
320{
321    auto r = regex("[]abc]*");
322    assert("]ab".matchFirst(r).hit == "]ab");
323    assertThrown(regex("[]"));
324    auto r2 = regex("[]abc--ab]*");
325    assert("]ac".matchFirst(r2).hit == "]");
326}
327
328// https://issues.dlang.org/show_bug.cgi?id=7674
329@safe unittest
330{
331    assert("1234".replace(regex("^"), "$$") == "$1234");
332    assert("hello?".replace(regex(r"\?", "g"), r"\?") == r"hello\?");
333    assert("hello?".replace(regex(r"\?", "g"), r"\\?") != r"hello\?");
334}
335
336// https://issues.dlang.org/show_bug.cgi?id=7679
337@safe unittest
338{
339    import std.algorithm.comparison : equal;
340    static foreach (S; AliasSeq!(string, wstring, dstring))
341    {{
342        enum re = ctRegex!(to!S(r"\."));
343        auto str = to!S("a.b");
344        assert(equal(std.regex.splitter(str, re), [to!S("a"), to!S("b")]));
345        assert(split(str, re) == [to!S("a"), to!S("b")]);
346    }}
347}
348
349// https://issues.dlang.org/show_bug.cgi?id=8203
350@safe unittest
351{
352    string data = "
353    NAME   = XPAW01_STA:STATION
354    NAME   = XPAW01_STA
355    ";
356    auto uniFileOld = data;
357    auto r = regex(
358       r"^NAME   = (?P<comp>[a-zA-Z0-9_]+):*(?P<blk>[a-zA-Z0-9_]*)","gm");
359    auto uniCapturesNew = match(uniFileOld, r);
360    for (int i = 0; i < 20; i++)
361        foreach (matchNew; uniCapturesNew) {}
362    //a second issue with same symptoms
363    auto r2 = regex(`([��-����-��\-_]+\s*)+(?<=[\s\.,\^])`);
364    match("���������� ����������������������", r2);
365}
366
367// https://issues.dlang.org/show_bug.cgi?id=8637 purity of enforce
368@safe unittest
369{
370    auto m = match("hello world", regex("world"));
371    enforce(m);
372}
373
374// https://issues.dlang.org/show_bug.cgi?id=8725
375@safe unittest
376{
377  static italic = regex( r"\*
378                (?!\s+)
379                (.*?)
380                (?!\s+)
381                \*", "gx" );
382  string input = "this * is* interesting, *very* interesting";
383  assert(replace(input, italic, "<i>$1</i>") ==
384      "this * is* interesting, <i>very</i> interesting");
385}
386
387// https://issues.dlang.org/show_bug.cgi?id=8349
388@safe unittest
389{
390    enum peakRegexStr = r"\>(wgEncode.*Tfbs.*\.(?:narrow)|(?:broad)Peak.gz)</a>";
391    enum peakRegex = ctRegex!(peakRegexStr);
392    //note that the regex pattern itself is probably bogus
393    assert(match(r"\>wgEncode-blah-Tfbs.narrow</a>", peakRegex));
394}
395
396// https://issues.dlang.org/show_bug.cgi?id=9211
397@safe unittest
398{
399    import std.algorithm.comparison : equal;
400    auto rx_1 =  regex(r"^(\w)*(\d)");
401    auto m = match("1234", rx_1);
402    assert(equal(m.front, ["1234", "3", "4"]));
403    auto rx_2 = regex(r"^([0-9])*(\d)");
404    auto m2 = match("1234", rx_2);
405    assert(equal(m2.front, ["1234", "3", "4"]));
406}
407
408// https://issues.dlang.org/show_bug.cgi?id=9280
409@safe unittest
410{
411    string tomatch = "a!b@c";
412    static r = regex(r"^(?P<nick>.*?)!(?P<ident>.*?)@(?P<host>.*?)$");
413    auto nm = match(tomatch, r);
414    assert(nm);
415    auto c = nm.captures;
416    assert(c[1] == "a");
417    assert(c["nick"] == "a");
418}
419
420
421// https://issues.dlang.org/show_bug.cgi?id=9579
422@safe unittest
423{
424    char[] input = ['a', 'b', 'c'];
425    string format = "($1)";
426    // used to give a compile error:
427    auto re = regex(`(a)`, "g");
428    auto r = replace(input, re, format);
429    assert(r == "(a)bc");
430}
431
432// https://issues.dlang.org/show_bug.cgi?id=9634
433@safe unittest
434{
435    auto re = ctRegex!"(?:a+)";
436    assert(match("aaaa", re).hit == "aaaa");
437}
438
439// https://issues.dlang.org/show_bug.cgi?id=10798
440@safe unittest
441{
442    auto cr = ctRegex!("[abcd--c]*");
443    auto m  = "abc".match(cr);
444    assert(m);
445    assert(m.hit == "ab");
446}
447
448// https://issues.dlang.org/show_bug.cgi?id=10913
449@system unittest
450{
451    @system static string foo(const(char)[] s)
452    {
453        return s.dup;
454    }
455    @safe static string bar(const(char)[] s)
456    {
457        return s.dup;
458    }
459    () @system {
460        replace!((a) => foo(a.hit))("blah", regex(`a`));
461    }();
462    () @safe {
463        replace!((a) => bar(a.hit))("blah", regex(`a`));
464    }();
465}
466
467// https://issues.dlang.org/show_bug.cgi?id=11262
468@safe unittest
469{
470    enum reg = ctRegex!(r",", "g");
471    auto str = "This,List";
472    str = str.replace(reg, "-");
473    assert(str == "This-List");
474}
475
476// https://issues.dlang.org/show_bug.cgi?id=11775
477@safe unittest
478{
479    assert(collectException(regex("a{1,0}")));
480}
481
482// https://issues.dlang.org/show_bug.cgi?id=11839
483@safe unittest
484{
485    import std.algorithm.comparison : equal;
486    assert(regex(`(?P<var1>\w+)`).namedCaptures.equal(["var1"]));
487    assert(collectException(regex(`(?P<1>\w+)`)));
488    assert(regex(`(?P<v1>\w+)`).namedCaptures.equal(["v1"]));
489    assert(regex(`(?P<__>\w+)`).namedCaptures.equal(["__"]));
490    assert(regex(`(?P<��>\w+)`).namedCaptures.equal(["��"]));
491}
492
493// https://issues.dlang.org/show_bug.cgi?id=12076
494@safe unittest
495{
496    auto RE = ctRegex!(r"(?<!x[a-z]+)\s([a-z]+)");
497    string s = "one two";
498    auto m = match(s, RE);
499}
500
501// https://issues.dlang.org/show_bug.cgi?id=12105
502@safe unittest
503{
504    auto r = ctRegex!`.*?(?!a)`;
505    assert("aaab".matchFirst(r).hit == "aaa");
506    auto r2 = ctRegex!`.*(?!a)`;
507    assert("aaab".matchFirst(r2).hit == "aaab");
508}
509
510// https://issues.dlang.org/show_bug.cgi?id=11784
511@safe unittest
512{
513    assert("abcdefghijklmnopqrstuvwxyz"
514        .matchFirst("[a-z&&[^aeiuo]]").hit == "b");
515}
516
517// https://issues.dlang.org/show_bug.cgi?id=12366
518@safe unittest
519{
520     auto re = ctRegex!(`^((?=(xx+?)\2+$)((?=\2+$)(?=(x+)(\4+$))\5){2})*x?$`);
521     assert("xxxxxxxx".match(re).empty);
522     assert(!"xxxx".match(re).empty);
523}
524
525// https://issues.dlang.org/show_bug.cgi?id=12582
526@safe unittest
527{
528    auto r = regex(`(?P<a>abc)`);
529    assert(collectException("abc".matchFirst(r)["b"]));
530}
531
532// https://issues.dlang.org/show_bug.cgi?id=12691
533@safe unittest
534{
535    assert(bmatch("e@", "^([a-z]|)*$").empty);
536    assert(bmatch("e@", ctRegex!`^([a-z]|)*$`).empty);
537}
538
539// https://issues.dlang.org/show_bug.cgi?id=12713
540@safe unittest
541{
542    assertThrown(regex("[[a-z]([a-z]|(([[a-z])))"));
543}
544
545// https://issues.dlang.org/show_bug.cgi?id=12747
546@safe unittest
547{
548    assertThrown(regex(`^x(\1)`));
549    assertThrown(regex(`^(x(\1))`));
550    assertThrown(regex(`^((x)(?=\1))`));
551}
552
553// https://issues.dlang.org/show_bug.cgi?id=13532
554version (none) // TODO: revist once we have proper benchmark framework
555@safe unittest
556{
557    import std.datetime.stopwatch : StopWatch, AutoStart;
558    import std.math.algebraic : abs;
559    import std.conv : to;
560    enum re1 = ctRegex!`[0-9][0-9]`;
561    immutable static re2 = ctRegex!`[0-9][0-9]`;
562    immutable iterations = 1_000_000;
563    size_t result1 = 0, result2 = 0;
564    auto sw = StopWatch(AutoStart.yes);
565    foreach (_; 0 .. iterations)
566    {
567        result1 += matchFirst("12345678", re1).length;
568    }
569    const staticTime = sw.peek();
570    sw.reset();
571    foreach (_; 0 .. iterations)
572    {
573        result2 += matchFirst("12345678", re2).length;
574    }
575    const enumTime = sw.peek();
576    assert(result1 == result2);
577    auto ratio = 1.0 * enumTime.total!"usecs" / staticTime.total!"usecs";
578    // enum is faster or the diff is less < 30%
579    assert(ratio < 1.0 || abs(ratio - 1.0) < 0.75,
580        "enum regex to static regex ratio "~to!string(ratio));
581}
582
583// https://issues.dlang.org/show_bug.cgi?id=14504
584@safe unittest
585{
586    auto p = ctRegex!("a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?" ~
587            "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
588}
589
590// https://issues.dlang.org/show_bug.cgi?id=14529
591@safe unittest
592{
593    auto ctPat2 = regex(r"^[CDF]$", "i");
594    foreach (v; ["C", "c", "D", "d", "F", "f"])
595        assert(matchAll(v, ctPat2).front.hit == v);
596}
597
598// https://issues.dlang.org/show_bug.cgi?id=14615
599@safe unittest
600{
601    import std.array : appender;
602    import std.regex : replaceFirst, replaceFirstInto, regex;
603    import std.stdio : writeln;
604
605    auto example = "Hello, world!";
606    auto pattern = regex("^Hello, (bug)");  // won't find this one
607    auto result = replaceFirst(example, pattern, "$1 Sponge Bob");
608    assert(result == "Hello, world!");  // Ok.
609
610    auto sink = appender!string;
611    replaceFirstInto(sink, example, pattern, "$1 Sponge Bob");
612    assert(sink.data == "Hello, world!");
613    replaceAllInto(sink, example, pattern, "$1 Sponge Bob");
614    assert(sink.data == "Hello, world!Hello, world!");
615}
616
617// https://issues.dlang.org/show_bug.cgi?id=15573
618@safe unittest
619{
620    auto rx = regex("[c d]", "x");
621    assert("a b".matchFirst(rx));
622}
623
624// https://issues.dlang.org/show_bug.cgi?id=15864
625@safe unittest
626{
627    regex(`(<a (?:(?:\w+=\"[^"]*\")?\s*)*href="\.\.?)"`);
628}
629
630@safe unittest
631{
632    auto r = regex("(?# comment)abc(?# comment2)");
633    assert("abc".matchFirst(r));
634    assertThrown(regex("(?#..."));
635}
636
637// https://issues.dlang.org/show_bug.cgi?id=17075
638@safe unittest
639{
640    enum titlePattern = `<title>(.+)</title>`;
641    static titleRegex = ctRegex!titlePattern;
642    string input = "<title>" ~ "<".repeat(100_000).join;
643    assert(input.matchFirst(titleRegex).empty);
644}
645
646// https://issues.dlang.org/show_bug.cgi?id=17212
647@safe unittest
648{
649    auto r = regex(" [a] ", "x");
650    assert("a".matchFirst(r));
651}
652
653// https://issues.dlang.org/show_bug.cgi?id=17157
654@safe unittest
655{
656    import std.algorithm.comparison : equal;
657    auto ctr = ctRegex!"(a)|(b)|(c)|(d)";
658    auto r = regex("(a)|(b)|(c)|(d)", "g");
659    auto s = "--a--b--c--d--";
660    auto outcomes = [
661        ["a", "a", "", "", ""],
662        ["b", "", "b", "", ""],
663        ["c", "", "", "c", ""],
664        ["d", "", "", "", "d"]
665    ];
666    assert(equal!equal(s.matchAll(ctr), outcomes));
667    assert(equal!equal(s.bmatch(r), outcomes));
668}
669
670// https://issues.dlang.org/show_bug.cgi?id=17667
671@safe unittest
672{
673    import std.algorithm.searching : canFind;
674    void willThrow(T, size_t line = __LINE__)(T arg, string msg)
675    {
676        auto e = collectException(regex(arg));
677        assert(e.msg.canFind(msg), to!string(line) ~ ": " ~ e.msg);
678    }
679    willThrow([r".", r"[\(\{[\]\}\)]"], "no matching ']' found while parsing character class");
680    willThrow([r"[\", r"123"], "no matching ']' found while parsing character class");
681    willThrow([r"[a-", r"123"], "no matching ']' found while parsing character class");
682    willThrow([r"[a-\", r"123"], "no matching ']' found while parsing character class");
683    willThrow([r"\", r"123"], "invalid escape sequence");
684}
685
686// https://issues.dlang.org/show_bug.cgi?id=17668
687@safe unittest
688{
689    import std.algorithm.searching;
690    auto e = collectException!RegexException(regex(q"<[^]>"));
691    assert(e.msg.canFind("no operand for '^'"), e.msg);
692}
693
694// https://issues.dlang.org/show_bug.cgi?id=17673
695@safe unittest
696{
697    string str = `<">`;
698    string[] regexps = ["abc", "\"|x"];
699    auto regexp = regex(regexps);
700    auto c = matchFirst(str, regexp);
701    assert(c);
702    assert(c.whichPattern == 2);
703}
704
705// https://issues.dlang.org/show_bug.cgi?id=18692
706@safe unittest
707{
708    auto rx = regex("()()()");
709    auto ma = "".matchFirst(rx);
710    auto ma2 = ma;
711    ma = ma2;
712    assert(ma[1] == "");
713}
714