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;