1 /** 2 * This module defines "mapping" input and output streams, which map elements 3 * of a wrapped stream to another type. For example, a mapping input stream 4 * could wrap around an input stream of strings, and parse each one as an int. 5 * 6 * Mapping streams generally operate using a templated function alias; that is, 7 * you provide a function at compile-time, and a stream on which to operate. 8 * The mapping function should take a single argument, and produce a result of 9 * any type. However, if your function returns a `MapResult` type (as defined 10 * in this module), the mapping streams will acknowledge any stream errors that 11 * occur. 12 */ 13 module streams.types.mapping; 14 15 import streams.primitives; 16 import streams.utils; 17 18 /** 19 * A return type to use when a mapping function could encounter an error. 20 * Mapping streams have a special case to treat this return type. 21 */ 22 alias MapResult(E) = Either!(StreamError, "error", E, "element"); 23 24 /** 25 * An input stream that wraps another stream, and applies a function to each 26 * element read from that stream. 27 */ 28 struct MappingInputStream(alias f, S) if (isSomeInputStream!S && isMappingFunction!(typeof(f), StreamType!S)) { 29 alias Ein = StreamType!S; 30 alias Eout = MappingFunctionReturnType!(typeof(f)); 31 32 private S stream; 33 34 this(S stream) { 35 this.stream = stream; 36 } 37 38 StreamResult readFromStream(Eout[] buffer) { 39 Ein[1] inputBuffer; 40 for (uint i = 0; i < buffer.length; i++) { 41 StreamResult result = this.stream.readFromStream(inputBuffer); 42 if (result.hasError) return result; 43 if (result.count == 0) return StreamResult(i); 44 static if (returnsMapResult!(typeof(f))) { 45 MapResult!Eout mapResult = f(inputBuffer[0]); 46 if (mapResult.hasError) return StreamResult(mapResult.error); 47 buffer[i] = mapResult.element; 48 } else { 49 buffer[i] = f(inputBuffer[0]); 50 } 51 } 52 return StreamResult(cast(uint) buffer.length); 53 } 54 } 55 56 /** 57 * Creates a mapping input stream that applies the function `f` to all elements 58 * read from the stream. 59 * Params: 60 * stream = The stream to wrap. 61 * Returns: A mapping input stream. 62 */ 63 MappingInputStream!(f, S) mappingInputStreamFor(alias f, S)( 64 S stream 65 ) if (isSomeInputStream!S && isMappingFunction!(typeof(f), StreamType!S)) { 66 return MappingInputStream!(f, S)(stream); 67 } 68 69 unittest { 70 import streams; 71 int[3] buf1 = [1, 2, 3]; 72 auto sIn1 = arrayInputStreamFor(buf1); 73 auto map1 = MappingInputStream!((int a) => a + 1, typeof(&sIn1))(&sIn1); 74 assert(isInputStream!(typeof(map1), int)); 75 int[3] bufOut1; 76 assert(map1.readFromStream(bufOut1) == StreamResult(3)); 77 assert(bufOut1 == [2, 3, 4]); 78 sIn1.reset(); 79 80 import std.stdio; 81 // Test that the function helper works too. 82 auto map2 = mappingInputStreamFor!((int a) => a - 1)(&sIn1); 83 int[3] bufOut2; 84 assert(map2.readFromStream(bufOut2) == StreamResult(3)); 85 assert(bufOut2 == [0, 1, 2]); 86 } 87 88 /** 89 * An output stream that applies a function to each element that's written to 90 * it before writing to the underlying stream. 91 */ 92 struct MappingOutputStream(alias f, S) if ( 93 isSomeOutputStream!S && 94 isSomeMappingFunction!(typeof(f)) && 95 is(MappingFunctionReturnType!(typeof(f)) == StreamType!S) 96 ) { 97 alias Ein = MappingFunctionInputType!(typeof(f)); 98 alias Eout = StreamType!S; 99 100 private S stream; 101 102 this(S stream) { 103 this.stream = stream; 104 } 105 106 StreamResult writeToStream(Ein[] buffer) { 107 Eout[1] outputBuffer = [Eout.init]; 108 foreach (element; buffer) { 109 static if (returnsMapResult!(typeof(f))) { 110 MapResult!Eout mapResult = f(element); 111 if (mapResult.hasError) return StreamResult(mapResult.error); 112 outputBuffer[0] = mapResult.element; 113 } else { 114 outputBuffer[0] = f(element); 115 } 116 StreamResult writeResult = this.stream.writeToStream(outputBuffer); 117 if (writeResult.hasError) return writeResult; 118 if (writeResult.count != 1) { 119 return StreamResult(StreamError("Failed to write element.", writeResult.count)); 120 } 121 } 122 return StreamResult(cast(uint) buffer.length); 123 } 124 } 125 126 /** 127 * Creates a mapping output stream that applies the function `f` to all elements 128 * written to the stream. 129 * Params: 130 * stream = The stream to wrap. 131 * Returns: A mapping output stream. 132 */ 133 MappingOutputStream!(f, S) mappingOutputStreamFor(alias f, S)( 134 S stream 135 ) if ( 136 isSomeOutputStream!S && 137 isSomeMappingFunction!(typeof(f)) && 138 is(MappingFunctionReturnType!(typeof(f)) == StreamType!S) 139 ) { 140 return MappingOutputStream!(f, S)(stream); 141 } 142 143 unittest { 144 import streams; 145 int[3] buf1 = [1, 2, 3]; 146 auto sOut1 = arrayOutputStreamFor!double; 147 auto mapOut1 = mappingOutputStreamFor!((int a) => 0.25 * a)(&sOut1); 148 assert(isOutputStream!(typeof(mapOut1), int)); 149 assert(mapOut1.writeToStream(buf1) == StreamResult(3)); 150 assert(sOut1.toArrayRaw() == [0.25, 0.5, 0.75]); 151 152 // Test writing to an error stream. 153 auto sOut2 = ErrorOutputStream!ubyte(); 154 auto mapOut2 = mappingOutputStreamFor!((ulong a) => cast(ubyte) (a & 0xFF))(&sOut2); 155 ulong[2] buf2 = [123, 456]; 156 assert(mapOut2.writeToStream(buf2).hasError); 157 // Also for a stream that doesn't write anything. 158 auto sOut3 = NoOpOutputStream!ubyte(); 159 auto mapOut3 = mappingOutputStreamFor!((ulong a) => cast(ubyte) (a & 0xFF))(&sOut3); 160 assert(mapOut3.writeToStream(buf2).hasError); 161 } 162 163 private bool isSomeMappingFunction(F)() { 164 import std.traits : isSomeFunction, ReturnType, isInstanceOf, TemplateArgsOf, Parameters; 165 static if (isSomeFunction!F) { 166 static if (Parameters!(F).length == 1) { 167 return !is(ReturnType!F == void); 168 } else { 169 return false; 170 } 171 } else { 172 return false; 173 } 174 } 175 176 unittest { 177 auto f1 = (int a) => cast(char) a; 178 assert(isSomeMappingFunction!(typeof(f1))); 179 auto f2 = (ubyte u) => MapResult!bool(true); 180 assert(isSomeMappingFunction!(typeof(f2))); 181 auto f3 = () => -1; 182 assert(!isSomeMappingFunction!(typeof(f3))); 183 } 184 185 private template MappingFunctionInputType(F) if (isSomeMappingFunction!F) { 186 import std.traits : Parameters; 187 alias MappingFunctionInputType = Parameters!(F)[0]; 188 } 189 190 private bool isMappingFunction(F, Ein)() if (isSomeMappingFunction!F) { 191 return is(MappingFunctionInputType!F == Ein); 192 } 193 194 private template MappingFunctionReturnType(F) if (isSomeMappingFunction!F) { 195 import std.traits : ReturnType, TemplateArgsOf, isInstanceOf; 196 alias r = ReturnType!F; 197 static if (isInstanceOf!(Either, r)) { 198 alias args = TemplateArgsOf!r; 199 static if (args[1] == "element") { 200 alias MappingFunctionReturnType = args[0]; 201 } else { 202 alias MappingFunctionReturnType = args[2]; 203 } 204 } else { 205 alias MappingFunctionReturnType = r; 206 } 207 } 208 209 unittest { 210 auto f1 = (int a) => 0.5f * a; 211 assert(is(MappingFunctionReturnType!(typeof(f1)) == float)); 212 auto f2 = (bool b) => !b; 213 assert(is(MappingFunctionReturnType!(typeof(f2)) == bool)); 214 auto f3 = (ubyte b) => MapResult!char('A'); 215 assert(is(MappingFunctionReturnType!(typeof(f3)) == char)); 216 } 217 218 private bool returnsMapResult(F)() if (isSomeMappingFunction!F) { 219 import std.traits : ReturnType, isInstanceOf; 220 alias r = ReturnType!F; 221 return isInstanceOf!(Either, r); 222 } 223 224 unittest { 225 auto f1 = (int a) => MapResult!bool(a > 5); 226 assert(returnsMapResult!(typeof(f1))); 227 auto f2 = (bool b) => !b; 228 assert(!returnsMapResult!(typeof(f2))); 229 }