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 flod.meta : str, Id;
10 
11 struct PullSource(E) {
12 	size_t pull(E[] buf) { return buf.length; }
13 	enum methodStr = "pull";
14 }
15 
16 struct PeekSource(E) {
17 	const(E)[] peek(size_t n) { return new E[n]; }
18 	void consume(size_t n) {}
19 	enum methodStr = "peek";
20 }
21 
22 struct PushSource(E) {
23 	enum methodStr = "push";
24 }
25 
26 struct AllocSource(E) {
27 	enum methodStr = "alloc";
28 }
29 
30 struct PullSink(E) {
31 	enum methodStr = "pull";
32 }
33 
34 struct PeekSink(E) {
35 	enum methodStr = "peek";
36 }
37 
38 struct PushSink(E) {
39 	size_t push(const(E)[] buf) { return buf.length; }
40 	enum methodStr = "push";
41 }
42 
43 struct AllocSink(E) {
44 	bool alloc(ref E[] buf, size_t n) { buf = new E[n]; return true; }
45 	void consume(size_t n) {}
46 	enum methodStr = "alloc";
47 }
48 
49 enum pullSource(E) = PullSource!E();
50 enum peekSource(E) = PeekSource!E();
51 enum pushSource(E) = PushSource!E();
52 enum allocSource(E) = AllocSource!E();
53 
54 enum pullSink(E) = PullSink!E();
55 enum peekSink(E) = PeekSink!E();
56 enum pushSink(E) = PushSink!E();
57 enum allocSink(E) = AllocSink!E();
58 
59 private struct None {}
60 
61 private template isSame(alias S) {
62 	template isSame(alias Z) {
63 		enum isSame = is(Id!S == Id!Z);
64 	}
65 }
66 
67 private template areSame(W...) {
68 	enum areSame = is(Id!(W[0 .. $ / 2]) == Id!(W[$ / 2 .. $]));
69 }
70 
71 package template Traits(alias Src, alias Snk, SrcE, SnkE, UDAs...) {
72 	static if (UDAs.length == 0) {
73 		alias Source = Src;
74 		alias Sink = Snk;
75 		alias SourceElementType = SrcE;
76 		alias SinkElementType = SnkE;
77 		static if (!is(Src == None))
78 			enum sourceMethodStr = Src!SrcE.methodStr;
79 		else
80 			enum sourceMethodStr = "";
81 		static if (!is(Snk == None))
82 			enum sinkMethodStr = Snk!SnkE.methodStr;
83 		else
84 			enum sinkMethodStr = "";
85 		enum str = .str!Sink ~ "!" ~ .str!SinkElementType ~ "-" ~ .str!Source ~ "!" ~ .str!SourceElementType;
86 	} else {
87 		import std.meta : anySatisfy;
88 
89 		static if (is(typeof(UDAs[0]))) {
90 			alias T = typeof(UDAs[0]);
91 			static if (is(T == S!E, alias S, E)) {
92 				static if (anySatisfy!(isSame!S, PullSource, PeekSource, PushSource, AllocSource)) {
93 					static assert(is(Id!Src == Id!None), "Source interface declared more than once");
94 					alias Traits = .Traits!(S, Snk, E, SnkE, UDAs[1 .. $]);
95 				} else static if (anySatisfy!(isSame!S, PullSink, PeekSink, PushSink, AllocSink)) {
96 					static assert(is(Id!Snk == Id!None), "Sink interface declared more than once");
97 					alias Traits = .Traits!(Src, S, SrcE, E, UDAs[1 .. $]);
98 				} else {
99 					alias Traits = .Traits!(Src, Snk, SrcE, SnkE, UDAs[1 .. $]);
100 				}
101 			} else {
102 				alias Traits = .Traits!(Src, Snk, SrcE, SnkE, UDAs[1 .. $]);
103 			}
104 		} else {
105 			alias Traits = .Traits!(Src, Snk, SrcE, SnkE, UDAs[1 .. $]);
106 		}
107 	}
108 }
109 
110 template Traits(alias S) {
111 	alias Traits = Traits!(None, None, void, void, __traits(getAttributes, S));
112 }
113 
114 unittest {
115 	@peekSource!int @(100) @Id!"zombie"() @allocSink!(Id!1)
116 	struct Foo {}
117 	alias Tr = Traits!Foo;
118 	static assert(areSame!(Tr.Source, PeekSource));
119 	static assert(areSame!(Tr.SourceElementType, int));
120 	static assert(areSame!(Tr.Sink, AllocSink));
121 	static assert(areSame!(Tr.SinkElementType, Id!1));
122 }
123 
124 unittest {
125 	@pullSource!int @pushSource!ubyte
126 	struct Bar {}
127 	static assert(!__traits(compiles, Traits!Bar)); // source interface specified twice
128 }
129 
130 unittest {
131 	@pullSink!double @pushSink!string @peekSink!void
132 	struct Baz {}
133 	static assert(!__traits(compiles, Traits!Baz)); // sink interface specified 3x
134 }
135 
136 enum isPullSource(alias S) = areSame!(Traits!S.Source, PullSource);
137 enum isPeekSource(alias S) = areSame!(Traits!S.Source, PeekSource);
138 enum isPushSource(alias S) = areSame!(Traits!S.Source, PushSource);
139 enum isAllocSource(alias S) = areSame!(Traits!S.Source, AllocSource);
140 
141 enum isPullSink(alias S) = areSame!(Traits!S.Sink, PullSink);
142 enum isPeekSink(alias S) = areSame!(Traits!S.Sink, PeekSink);
143 enum isPushSink(alias S) = areSame!(Traits!S.Sink, PushSink);
144 enum isAllocSink(alias S) = areSame!(Traits!S.Sink, AllocSink);
145 
146 unittest {
147 	@pullSource!int @pullSink!bool
148 	struct Foo {}
149 	static assert( isPullSource!Foo);
150 	static assert(!isPeekSource!Foo);
151 	static assert(!isPushSource!Foo);
152 	static assert(!isAllocSource!Foo);
153 	static assert( isPullSink!Foo);
154 	static assert(!isPeekSink!Foo);
155 	static assert(!isPushSink!Foo);
156 	static assert(!isAllocSink!Foo);
157 }
158 
159 enum isPassiveSource(alias S) = isPeekSource!S || isPullSource!S;
160 enum isActiveSource(alias S) = isPushSource!S || isAllocSource!S;
161 enum isSource(alias S) = isPassiveSource!S || isActiveSource!S;
162 
163 enum isPassiveSink(alias S) = isPushSink!S || isAllocSink!S;
164 enum isActiveSink(alias S) = isPeekSink!S || isPullSink!S;
165 enum isSink(alias S) = isPassiveSink!S || isActiveSink!S;
166 
167 
168 /** Returns `true` if `S[0]` is a source and `S[1]` is a sink and they both use the same
169  *  method of passing data.
170  */
171 enum areCompatible(alias Source, alias Sink) =
172 	(isPeekSource!Source && isPeekSink!Sink)
173 		|| (isPullSource!Source && isPullSink!Sink)
174 		|| (isAllocSource!Source && isAllocSink!Sink)
175 		|| (isPushSource!Source && isPushSink!Sink);
176 
177 template sourceMethod(alias S) {
178 	static if (isPeekSource!S) {
179 		alias sourceMethod = peekSource!(Traits!S.SourceElementType);
180 	} else static if (isPullSource!S) {
181 		alias sourceMethod = pullSource!(Traits!S.SourceElementType);
182 	} else static if (isAllocSource!S) {
183 		alias sourceMethod = allocSource!(Traits!S.SourceElementType);
184 	} else static if (isPushSource!S) {
185 		alias sourceMethod = pushSource!(Traits!S.SourceElementType);
186 	} else {
187 		bool sourceMethod() { return false; }
188 	}
189 }
190 
191 template sinkMethod(alias S) {
192 	static if (isPeekSink!S) {
193 		alias sinkMethod = peekSink!(Traits!S.SinkElementType);
194 	} else static if (isPullSink!S) {
195 		alias sinkMethod = pullSink!(Traits!S.SinkElementType);
196 	} else static if (isAllocSink!S) {
197 		alias sinkMethod = allocSink!(Traits!S.SinkElementType);
198 	} else static if (isPushSink!S) {
199 		alias sinkMethod = pushSink!(Traits!S.SinkElementType);
200 	} else {
201 		bool sinkMethod() { return false; }
202 	}
203 }
204 
205 unittest {
206 	@peekSource!double static struct Foo {}
207 	static assert(sourceMethod!Foo == peekSource!double);
208 }
209 
210 mixin template TestScheduler() {
211 	static struct __Scheduler {
212 		void stop();
213 	}
214 	__Scheduler _flod_scheduler;
215 
216 	int yield() { return 1; }
217 }
218 
219 bool check(alias T)()
220 {
221 	if (__ctfe) {
222 		alias SoE = Traits!T.SourceElementType;
223 		alias SiE = Traits!T.SinkElementType;
224 		static if (isPushSink!T && isPullSource!T) {
225 			import flod.pipeline : AllWrapper;
226 			AllWrapper!(T!TestScheduler) t;
227 			SiE[15] pushbuf;
228 			SoE[15] pullbuf;
229 			static if (!__traits(compiles, t.push(pushbuf[])))
230 				t.push(pushbuf[]);
231 			static if (!__traits(compiles, t.pull(pullbuf[])))
232 				t.push(pullbuf[]);
233 		}
234 		else {
235 			pragma(msg, "@check not implemented for ", str!(Traits!T), " (", str!T, ")");
236 		}
237 	}
238 	return true;
239 }