1 /** Templates for declaring and examining static interfaces of pipeline stages.
2  *
3  *  Authors: $(LINK2 https://github.com/epi, Adrian Matoga)
4  *  Copyright: © 2016 Adrian Matoga
5  *  License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0).
6  */
7 module flod.traits;
8 
9 import std.meta : AliasSeq, Filter, NoDuplicates, staticMap;
10 import flod.meta : str, Id;
11 
12 /// Enumerates all methods of passing data between stages.
13 enum Method {
14 	pull = 0,  /// Sink requests that the source fill sink's buffer with data.
15 	peek = 1,  /// Sink requests a read-only view on source's buffer.
16 	push = 2,  /// Source requests that the sink accepts data in source's buffer.
17 	alloc = 3, /// Source requests a writable view on sink's buffer.
18 }
19 
20 enum string[Method.max + 1] methodNames = [
21 	Method.pull : "pull", Method.peek : "peek",
22 	Method.push : "push", Method.alloc : "alloc" ];
23 
24 private enum nullMethod = cast(Method) -1;
25 
26 struct MethodAttribute {
27 	Method sinkMethod = nullMethod;
28 	Method sourceMethod = nullMethod;
29 pure nothrow @nogc const:
30 	@property bool isActiveSource()
31 	{
32 		return sourceMethod == Method.push || sourceMethod == Method.alloc;
33 	}
34 	@property bool isPassiveSource()
35 	{
36 		return sourceMethod == Method.pull || sourceMethod == Method.peek;
37 	}
38 	@property bool isActiveSink()
39 	{
40 		return sinkMethod == Method.pull || sinkMethod == Method.peek;
41 	}
42 	@property bool isPassiveSink()
43 	{
44 		return sinkMethod == Method.push || sinkMethod == Method.alloc;
45 	}
46 	@property bool isDriver()
47 	{
48 		return (isActiveSource && !isPassiveSink) || (isActiveSink && !isPassiveSource);
49 	}
50 	@property bool isPassiveFilter()
51 	{
52 		return isPassiveSource && isPassiveSink;
53 	}
54 }
55 
56 private struct TypedMethodAttribute(SinkE, SourceE) {
57 	alias SinkElementType = SinkE;
58 	alias SourceElementType = SourceE;
59 	MethodAttribute methods;
60 	alias methods this;
61 }
62 
63 /// This attribute specifies that the stage is a source.
64 auto source(E = void)(Method source_method)
65 {
66 	return TypedMethodAttribute!(void, E)(
67 		MethodAttribute(nullMethod, source_method));
68 }
69 
70 /// This attribute specifies that the stage is a sink.
71 auto sink(E = void)(Method sink_method)
72 {
73 	return TypedMethodAttribute!(E, void)(
74 		MethodAttribute(sink_method, nullMethod));
75 }
76 
77 /// This attribute specifies that the stage is a filter.
78 auto filter(E...)(Method sink_method, Method source_method)
79 {
80 	auto ma = MethodAttribute(sink_method, source_method);
81 	static if (E.length == 0)
82 		return TypedMethodAttribute!(void,  void)(ma);
83 	else static if (E.length == 1)
84 		return TypedMethodAttribute!(void,  E[0])(ma);
85 	else static if (E.length == 2)
86 		return TypedMethodAttribute!(E[0], E[1])(ma);
87 	else
88 		static assert(0, "Too many types specified");
89 }
90 
91 /// ditto
92 auto filter(E...)(Method method) { return filter!E(method, method); }
93 
94 private template getMethodAttributes(alias S) {
95 	private {
96 		alias getTypeOf(Z...) = typeof(Z[0]);
97 		enum isNotVoid(S) = !is(S == void);
98 		enum isMethodAttribute(Z...) = is(typeof(Z[0]) == TypedMethodAttribute!(A, B), A, B);
99 		template getNthType(size_t n) {
100 			alias getNthType(M : TypedMethodAttribute!EL, EL...) = EL[n];
101 		}
102 		template getUntyped(Z...) {
103 			enum getUntyped = Z[0].methods;
104 		}
105 	}
106 
107 	alias typed = AliasSeq!(Filter!(isMethodAttribute, __traits(getAttributes, S)));
108 	enum untyped = [ staticMap!(getUntyped, typed) ];
109 
110 	private {
111 		alias allNthTypes(size_t n) =
112 			NoDuplicates!(
113 				Filter!(
114 					isNotVoid,
115 					staticMap!(
116 						getNthType!n,
117 						staticMap!(getTypeOf, typed))));
118 		alias SinkElementTypes = allNthTypes!0;
119 		alias SourceElementTypes = allNthTypes!1;
120 	}
121 
122 	alias SinkElementType = AliasSeq!(SinkElementTypes, void)[0];
123 	alias SourceElementType = AliasSeq!(SourceElementTypes, void)[0];
124 
125 	// check if all attributes define the same pair of types (or void, i.e. unspecified)
126 	static assert(SourceElementTypes.length <= 1,
127 		"Conflicting source element types specified: " ~ SourceElementTypes.stringof);
128 	static assert(SinkElementTypes.length <= 1,
129 		"Conflicting sink element types specified: " ~ SinkElementTypes.stringof);
130 
131 	static if (untyped.length >= 2) {
132 		// check if methods for different kinds of stages aren't mixed
133 		static assert({
134 				import std.algorithm : reduce, map;
135 				int mask(MethodAttribute ma)
136 				{
137 					return ((ma.sinkMethod != nullMethod) ? 2 : 0)
138 						| ((ma.sourceMethod != nullMethod) ? 1 : 0);
139 				}
140 				return reduce!((a, b) => a | b)(0, untyped.map!mask)
141 					== reduce!((a, b) => a & b)(3, untyped.map!mask); }(),
142 			"A stage must be either a source, a sink or a filter - not a mix thereof");
143 	}
144 }
145 
146 ///
147 package enum getMethods(alias S) = getMethodAttributes!S.untyped;
148 
149 ///
150 package alias SinkElementType(alias S) = getMethodAttributes!S.SinkElementType;
151 
152 ///
153 package alias SourceElementType(alias S) = getMethodAttributes!S.SourceElementType;
154 
155 /// Gets the element type at source or sink end of i-th stage in `StageSeq`.
156 package template SourceElementType(size_t i, StageSeq...) {
157 	alias E = SourceElementType!(StageSeq[i]);
158 	static if (!is(E == void))
159 		alias SourceElementType = E;
160 	else static if (i == StageSeq.length - 1)
161 		alias SourceElementType = SinkElementType!(i, StageSeq);
162 	else {
163 		alias W = SinkElementType!(StageSeq[i]);
164 		static if (!is(W == void))
165 			alias SourceElementType = W;
166 		else
167 			alias SourceElementType = SinkElementType!(i, StageSeq);
168 	}
169 }
170 
171 /// ditto
172 package template SinkElementType(size_t i, StageSeq...) {
173 	alias E = SinkElementType!(StageSeq[i]);
174 	static if (is(E == void) && i > 0)
175 		alias SinkElementType = SourceElementType!(i - 1, StageSeq);
176 	else
177 		alias SinkElementType = E;
178 }
179 
180 unittest {
181 	@source!double(Method.pull) struct Foo {}
182 	@filter(Method.pull) struct Bar {}
183 	@sink(Method.pull) struct Baz {}
184 	static assert(is(SourceElementType!(0, Foo, Baz) == double));
185 	static assert(is(SinkElementType!(2, Foo, Bar, Baz) == double));
186 }
187 
188 unittest {
189 	struct Foo {}
190 	static assert(getMethods!Foo == []);
191 	static assert(is(getMethodAttributes!Foo.SinkElementType == void));
192 	static assert(is(getMethodAttributes!Foo.SourceElementType == void));
193 }
194 
195 unittest {
196 	@filter!(int, ulong)(Method.push, Method.pull)
197 	struct Foo {}
198 	static assert(getMethods!Foo == [ filter(Method.push, Method.pull) ]);
199 	static assert(is(getMethodAttributes!Foo.SinkElementType == int));
200 	static assert(is(getMethodAttributes!Foo.SourceElementType == ulong));
201 }
202 
203 unittest {
204 	@sink!int(Method.alloc)
205 	@sink(Method.push)
206 	struct Foo {}
207 	static assert(getMethods!Foo == [ sink(Method.alloc), sink(Method.push) ]);
208 	static assert(is(SinkElementType!Foo == int));
209 }
210 
211 unittest {
212 	@source(Method.push) @sink(Method.push)
213 	struct Foo {}
214 	static assert(!__traits(compiles, getMethods!Foo)); // error: specified both source and sink attributes
215 }
216 
217 unittest {
218 	@source!int(Method.push) @source!double(Method.pull)
219 	struct Foo {}
220 	static assert(!__traits(compiles, getMethods!Foo)); // error: conflicting source types (int, double)
221 }
222 
223 unittest {
224 	@filter!(int, int)(Method.push) @filter!(double, int)(Method.push)
225 	struct Foo {}
226 	static assert(!__traits(compiles, getMethods!Foo)); // error: conflicting sink types (int, double)
227 }
228 
229 private bool implementsMethod(string endp)(Method m, MethodAttribute[] attrs...) {
230 	foreach (attr; attrs) {
231 		mixin(`bool im = attr.` ~ endp ~ `Method == m;`);
232 		if (im) return true;
233 	}
234 	return false;
235 }
236 
237 enum isPullSource(alias S) = implementsMethod!`source`(Method.pull, getMethods!S);
238 enum isPeekSource(alias S) = implementsMethod!`source`(Method.peek, getMethods!S);
239 enum isPushSource(alias S) = implementsMethod!`source`(Method.push, getMethods!S);
240 enum isAllocSource(alias S) = implementsMethod!`source`(Method.alloc, getMethods!S);
241 
242 enum isPullSink(alias S) = implementsMethod!`sink`(Method.pull, getMethods!S);
243 enum isPeekSink(alias S) = implementsMethod!`sink`(Method.peek, getMethods!S);
244 enum isPushSink(alias S) = implementsMethod!`sink`(Method.push, getMethods!S);
245 enum isAllocSink(alias S) = implementsMethod!`sink`(Method.alloc, getMethods!S);
246 
247 unittest {
248 	@filter!(bool, int)(Method.pull)
249 	struct Foo {}
250 	static assert( isPullSource!Foo);
251 	static assert(!isPeekSource!Foo);
252 	static assert(!isPushSource!Foo);
253 	static assert(!isAllocSource!Foo);
254 	static assert( isPullSink!Foo);
255 	static assert(!isPeekSink!Foo);
256 	static assert(!isPushSink!Foo);
257 	static assert(!isAllocSink!Foo);
258 }
259 
260 enum isPassiveSource(alias S) = isPeekSource!S || isPullSource!S;
261 enum isActiveSource(alias S) = isPushSource!S || isAllocSource!S;
262 enum isSource(alias S) = isPassiveSource!S || isActiveSource!S;
263 
264 enum isPassiveSink(alias S) = isPushSink!S || isAllocSink!S;
265 enum isActiveSink(alias S) = isPeekSink!S || isPullSink!S;
266 enum isSink(alias S) = isPassiveSink!S || isActiveSink!S;
267 
268 enum isStage(alias S) = isSource!S || isSink!S;