1 #!/usr/bin/env rdmd-dev-module
2 
3 module skip_ex;
4 
5 import std.functional : binaryFun;
6 import std.range: back, save, empty, popBack, hasSlicing;
7 import std.algorithm : skipOver;
8 
9 // TODO Add variadic (static and dynamic) versions of "(starts|ends)With(Either)?"
10 
11 alias skipFronts = skipOver;
12 
13 /**
14    If $(D startsWith(r1, r2)), consume the corresponding elements off $(D
15    r1) and return $(D true). Otherwise, leave $(D r1) unchanged and
16    return $(D false).
17 */
18 bool skipOverBack(alias pred = "a == b", R1, R2)(ref R1 r1, R2 r2)
19     if (is(typeof(binaryFun!pred(r1.back, r2.back))))
20 {
21     auto r = r1.save;
22     while (!r2.empty && !r.empty && binaryFun!pred(r.back, r2.back))
23     {
24         r.popBack();
25         r2.popBack();
26     }
27     if (r2.empty)
28         r1 = r;
29     return r2.empty;
30 }
31 alias skipBacks = skipOverBack;
32 
33 @safe pure unittest
34 {
35     import std.algorithm: equal;
36 
37     auto s1 = "Hello world";
38     assert(!skipOverBack(s1, "Ha"));
39     assert(s1 == "Hello world");
40     assert(skipOverBack(s1, "world") && s1 == "Hello ");
41 }
42 
43 import std.typecons: tuple, Tuple;
44 
45 import std.algorithm: startsWith;
46 
47 /** Variadic Version of $(D skipOver).
48     Returns: index + 1 into matching $(D needles), 0 otherwise.
49  */
50 size_t skipOverEither(alias pred = "a == b", Range, Ranges...)(ref Range haystack,
51                                                                Ranges needles)
52     if (Ranges.length >= 2 &&
53         is(typeof(startsWith!pred(haystack, needles))))
54 {
55     import std.algorithm : skipOver;
56     foreach (const ix, needle; needles)
57         if (haystack.skipOver(needle))
58             return ix + 1;
59     return 0;
60 }
61 
62 @safe pure nothrow @nogc unittest
63 {
64     auto x = "beta version";
65     assert(x.skipOverEither("beta", "be") == 1);
66     assert(x == " version");
67 }
68 
69 /** Skip Over Shortest Matching prefix in $(D needles) that prefixes $(D haystack).
70     TODO Make return value a specific type that has bool conversion so we can
71     call it as
72     if (auto hit = r.skipOverShortestOf(...)) { ... }
73  */
74 size_t skipOverShortestOf(alias pred = "a == b",
75                           Range,
76                           Ranges...)(ref Range haystack,
77                                      Ranges needles)
78     if (Ranges.length >= 2 &&
79         is(typeof(startsWith!pred(haystack, needles))))
80 {
81     const hit = startsWith!pred(haystack, needles);
82     if (hit)
83     {
84         // get needle lengths
85         size_t[needles.length] lengths;
86         foreach (ix, needle; needles)
87         {
88             import std.traits: isSomeString, isSomeChar;
89             import std.range: ElementType;
90             import std.typecons: Unqual;
91 
92             alias Needle = Unqual!(typeof(needle));
93 
94             static if (is(Unqual!Range ==
95                           Needle))
96             {
97                 lengths[ix] = needle.length;
98             }
99             else static if (is(Unqual!(ElementType!Range) ==
100                                Unqual!(ElementType!Needle)))
101             {
102                 lengths[ix] = needle.length;
103             }
104             else static if (isSomeString!Range &&
105                             isSomeString!Needle)
106             {
107                 lengths[ix] = needle.length;
108             }
109             else static if (isSomeChar!(ElementType!Range) &&
110                             isSomeChar!Needle)
111             {
112                 lengths[ix] = 1;
113             }
114             else static if (is(Unqual!(ElementType!Range) ==
115                                Needle))
116             {
117                 lengths[ix] = 1;
118             }
119             else
120             {
121                 static assert(false,
122                               "Cannot handle needle of type " ~ Needle.stringof ~
123                               " when haystack has ElementType " ~ (ElementType!Range).stringof);
124             }
125         }
126 
127         import std.range: popFrontN;
128         haystack.popFrontN(lengths[hit - 1]);
129     }
130 
131     return hit;
132 
133 }
134 
135 @safe pure unittest
136 {
137     auto x = "beta version";
138     assert(x.skipOverShortestOf("beta", "be") == 2);
139     assert(x == "ta version");
140 }
141 
142 @safe pure unittest
143 {
144     auto x = "beta version";
145     assert(x.skipOverShortestOf("be", "beta") == 1);
146     assert(x == "ta version");
147 }
148 
149 @safe pure unittest
150 {
151     auto x = "beta version";
152     assert(x.skipOverShortestOf('b', "be", "beta") == 1);
153     assert(x == "eta version");
154 }
155 
156 /** Skip Over Longest Matching prefix in $(D needles) that prefixes $(D haystack). */
157 Tuple!(bool, size_t) skipOverLongestOf(alias pred = "a == b", Range, Ranges...)(ref Range haystack, Ranges needles)
158 {
159     // TODO figure out which needles that are prefixes of other needles by first
160     // sorting them and then use some adjacent filtering algorithm
161     return haystack.skipOverShortestOf(needles);
162 }
163 
164 size_t skipOverBackShortestOf(alias pred = "a == b", Range, Ranges...)(ref Range haystack, Ranges needles)
165 // TODO We cannot prove that cast(ubyte[]) of a type that have no directions is safe
166     @trusted
167     if (Ranges.length >= 2 &&
168         is(typeof(startsWith!pred(haystack, needles))))
169 {
170     import std.range: retro, ElementType;
171     import std.traits: hasIndirections;
172     import std.typecons: Unqual;
173     import std.meta: staticMap, AliasSeq;
174     // import traits_ex: allSameType;
175 
176     static if ((!hasIndirections!(ElementType!Range))/*  && */
177                /* allSameType!(Unqual!Range, staticMap!(Unqual, Ranges)) */)
178     {
179         auto retroHaystack = (cast(ubyte[])haystack).retro;
180 
181         alias Retro(Range) = typeof((ubyte[]).init.retro);
182         AliasSeq!(staticMap!(Retro, Ranges)) retroNeedles;
183         foreach (ix, needle; needles)
184         {
185             retroNeedles[ix] = (cast(ubyte[])needle).retro;
186         }
187 
188         const retroHit = retroHaystack.skipOverShortestOf(retroNeedles);
189         haystack = haystack[0.. $ - (haystack.length - retroHaystack.length)];
190 
191         return retroHit;
192     }
193     else
194     {
195         static assert(false, "Unsupported combination of haystack type " ~ Range.stringof ~
196                       " with needle types " ~ Ranges.stringof);
197     }
198 }
199 
200 @safe pure nothrow @nogc unittest
201 {
202     auto x = "alpha_beta";
203     assert(x.skipOverBackShortestOf("x", "beta") == 2);
204     assert(x == "alpha_");
205 }
206 
207 @safe pure nothrow @nogc unittest
208 {
209     auto x = "alpha_beta";
210     assert(x.skipOverBackShortestOf("a", "beta") == 1);
211     assert(x == "alpha_bet");
212 }
213 
214 /** Drop $(D prefixes) in $(D s).
215     TODO Use multi-argument skipOver when it becomes available http://forum.dlang.org/thread/bug-12335-3@https.d.puremagic.com%2Fissues%2F
216 */
217 void skipOverPrefixes(R, A)(ref R s, in A prefixes)
218 {
219     foreach (prefix; prefixes)
220     {
221         if (s.length > prefix.length &&
222             s.skipOver(prefix)) { break; }
223     }
224 }
225 
226 /** Drop $(D suffixes) in $(D s). */
227 void skipOverSuffixes(R, A)(ref R s, in A suffixes)
228 {
229     foreach (suffix; suffixes)
230     {
231         if (s.length > suffix.length &&
232             s.endsWith(suffix)) { s = s[0 .. $ - suffix.length]; break; }
233     }
234 }