1 /** 2 * A collection of compile-time functions to help in identifying stream types 3 * and related flavors of them. 4 * 5 * Streams come in two main flavors: ${B input} and ${B output} streams. 6 * 7 * ${B Input streams} are defined by the presence of a `read` function with the 8 * following signature: 9 * ```d 10 * int read(DataType[] buffer) 11 * ``` 12 * 13 * ${B Output streams} are defined by the presence of a `write` function with 14 * the following signature: 15 * ```d 16 * int read(DataType[] buffer) 17 * ``` 18 * 19 * Usually these functions can be used as [Template Constraints](https://dlang.org/spec/template.html#template_constraints) 20 * when defining your own functions and symbols to work with streams. 21 * ```d 22 * void useBytes(S)(S stream) if (isInputStream!(S, ubyte)) { 23 * ubyte[] buffer = new ubyte[8192]; 24 * int bytesRead = stream.read(buffer); 25 * // Do something with the data. 26 * } 27 * ``` 28 */ 29 module streams.primitives; 30 31 import std.traits; 32 import std.range; 33 34 /** 35 * Determines if the given template argument is some form of input stream, 36 * where an input stream is anything with a `read` method that takes a single 37 * array parameter, and returns an integer number of elements that were read, 38 * or -1 in case of error. This method does not care about the type of elements 39 * that can be read by the stream. 40 * 41 * Returns: `true` if the given argument is an input stream type. 42 */ 43 bool isSomeInputStream(StreamType)() { 44 // Note: We use a cascading static check style so the compiler runs these checks in this order. 45 static if (hasMember!(StreamType, "read")) { 46 alias func = StreamType.read; 47 static if (isCallable!func && is(ReturnType!func == int)) { 48 static if (Parameters!func.length == 1) { 49 return isDynamicArray!(Parameters!func[0]); 50 } else { return false; } 51 } else { return false; } 52 } else { return false; } 53 } 54 55 unittest { 56 struct S1 { 57 int read(ubyte[] buffer) { return 0; } 58 } 59 assert(isSomeInputStream!S1); 60 struct S2 { 61 int read(bool[] buffer) { return 42; } 62 } 63 assert(isSomeInputStream!S2); 64 struct S3 { 65 int read(bool[] buffer, int otherArg) { return 0; } 66 } 67 assert(!isSomeInputStream!S3); 68 struct S4 { 69 void read(long[] buffer) {} 70 } 71 assert(!isSomeInputStream!S4); 72 struct S5 { 73 int read = 10; 74 } 75 assert(!isSomeInputStream!S5); 76 struct S6 {} 77 assert(!isSomeInputStream!S6); 78 interface I1 { 79 int read(ubyte[] buffer); 80 } 81 assert(isSomeInputStream!I1); 82 class C1 { 83 int read(ubyte[] buffer) { return 0; } 84 } 85 assert(isSomeInputStream!C1); 86 } 87 88 /** 89 * Determines if the given template argument is some form of output stream, 90 * where an output stream is anything with a `write` method that takes a single 91 * array parameter, and returns an integer number of elements that were read, 92 * or -1 in case of error. This method does not care about the type of elements 93 * that can be read by the stream. 94 * 95 * Returns: `true` if the given argument is an output stream type. 96 */ 97 bool isSomeOutputStream(StreamType)() { 98 // Note: We use a cascading static check style so the compiler runs these checks in this order. 99 static if (hasMember!(StreamType, "write")) { 100 alias func = StreamType.write; 101 static if (isCallable!func && is(ReturnType!func == int)) { 102 static if (Parameters!func.length == 1) { 103 return isDynamicArray!(Parameters!func[0]); 104 } else { return false; } 105 } else { return false; } 106 } else { return false; } 107 } 108 109 unittest { 110 struct S1 { 111 int write(ubyte[] buffer) { return 0; } 112 } 113 assert(isSomeOutputStream!S1); 114 struct S2 { 115 int write(bool[] buffer) { return 42; } 116 } 117 assert(isSomeOutputStream!S2); 118 struct S3 { 119 int write(bool[] buffer, int otherArg) { return 0; } 120 } 121 assert(!isSomeOutputStream!S3); 122 struct S4 { 123 void write(long[] buffer) {} 124 } 125 assert(!isSomeOutputStream!S4); 126 struct S5 { 127 int write = 10; 128 } 129 assert(!isSomeOutputStream!S5); 130 struct S6 {} 131 assert(!isSomeOutputStream!S6); 132 } 133 134 /** 135 * Determines if the given stream type is an input stream for reading data of 136 * the given type. 137 * Returns: `true` if the given stream type is an input stream. 138 */ 139 bool isInputStream(StreamType, DataType)() { 140 static if (isSomeInputStream!StreamType) { 141 return is(Parameters!(StreamType.read)[0] == DataType[]); 142 } else { 143 return false; 144 } 145 } 146 147 unittest { 148 // Test a valid input stream. 149 struct S1 { 150 int read(ubyte[] buffer) { 151 return 0; // Don't do anything with the data. 152 } 153 } 154 assert(isInputStream!(S1, ubyte)); 155 156 // Test a few invalid input streams. 157 struct S2 {} 158 assert(!isInputStream!(S2, ubyte)); 159 struct S3 { 160 void read(ubyte[] buffer) { 161 // Invalid return type! 162 } 163 } 164 assert(!isInputStream!(S3, ubyte)); 165 struct S4 { 166 int read() { 167 return 0; // Missing required arguments. 168 } 169 } 170 assert(!isInputStream!(S4, ubyte)); 171 class C1 { 172 int read(char[] buffer) { 173 return 0; 174 } 175 } 176 assert(isInputStream!(C1, char)); 177 } 178 179 /** 180 * Wraps an existing input stream as a Phobos-style input range, to make any 181 * input stream compatible with functions that take input ranges. The given 182 * stream is stored as a pointer in the underlying range implementation, so you 183 * should still manage ownership of the original stream. 184 * 185 * ```d 186 * import std.range.primitives : isInputRange; 187 * import streams; 188 * 189 * auto stream = inputStreamFor!int([1, 2, 3]); 190 * auto range = asInputRange!int(stream); 191 * assert(isInputRange!(typeof(range))); 192 * ``` 193 * Params: 194 * stream = The stream to wrap. 195 * Returns: The input range. 196 */ 197 auto asInputRange(E, S)(ref S stream) if (isInputStream!(S, E)) { 198 struct InputStreamRange { 199 import std.typecons; 200 201 private S* stream; 202 private Nullable!E lastElement; 203 private int lastRead; 204 205 this(ref S stream) { 206 this.stream = &stream; 207 // Initialize the range with one element. 208 this.popFront(); 209 } 210 211 void popFront() { 212 E[1] buffer; 213 this.lastRead = this.stream.read(buffer); 214 if (this.lastRead > 0) { 215 this.lastElement = nullable(buffer[0]); 216 } else { 217 this.lastElement = Nullable!E.init; 218 } 219 } 220 221 bool empty() { 222 return this.lastRead < 1; 223 } 224 225 E front() { 226 return this.lastElement.get(); 227 } 228 } 229 return InputStreamRange(stream); 230 } 231 232 unittest { 233 import streams; 234 auto s = inputStreamFor!ubyte([1, 2, 3]); 235 auto r = asInputRange!ubyte(s); 236 assert(isInputRange!(typeof(r))); 237 } 238 239 /** 240 * Determines if the given stream type is an output stream for writing data of 241 * the given type. 242 * Returns: `true` if the given stream type is an output stream. 243 */ 244 bool isOutputStream(StreamType, DataType)() { 245 static if (isSomeOutputStream!StreamType) { 246 return is(Parameters!(StreamType.write)[0] == DataType[]); 247 } else { 248 return false; 249 } 250 } 251 252 unittest { 253 // Test a valid output stream. 254 struct S1 { 255 int write(ref ubyte[] buffer) { 256 return 0; // Don't do anything with the data. 257 } 258 } 259 assert(isOutputStream!(S1, ubyte)); 260 261 // Test a few invalid output streams. 262 struct S2 {} 263 assert(!isOutputStream!(S2, ubyte)); 264 struct S3 { 265 void write(ubyte[] buffer) { 266 // Invalid return type! 267 } 268 } 269 assert(!isOutputStream!(S3, ubyte)); 270 struct S4 { 271 int write() { 272 return 0; // Missing required arguments. 273 } 274 } 275 assert(!isOutputStream!(S4, ubyte)); 276 } 277 278 /** 279 * Wraps an existing output stream as a Phobos-style output range with a 280 * `put` method, to make any output stream compatible with functions that take 281 * output ranges. The given stream is stored as a pointer in the underlying 282 * range implementation, so you should still manage ownership of the original 283 * stream. 284 * 285 * ```d 286 * import std.range.primitives : isOutputRange; 287 * import streams; 288 * 289 * auto stream = ArrayOutputStream!int(); 290 * auto range = asOutputRange!int(stream); 291 * assert(isOutputRange!(typeof(range), int)); 292 * ``` 293 * Params: 294 * stream = The stream to wrap. 295 * Returns: The output range. 296 */ 297 auto asOutputRange(E, S)(ref S stream) if (isOutputStream!(S, E)) { 298 struct StreamOutputRange { 299 private S* stream; 300 301 void put(E[] buffer) { 302 this.stream.write(buffer); 303 } 304 } 305 return StreamOutputRange(&stream); 306 } 307 308 unittest { 309 import streams; 310 auto s = ArrayOutputStream!ubyte(); 311 auto o = asOutputRange!ubyte(s); 312 assert(isOutputRange!(typeof(o), ubyte)); 313 } 314 315 /** 316 * Determines if the given template argument is a stream of any kind; that is, 317 * it is at least implementing the functions required to be an input or output 318 * stream. 319 * Returns: `true` if the given argument is some stream. 320 */ 321 bool isSomeStream(StreamType)() { 322 return isSomeInputStream!StreamType || isSomeOutputStream!StreamType; 323 } 324 325 unittest { 326 struct S1 { 327 int read(ubyte[] buffer) { 328 return 0; 329 } 330 } 331 assert(isSomeStream!S1); 332 struct S2 { 333 int write(ubyte[] buffer) { 334 return 0; 335 } 336 } 337 assert(isSomeStream!S2); 338 struct S3 {} 339 assert(!isSomeStream!S3); 340 } 341 342 /** 343 * Determines if the given stream type is an input or output stream for data of 344 * the given type. 345 * Returns: `true` if the stream type is an input or output stream for the given data type. 346 */ 347 bool isSomeStream(StreamType, DataType)() { 348 return isInputStream!(StreamType, DataType) || isOutputStream(StreamType, DataType); 349 } 350 351 /** 352 * Determines if the given stream type is an input stream for `ubyte` elements. 353 * Returns: `true` if the stream type is a byte input stream. 354 */ 355 bool isByteInputStream(StreamType)() { 356 return isInputStream!(StreamType, ubyte); 357 } 358 359 /** 360 * Determines if the given stream type is an output stream for `ubyte` elements. 361 * Returns: `true` if the stream type is a byte output stream. 362 */ 363 bool isByteOutputStream(StreamType)() { 364 return isOutputStream!(StreamType, ubyte); 365 } 366 367 /** 368 * Determines if the given template argument is a closable stream type, which 369 * defines a `void close()` method as a means to close and/or deallocate the 370 * underlying resource that the stream reads from or writes to. 371 * 372 * Returns: `true` if the given argument is a closable stream. 373 */ 374 bool isClosableStream(StreamType)() { 375 static if ( 376 isSomeStream!StreamType && 377 hasMember!(StreamType, "close") && 378 isCallable!(StreamType.close) 379 ) { 380 alias closeFunction = StreamType.close; 381 alias params = Parameters!closeFunction; 382 return ( 383 allSameType!(void, ReturnType!closeFunction) && 384 params.length == 0 385 ); 386 } else { 387 return false; 388 } 389 } 390 391 unittest { 392 struct S1 { 393 int read(ubyte[] buffer) { 394 return 0; 395 } 396 void close() {} 397 } 398 assert(isClosableStream!S1); 399 struct S2 { 400 int read(ubyte[] buffer) { 401 return 0; 402 } 403 } 404 assert(!isClosableStream!S2); 405 struct S3 {} 406 assert(!isClosableStream!S3); 407 } 408 409 /** 410 * Determines if the given template argument is a flushable stream type, which 411 * is any output stream that defines a `void flush()` method, which should 412 * cause any data buffered by the stream or its resources to be flushed. The 413 * exact nature of how a flush operates is implementation-dependent. 414 * 415 * Returns: `true` if the given argument is a flushable stream. 416 */ 417 bool isFlushableStream(StreamType)() { 418 import std.traits; 419 static if ( 420 isSomeOutputStream!StreamType && 421 hasMember!(StreamType, "flush") && 422 isCallable!(StreamType.flush) 423 ) { 424 alias flushFunction = StreamType.flush; 425 alias params = Parameters!flushFunction; 426 return ( 427 allSameType!(void, ReturnType!flushFunction) && 428 params.length == 0 429 ); 430 } else { 431 return false; 432 } 433 } 434 435 unittest { 436 struct S1 { 437 int write(ubyte[] buffer) { 438 return 0; 439 } 440 void flush() {} 441 } 442 assert(isFlushableStream!S1); 443 struct S2 { 444 int write(ubyte[] buffer) { 445 return 0; 446 } 447 } 448 assert(!isFlushableStream!S2); 449 struct S3 {} 450 assert(!isFlushableStream!S3); 451 } 452 453 /** 454 * An exception that may be thrown if an illegal operation or error occurs 455 * while working with streams. Generally, if an exception is to be thrown while 456 * reading or writing in a stream's implementation, a `StreamException` should 457 * be wrapped around it to provide a common interface for error handling. 458 */ 459 class StreamException : Exception { 460 import std.exception; 461 462 mixin basicExceptionCtors; 463 }