1 /**
2   Base mechanisms used to determine information about iopipes.
3 Copyright: Copyright Steven Schveighoffer 2011-.
4 License:   Boost License 1.0. (See accompanying file LICENSE_1_0.txt or copy
5            at http://www.boost.org/LICENSE_1_0.txt)
6 Authors:   Steven Schveighoffer
7  */
8 module iopipe.traits;
9 
10 /**
11  * add window property to all arrays that allows any array to be the start of a pipe.
12  * Returns: t
13  */
14 auto window(T)(T[] t)
15 {
16     return t;
17 }
18 
19 /**
20  * add extend function to all arrays that allows any array to be the start of a pipe chain.
21  * Params: t = The array to attempt to extend.
22  *         elements = ignored
23  * Returns: Always returns 0 because arrays cannot be extended.
24  */
25 size_t extend(T)(T[] t, size_t elements)
26 {
27     return 0;
28 }
29 
30 /**
31  * Add release function to all arrays. This will remove the given number of elements
32  * from the front of the array
33  * Params: t = The array to release elements from.
34  *         elements = Number of elements to release
35  */
36 void release(T)(ref T[] t, size_t elements)
37 {
38     assert(elements <= t.length);
39     t = t[elements .. $];
40 }
41 
42 @safe unittest
43 {
44     // ensure an array is a valid iopipe
45     static assert(isIopipe!(ubyte[]));
46     static assert(isIopipe!(string));
47     static assert(isIopipe!(int[]));
48 
49     // release is the only testworthy function
50     import std.range: iota;
51     import std.array: array;
52 
53     auto arr = iota(100).array;
54 
55     auto oldarr = arr;
56     arr.release(20);
57     assert(oldarr[20 .. $] == arr);
58 }
59 
60 /**
61  * evaluates to true if the given type is a valid ioPipe
62  */
63 template isIopipe(T)
64 {
65     enum isIopipe = is(typeof(()
66         {
67             import std.range.primitives;
68             import std.traits;
69             auto t = T.init;
70             auto window = t.window;
71             alias W = typeof(window);
72             static assert(isNarrowString!W || isRandomAccessRange!W);
73             auto x = t.extend(size_t(0));
74             static assert(is(typeof(x) == size_t));
75             t.release(size_t(0));
76         }));
77 }
78 
79 @safe unittest
80 {
81     import std.meta: AliasSeq;
82     import std.traits: isNarrowString;
83     static struct S1(T)
84     {
85         T[] window;
86         size_t extend(size_t elements) { return 0; }
87         void release(size_t elements) {}
88     }
89 
90     // test struct with random access range instead of array
91     import std.range: chain;
92     static struct S2(T)
93     {
94         T[] arr1;
95         T[] arr2;
96         auto window() { return chain(arr1, arr2); }
97         size_t extend(size_t elements) { return 0; }
98         void release(size_t elements) {}
99     }
100 
101     foreach(type; AliasSeq!(char, wchar, dchar, ubyte, byte, ushort, short, uint, int))
102     {
103         static assert(isIopipe!(S1!type), "S1!" ~ type.stringof);
104         // Phobos treats narrow strings as non-random access range of dchar, so
105         // compositions will not work with iopipe.
106         static if(!isNarrowString!(type[]))
107             static assert(isIopipe!(S2!type), "S2!" ~ type.stringof);
108     }
109 }
110 
111 // I don't know how to do this a better way...
112 template PropertyType(alias x)
113 {
114     import std.traits: ReturnType;
115     static if(is(typeof(x) == function))
116         alias PropertyType = ReturnType!x;
117     else
118         alias PropertyType = typeof(x);
119 }
120 
121 /**
122  * Determine the type of the window of the given pipe type. This works when the
123  * window is a method or a field.
124  */
125 template WindowType(T)
126 {
127     alias WindowType = PropertyType!(T.init.window);
128 }
129 
130 @safe unittest
131 {
132     static struct S1 { ubyte[] window; }
133     static assert(is(WindowType!S1 == ubyte[]));
134 
135     static struct S2 { ubyte[] window() { return null; } }
136     static assert(is(WindowType!S2 == ubyte[]));
137 }
138 
139 /**
140  * Evaluates to true if the given io pipe has a valve
141  */
142 template hasValve(T)
143 {
144     import std.traits : hasMember;
145     static if(hasMember!(T, "valve"))
146         enum hasValve = isIopipe!(PropertyType!(T.init.valve));
147     else
148         enum hasValve = false;
149 }
150 
151 /**
152  * Boilerplate for implementing a valve. If you don't define a custom valve,
153  * you should always mixin this template in all your iopipe templates.
154  *
155  * Params: pipechain = symbol that contains the upstream pipe chain.
156  */
157 mixin template implementValve(alias pipechain)
158 {
159     static if(hasValve!(PropertyType!(pipechain)))
160         ref valve() { return pipechain.valve; }
161 }
162 
163 @safe unittest
164 {
165     static struct S1
166     {
167         int[] valve;
168         size_t extend(size_t elements) { return elements; }
169         int[] window;
170         void release(size_t elements) {}
171     }
172 
173     static assert(hasValve!S1);
174 
175     static struct S2(T)
176     {
177         T upstream;
178         size_t extend(size_t elements) { return elements; }
179         int[] window;
180         void release(size_t elements) {}
181 
182         mixin implementValve!(upstream);
183     }
184 
185     static assert(hasValve!(S2!S1));
186     static assert(!hasValve!(S2!(int[])));
187 }
188 
189 /**
190  * Determine the number of valves in the given pipeline
191  */
192 template valveCount(T)
193 {
194     static if(hasValve!(T))
195     {
196         enum valveCount = 1 + .valveCount!(PropertyType!(T.init.valve));
197     }
198     else
199     {
200         enum valveCount = 0;
201     }
202 }
203 
204 @safe unittest
205 {
206     static struct ValveStruct(T, bool shouldAddValve)
207     {
208         static if(shouldAddValve)
209         {
210             T valve;
211         }
212         else
213         {
214             T upstream;
215             mixin implementValve!(upstream);
216         }
217         int[] window;
218         size_t extend(size_t elements) { return elements; }
219         void release(size_t elements) {}
220 
221     }
222 
223     static void foo(bool shouldAddValve, int curValves, int depth, T)(T t)
224     {
225         auto p = ValveStruct!(T, shouldAddValve)();
226         enum myValves = curValves + (shouldAddValve? 1 : 0);
227         static assert(valveCount!(typeof(p)) == myValves);
228         static if(depth > 0)
229         {
230             foo!(true, myValves, depth - 1)(p);
231             foo!(false, myValves, depth - 1)(p);
232         }
233     }
234 
235     foo!(true, 0, 4)((int[]).init);
236     foo!(false, 0, 4)((int[]).init);
237 }