1 module game.chain;
2 
3 import std.meta : allSatisfy, staticMap;
4 import std.traits : CommonType, isCallable, isFloatingPoint, isIntegral,
5   isPointer, isSomeFunction, isStaticArray, Unqual, isInstanceOf;
6 import std.range.primitives;
7 
8 auto chain(Ranges...)(Ranges rs)
9 if (Ranges.length > 0 &&
10     allSatisfy!(isInputRange, staticMap!(Unqual, Ranges)) &&
11     !is(CommonType!(staticMap!(ElementType, staticMap!(Unqual, Ranges))) == void))
12 {
13     static if (Ranges.length == 1)
14     {
15         return rs[0];
16     }
17     else
18     {
19         static struct Result
20         {
21         private:
22             alias R = staticMap!(Unqual, Ranges);
23             alias RvalueElementType = CommonType!(staticMap!(.ElementType, R));
24             private template sameET(A)
25             {
26                 enum sameET = is(.ElementType!A == RvalueElementType);
27             }
28 
29             enum bool allSameType = allSatisfy!(sameET, R);
30 
31             // This doesn't work yet
32             static if (allSameType)
33             {
34                 alias ElementType = ref RvalueElementType;
35             }
36             else
37             {
38                 alias ElementType = RvalueElementType;
39             }
40             static if (allSameType && allSatisfy!(hasLvalueElements, R))
41             {
42                 static ref RvalueElementType fixRef(ref RvalueElementType val)
43                 {
44                     return val;
45                 }
46             }
47             else
48             {
49                 static RvalueElementType fixRef(RvalueElementType val)
50                 {
51                     return val;
52                 }
53             }
54 
55             // This is the entire state
56             R source;
57             // TODO: use a vtable (or more) instead of linear iteration
58 
59         public:
60             this(R input)
61             {
62                 foreach (i, v; input)
63                 {
64                     source[i] = v;
65                 }
66             }
67 
68             import std.meta : anySatisfy;
69 
70             static if (anySatisfy!(isInfinite, R))
71             {
72                 // Propagate infiniteness.
73                 enum bool empty = false;
74             }
75             else
76             {
77                 @property bool empty()
78                 {
79                     foreach (i, Unused; R)
80                     {
81                         if (!source[i].empty) return false;
82                     }
83                     return true;
84                 }
85             }
86 
87             void popFront()
88             {
89                 foreach (i, Unused; R)
90                 {
91                     if (source[i].empty) continue;
92                     source[i].popFront();
93                     return;
94                 }
95             }
96 
97             @property auto ref front()
98             {
99                 foreach (i, Unused; R)
100                 {
101                     if (source[i].empty) continue;
102                     return fixRef(source[i].front);
103                 }
104                 assert(false);
105             }
106 
107             static if (allSameType && allSatisfy!(hasAssignableElements, R))
108             {
109                 // @@@BUG@@@
110                 //@property void front(T)(T v) if (is(T : RvalueElementType))
111 
112                 @property void front(RvalueElementType v)
113                 {
114                     foreach (i, Unused; R)
115                     {
116                         if (source[i].empty) continue;
117                         source[i].front = v;
118                         return;
119                     }
120                     assert(false);
121                 }
122             }
123 
124             static if (allSatisfy!(hasMobileElements, R))
125             {
126                 RvalueElementType moveFront()
127                 {
128                     foreach (i, Unused; R)
129                     {
130                         if (source[i].empty) continue;
131                         return source[i].moveFront();
132                     }
133                     assert(false);
134                 }
135             }
136 
137             static if (allSatisfy!(isBidirectionalRange, R))
138             {
139                 @property auto ref back()
140                 {
141                     foreach_reverse (i, Unused; R)
142                     {
143                         if (source[i].empty) continue;
144                         return fixRef(source[i].back);
145                     }
146                     assert(false);
147                 }
148 
149                 void popBack()
150                 {
151                     foreach_reverse (i, Unused; R)
152                     {
153                         if (source[i].empty) continue;
154                         source[i].popBack();
155                         return;
156                     }
157                 }
158 
159                 static if (allSatisfy!(hasMobileElements, R))
160                 {
161                     RvalueElementType moveBack()
162                     {
163                         foreach_reverse (i, Unused; R)
164                         {
165                             if (source[i].empty) continue;
166                             return source[i].moveBack();
167                         }
168                         assert(false);
169                     }
170                 }
171 
172                 static if (allSameType && allSatisfy!(hasAssignableElements, R))
173                 {
174                     @property void back(RvalueElementType v)
175                     {
176                         foreach_reverse (i, Unused; R)
177                         {
178                             if (source[i].empty) continue;
179                             source[i].back = v;
180                             return;
181                         }
182                         assert(false);
183                     }
184                 }
185             }
186 
187             static if (allSatisfy!(hasLength, R))
188             {
189                 @property size_t length()
190                 {
191                     size_t result;
192                     foreach (i, Unused; R)
193                     {
194                         result += source[i].length;
195                     }
196                     return result;
197                 }
198 
199                 alias opDollar = length;
200             }
201 
202             static if (allSatisfy!(isRandomAccessRange, R))
203             {
204                 auto ref opIndex(size_t index)
205                 {
206                     foreach (i, Range; R)
207                     {
208                         static if (isInfinite!(Range))
209                         {
210                             return source[i][index];
211                         }
212                         else
213                         {
214                             immutable length = source[i].length;
215                             if (index < length) return fixRef(source[i][index]);
216                             index -= length;
217                         }
218                     }
219                     assert(false);
220                 }
221 
222                 static if (allSatisfy!(hasMobileElements, R))
223                 {
224                     RvalueElementType moveAt(size_t index)
225                     {
226                         foreach (i, Range; R)
227                         {
228                             static if (isInfinite!(Range))
229                             {
230                                 return source[i].moveAt(index);
231                             }
232                             else
233                             {
234                                 immutable length = source[i].length;
235                                 if (index < length) return source[i].moveAt(index);
236                                 index -= length;
237                             }
238                         }
239                         assert(false);
240                     }
241                 }
242 
243                 static if (allSameType && allSatisfy!(hasAssignableElements, R))
244                     void opIndexAssign(ElementType v, size_t index)
245                     {
246                         foreach (i, Range; R)
247                         {
248                             static if (isInfinite!(Range))
249                             {
250                                 source[i][index] = v;
251                             }
252                             else
253                             {
254                                 immutable length = source[i].length;
255                                 if (index < length)
256                                 {
257                                     source[i][index] = v;
258                                     return;
259                                 }
260                                 index -= length;
261                             }
262                         }
263                         assert(false);
264                     }
265             }
266 
267             static if (allSatisfy!(hasLength, R) && allSatisfy!(hasSlicing, R))
268                 auto opSlice(size_t begin, size_t end) return scope
269                 {
270                     auto result = this;
271                     foreach (i, Unused; R)
272                     {
273                         immutable len = result.source[i].length;
274                         if (len < begin)
275                         {
276                             result.source[i] = result.source[i]
277                                 [len .. len];
278                             begin -= len;
279                         }
280                         else
281                         {
282                             result.source[i] = result.source[i]
283                                 [begin .. len];
284                             break;
285                         }
286                     }
287                     auto cut = length;
288                     cut = cut <= end ? 0 : cut - end;
289                     foreach_reverse (i, Unused; R)
290                     {
291                         immutable len = result.source[i].length;
292                         if (cut > len)
293                         {
294                             result.source[i] = result.source[i]
295                                 [0 .. 0];
296                             cut -= len;
297                         }
298                         else
299                         {
300                             result.source[i] = result.source[i]
301                                 [0 .. len - cut];
302                             break;
303                         }
304                     }
305                     return result;
306                 }
307         }
308         return Result(rs);
309     }
310 }