1 /**
2  * iz streams, standard streams implementation with several extention related
3  * to D ranges.
4  */
5 module iz.streams;
6 
7 import
8     core.exception;
9 import
10     std..string, std.range, std.traits, std.exception;
11 import
12     iz.types, iz.memory, iz.sugar;
13 
14 /// FileStream creation mode 'There': opens only if exists.
15 immutable int cmThere    = 0;
16 /// FileStream creation mode 'NotThere': creates only if not exists.
17 immutable int cmNotThere = 1;
18 /// FileStream creation mode 'Always': creates if not exists otherwise open.
19 immutable int cmAlways   = 2;
20 
21 version (Windows)
22 {
23     import core.sys.windows.windows, std.windows.syserror;
24 
25     private immutable READ_WRITE =  GENERIC_READ | GENERIC_WRITE;
26     private immutable FILE_SHARE_ALL = FILE_SHARE_READ | FILE_SHARE_WRITE;
27 
28     extern(Windows) @nogc export BOOL SetEndOfFile(in HANDLE hFile);
29 
30     extern(Windows) export HANDLE CreateNamedPipeA(
31        LPCTSTR lpName,
32        DWORD dwOpenMode,
33        DWORD dwPipeMode,
34        DWORD nMaxInstances,
35        DWORD nOutBufferSize,
36        DWORD nInBufferSize,
37        DWORD nDefaultTimeOut,
38        LPSECURITY_ATTRIBUTES lpSecurityAttributes
39     );
40 
41     alias StreamHandle = HANDLE;
42 
43     private immutable int skBeg = FILE_BEGIN;
44     private immutable int skCur = FILE_CURRENT;
45     private immutable int skEnd = FILE_END;
46 
47     /// defines the FileStream share modes.
48     immutable int shNone = 0;
49     /// ditto
50     immutable int shRead = FILE_SHARE_READ;
51     /// ditto
52     immutable int shWrite= FILE_SHARE_WRITE;
53     /// ditto
54     immutable int shAll  = shWrite | shRead;
55 
56     /// defines the FileStream access modes.
57     immutable uint acRead = GENERIC_READ;
58     /// ditto
59     immutable uint acWrite= GENERIC_WRITE;
60     /// ditto
61     immutable uint acAll  = acRead | acWrite;
62 
63     /// returns true if aHandle is valid.
64     bool isHandleValid(StreamHandle aHandle) pure @nogc @safe
65     {
66         return (aHandle != INVALID_HANDLE_VALUE);
67     }
68 
69     /// translates a cmXX to a platform specific option.
70     int cmToSystem(int aCreationMode) pure @nogc @safe
71     {
72         switch(aCreationMode)
73         {
74             case cmThere: return OPEN_EXISTING;
75             case cmNotThere: return CREATE_NEW;
76             case cmAlways: return OPEN_ALWAYS;
77             default: return OPEN_ALWAYS;
78         }
79     }
80 
81 }
82 else version (Posix)
83 {
84     import core.sys.posix.fcntl, core.sys.posix.unistd;
85     import core.sys.posix.stdio;
86 
87     static import std.conv;
88 
89     alias StreamHandle = int;
90 
91     // Stream seek modes, used as platform-specific constants in SeekMode.
92     private immutable int skBeg = 0;//SEEK_SET;
93     private immutable int skCur = 1;//SEEK_CUR;
94     private immutable int skEnd = 2;//SEEK_END;
95 
96     /// share modes. (does not allow execution)
97     immutable int shNone = std.conv.octal!600;
98     immutable int shRead = std.conv.octal!644;
99     immutable int shWrite= std.conv.octal!622;
100     immutable int shAll  = std.conv.octal!666;
101 
102     /// access modes.
103     immutable uint acRead = O_RDONLY;
104     immutable uint acWrite= O_WRONLY;
105     immutable uint acAll  = O_RDWR;
106 
107     /// pipe direction
108     immutable uint pdIn  = 0;
109     immutable uint pdOut = 0;
110     immutable uint pdAll = 0;
111 
112     /// returns true if aHandle is valid.
113     bool isHandleValid(StreamHandle aHandle) pure @nogc @safe
114     {
115         return (aHandle > -1);
116     }
117 
118     /// translates a cmXX to a platform specific option.
119     int cmToSystem(int aCreationMode)  pure @nogc @safe
120     {
121         switch(aCreationMode)
122         {
123             case cmThere: return 0;
124             case cmNotThere: return O_CREAT | O_EXCL;
125             case cmAlways: return O_CREAT;
126             default: return O_CREAT;
127         }
128     }
129 }
130 
131 /**
132  * Enumerates the Stream seek modes.
133  *
134  * Bugs:
135  *      https://issues.dlang.org/show_bug.cgi?id=13975
136  */
137 enum SeekMode: ubyte
138 {
139     beg = skBeg, /// seek from the beginning.
140     cur = skCur, /// seek from the current position.
141     end = skEnd  /// seek from the ending.
142 }
143 
144 /**
145  * An implementer can save to and load from a Stream.
146  */
147 interface StreamPersist
148 {
149     /// Saves something in aStream
150     void saveToStream(Stream stream);
151     /// Loads something from aStream. aStream initial position is preserved.
152     void loadFromStream(Stream stream);
153 }
154 
155 /**
156  * An implementer can save to and load from a file with a UTF8 file name.
157  */
158 interface FilePersist8
159 {
160     /// Saves something to aFilename.
161     void saveToFile(const(char)[] aFilename);
162     /// Loads something to aFilename.
163     void loadFromFile(const(char)[] aFilename);
164 }
165 
166 /// Generates all the typed write() and read() of a Stream implementation.
167 string genReadWriteVar()
168 {
169     import std.ascii: toUpper;
170     string result;
171     char[] type;
172     foreach(T; BasicTypes)
173     {
174         type = T.stringof.dup;
175         type[0] = toUpper(type[0]);
176         result ~= "alias read" ~ type ~ "= readVariable!" ~ T.stringof ~  ';';
177         result ~= "alias write" ~ type ~ "= writeVariable!" ~ T.stringof ~  ';';
178     }
179     return result;
180 }
181 
182 /**
183  * Defines the members of a Stream.
184  */
185 interface Stream
186 {
187     /**
188      * Reads from the Stream.
189      * Params:
190      *      buffer = A pointer to the target.
191      *      count = The number of bytes to read.
192      * Returns:
193      *      The count of bytes that's been read.
194      */
195     @nogc size_t read(Ptr buffer, size_t count);
196 
197     /**
198      * Reads a typed variable.
199      *
200      * Typed readers are generated for each type in iz.types.BasicTypes
201      * and they are named readInt, readChar, etc.
202      *
203      * Params:
204      *      T = The type of the variable to read.
205      *
206      * Returns:
207      *      A variable of type T. This value can be undefined if the stream
208      *      position does not allow to read a T.
209      */
210     T readVariable(T)()
211     {
212         T result;
213         read(&result, T.sizeof);
214         return result;
215     }
216 
217     /**
218      * Writes the content of a buffer.
219      *
220      * Params:
221      *      buffer = A pointer to the buffer to write.
222      *      count = The size of the buffer.
223      * Returns:
224      *      the count of bytes that's been written.
225      */
226     @nogc size_t write(const Ptr buffer, size_t count);
227 
228     /**
229      * Writes a typed value.
230      *
231      * Typed writers are generated for each type in iz.types.BasicTypes
232      * and they are named writeInt, writeChar, etc.
233      *
234      * Params:
235      *      T = The type of the variable to read.
236      *      value = the T to write.
237      *
238      * Returns:
239      *      the count of bytes that's been written (either T.sizeof or 0).
240      */
241     size_t writeVariable(T)(T value)
242     {
243         return write(&value, T.sizeof);
244     }
245 
246     /**
247      * Sets the stream position.
248      *
249      * Params:
250      *      offset = The offset from the start position.
251      *      mode = The start position. Either the Stream.position if
252      *      $(D mode == SeekMode.skCur), 0 if $(D mode == SeekMode.skBeg) or
253      *      Stream.size if $(D mode == SeekMode.skEnd).
254      * Returns:
255      *      the new position.
256      */
257     @nogc long seek(long offset, SeekMode mode);
258     /// ditto
259     @nogc int seek(int offset, SeekMode mode);
260 
261     /**
262      * Sets or gets the stream size.
263      */
264     @nogc long size();
265     /// ditto
266     @nogc void size(long value);
267     /// ditto
268     @nogc void size(int value);
269 
270     /**
271      * Sets or gets the stream position.
272      */
273     @nogc long position();
274     /// ditto
275     @nogc void position(long value);
276     /// ditto
277     @nogc void position(int value);
278 
279     /**
280      * Resets the stream size to 0.
281      */
282     @nogc void clear();
283 
284     /// Support for the concatenation operator.
285     void opOpAssign(string op)(Stream rhs)
286     {
287         static if(op == "~")
288         {
289             Stream lhs = this;
290             auto immutable stored = rhs.position;
291 
292             lhs.seek(0, SeekMode.end);
293             rhs.seek(0, SeekMode.beg);
294 
295             size_t read;
296             size_t buff_sz = 4096;
297             auto buff = getMem(buff_sz);
298             scope (exit)
299             {
300                 rhs.position = stored;
301                 freeMem(buff);
302             }
303 
304             while (true)
305             {
306                 read = rhs.read(buff, buff_sz);
307                 if (read == 0) return;
308                 lhs.write(buff, read);
309             }
310         }
311         else static assert(0, "Stream.opOpAssign not implemented for " ~ op);
312     }
313 
314     mixin(genReadWriteVar);
315 }
316 
317 /**
318  * Helper designed to construct a new StreamRange allocated on the C heap.
319  *
320  * Params:
321  *      ST = The Stream descendant for which the range will be created.
322  *      T = The range element type.
323  * Returns:
324  *      A pointer to a StreamRange that has to be free manually with $(D destruct()).
325  */
326 auto streamRange(T, ST)(ST st)
327 if (is(ST : Stream))
328 {
329     return StreamRange!(ST,T)(st);
330 }
331 
332 /**
333  * Input, forward and bidirectional range for a Stream.
334  *
335  * Params:
336  *      ST = The Stream descendant for which the range will be created.
337  *      T = The range element type.
338  */
339 struct StreamRange(ST, T)
340 if (is(ST : Stream))
341 {
342     private:
343 
344         ulong _fpos, _bpos;
345         ST _str;
346 
347     public:
348 
349         /// initializes a StreamRange with a Stream instance.
350         this(ST stream) @nogc
351         {
352             _str = stream;
353             _bpos = stream.size - T.sizeof;
354         }
355 
356         /// InputRange primitive.
357         T front() @nogc
358         {
359             T result;
360             _str.position = _fpos;
361             _str.read(&result, T.sizeof);
362             _str.position = _fpos;
363             return result;
364         }
365 
366         /// Bidirectional primitive.
367         T back() @nogc
368         {
369             T result;
370             _str.position = _bpos;
371             _str.read(&result, T.sizeof);
372             _str.position = _bpos;
373             return result;
374         }
375 
376         /// InputRange primitive.
377         @safe void popFront() @nogc
378         {
379             _fpos += T.sizeof;
380         }
381 
382         /// Bidirectional primitive.
383         @safe void popBack() @nogc
384         {
385             _bpos -= T.sizeof;
386         }
387 
388         /// InputRange & BidirectionalRange primitive.
389         bool empty() @nogc
390         {
391             return (_fpos == _str.size) || (_fpos + T.sizeof > _str.size)
392                     || (_bpos == 0) || (_bpos - T.sizeof < 0);
393         }
394 
395         /// ForwardRange primitive.
396         typeof(this) save() @nogc
397         {
398             typeof(this) result = typeof(this)(_str);
399             result._fpos = _fpos;
400             result._bpos = _bpos;
401             return result;
402         }
403 }
404 
405 unittest
406 {
407     uint i;
408     MemoryStream str = construct!MemoryStream;
409     scope(exit) destruct(str);
410 
411     ushort[] src1 = [0,1,2,3,4,5,6,7,8,9];
412     str.write(src1.ptr, src1.length * ushort.sizeof);
413     auto rng1 = construct!(StreamRange!(MemoryStream,ushort))(str);
414     scope(exit) destruct(rng1);
415 
416     // foreach processes a copy of rng.
417     // http://forum.dlang.org/thread/jp16ni$fug$1@digitalmars.com
418     foreach(ushort v; *rng1)
419     {
420         assert(v == src1[i]);
421         ++i;
422     }
423     assert(rng1._fpos == 0);
424 
425     // bidir
426     foreach_reverse(ushort v; *rng1)
427     {
428         --i;
429         assert(v == src1[i]);
430     }
431     assert(rng1._fpos == 0);
432 
433     i = 0;
434     while(!rng1.empty)
435     {
436         assert(rng1.front == src1[i]);
437         ++i;
438         rng1.popFront;
439     }
440     assert(rng1.empty);
441 
442     // other type + streamRange type inference
443     str.clear;
444     long[] src2 = [10,11,12,13,14,15,16,17,18,19];
445     str.write(src2.ptr, src2.length * long.sizeof);
446     auto rng2 = streamRange!long(str);
447     scope(exit) destruct(rng2);
448     foreach(long v; rng2)
449     {
450         assert(v == src2[i - 10]);
451         ++i;
452     }
453 
454     // test empty with a non full element at the tail
455     ubyte tail = 98;
456     str.position = str.size;
457     str.write(&tail,1);
458     auto rng3 = streamRange!long(str);
459     scope(exit) destruct(rng3);
460     while(!rng3.empty) rng3.popFront;
461     str.position = rng3._fpos;
462     tail = 0;
463     str.read(&tail,1);
464     assert(tail == 98);
465 }
466 
467 
468 /**
469  * Copies the content of a Stream to another one.
470  *
471  * The position in the source is preserved.
472  *
473  * Params:
474  *      source = The Stream instance whose content will be copied.
475  *      target = The Stream instance whose content will be replaced.
476  */
477 void copyStream(Stream source, Stream target) @nogc
478 {
479     auto immutable oldpos = source.position;
480     auto buffsz = 4096;
481     auto buff = getMem(buffsz);
482     if (!buff) throwStaticEx!OutOfMemoryError;
483 
484     scope(exit)
485     {
486         source.position = oldpos;
487         freeMem(buff);
488     }
489 
490     source.position = 0;
491     target.size = source.size;
492     target.position = 0;
493 
494     while(source.position != source.size)
495     {
496         auto cnt = source.read(buff, buffsz);
497         target.write(buff, cnt);
498     }
499 }
500 
501 unittest
502 {
503     ubyte[] _a = [0x11,0x22,0x33,0x44];
504     ubyte[] _b = [0x55,0x66,0x77,0x88];
505     auto a = construct!MemoryStream;
506     auto b = construct!MemoryStream;
507     a.write(_a.ptr, 4);
508     b.write(_b.ptr, 4);
509     a ~= b;
510     a.position = 0;
511     ulong g;
512     a.read(&g,8);
513     assert(a.size == 8);
514     version(LittleEndian) assert(g == 0x8877665544332211);
515     version(BigEndian) assert(g == 0x1122334455667788);
516     a.destruct;
517     b.destruct;
518 }
519 
520 
521 /**
522  * Writes an input range to a stream.
523  *
524  * A failure can be verified by testing if the range is empty after the call.
525  *
526  * Params:
527  *      target = A Stream instance.
528  *      r = An input range.
529  */
530 void writeRange(R)(Stream target, R r)
531 if (isInputRange!R)
532 {
533     alias T = ElementType!R;
534     size_t c = void;
535     T t;
536     while (!r.empty)
537     {
538         t = r.front;
539         c = target.write(&t, T.sizeof);
540         if (!c) break;
541         r.popFront;
542     }
543 }
544 
545 @nogc unittest
546 {
547     auto rng = iota(0u,100u);
548     auto rng1 = rng.save;
549     MemoryStream str = construct!MemoryStream;
550     scope(exit) str.destruct;
551     str.writeRange(rng);
552     assert(str.size == 100 * 4);
553     auto rng2 = streamRange!uint(str);
554     while (!rng2.empty)
555     {
556         assert(rng2.front == rng1.front);
557         rng1.popFront;
558         rng2.popFront;
559     }
560 }
561 
562 
563 /**
564  * Writes an unidimensional array in a stream.
565  *
566  * By default writes the array length as a ulong and then always the array content.
567  * While writeRange() already allows to write an array, there is no clue about
568  * its length. Additionally this function is more efficient.
569  *
570  * Params:
571  *      WriteLength = When set to true (the default), the array length is written.
572  *      str = The Stream where data are written.
573  *      t = The array to write.
574  */
575 void writeArray(bool WriteLength = true, T)(Stream str, auto ref T t)
576 if (isArray!T && !isMultiDimensionalArray!T)
577 {
578     static if (WriteLength) str.writeUlong(t.length);
579     str.write(t.ptr, t.length * typeof(T.init[0]).sizeof);
580 }
581 
582 /**
583  * Reads an unidimensional array from a stream.
584  *
585  * By default reads the array length as a ulong and then always the array content.
586  * Params:
587  *      ReadLength = When set to true (the default), the array length is read.
588  *      Otherwise the data are read according to the current array length.
589  *      str = The Stream where data are read.
590  *      t = The array to read.
591  */
592 void readArray(bool ReadLength = true, T)(Stream str, ref T t)
593 if (isArray!T && !isMultiDimensionalArray!T)
594 {
595     static if (ReadLength)
596         t.length = cast(uint) str.readUlong;
597     str.read(t.ptr, t.length * typeof(T.init[0]).sizeof);
598 }
599 
600 unittest
601 {
602     auto src = "0123456789".dup;
603     MemoryStream str = construct!MemoryStream;
604     str.writeArray(src);
605     assert(str.size == ulong.sizeof + src.length);
606     str.position = 0;
607     src = "azertyuiop".dup;
608     str.readArray(src);
609     assert(src == "0123456789");
610 
611     str.clear;
612     str.writeArray!false(src);
613     assert(str.size == src.length);
614     str.position = 0;
615     src = "az".dup;
616     str.readArray!false(src);
617     assert(src == "01");
618 
619     destruct(str);
620 }
621 
622 /**
623  * Decodes an UTF8 line from a Stream.
624  *
625  * Params:
626  *      keepTerminator = Indicates wether the line ending is included in the result.
627  *      str = The Stream to read.
628  * Returns:
629  *      A dchar input range that represents a line.
630  */
631 auto decodeLine(bool keepTerminator = false)(Stream str)
632 in
633 {
634     assert(str !is null);
635 }
636 body
637 {
638     struct LineReader
639     {
640         bool _empty;
641         dchar _front;
642         char[4] _buff;
643         size_t _buffLen;
644         size_t _buffPos = -1;
645 
646         static if (keepTerminator)
647         {
648             bool _nextIsLast;
649         }
650 
651         ///
652         dchar front() @safe @nogc pure nothrow
653         {
654             return _front;
655         }
656 
657         ///
658         bool empty() @safe @nogc pure nothrow
659         {
660             return _empty;
661         }
662 
663         ///
664         void popFront() @trusted
665         {
666             static if (keepTerminator)
667             {
668                 _empty = _nextIsLast;
669                 if (_empty)
670                     return;
671             }
672 
673             if (_buffPos != 0)
674             {
675                 _buffLen = str.read(_buff.ptr, 4);
676                 _buffPos = 0;
677             }
678             if (_buffLen)
679             {
680                 import std.utf: decode;
681                 _front = decode(_buff[], _buffPos);
682 
683                 switch (_front)
684                 {
685                 case '\n':
686                 {
687                     static if (!keepTerminator)
688                     {
689                         _empty = true;
690                         str.position = str.position - _buffLen + _buffPos;
691                         if (str.position == str.size)
692                             _empty = true;
693                     }
694                     else
695                     {
696                         _nextIsLast = true;
697                         str.position = str.position - _buffLen + _buffPos;
698                     }
699                     break;
700                 }
701                 case '\r':
702                 {
703                     if (str.position == str.size)
704                         _empty = true;
705                     else
706                     {
707                         str.position = str.position - _buffLen + _buffPos;
708                         static if (!keepTerminator)
709                         {
710                             const ubyte b = str.readVariable!ubyte;
711                             if (b == '\n')
712                                 _empty = true;
713                             else str.position = str.position - 1;
714                         }
715                     }
716                     break;
717                 }
718                 default: str.position = str.position - _buffLen + _buffPos;
719                 }
720             }
721             else _empty = true;
722         }
723     }
724 
725     LineReader lr;
726     lr.popFront;
727     return lr;
728 }
729 ///
730 unittest
731 {
732     import std.array: array;
733     auto text = "01\r\n23\n45\n".dup;
734     MemoryStream str = construct!MemoryStream();
735     scope(exit) destruct(str);
736     str.write(text.ptr, text.length);
737     str.position = 0;
738     auto _01 = str.decodeLine.array;
739     assert(_01 == "01");
740     auto _23 = str.decodeLine.array;
741     assert(_23 == "23");
742     auto _45 = str.decodeLine.array;
743     assert(_45 == "45");
744     auto term = str.decodeLine.array;
745     assert(term == "");
746 }
747 
748 unittest
749 {
750     import std.array: array;
751     auto text = "01\r23\né5é".dup;
752     MemoryStream str = construct!MemoryStream();
753     scope(exit) destruct(str);
754     str.write(text.ptr, text.length);
755     str.position = 0;
756     auto _01 = str.decodeLine.array;
757     assert(_01 == "01\r23");
758     auto _45 = str.decodeLine.array;
759     assert(_45 == "é5é");
760 }
761 
762 unittest
763 {
764     import std.array: array;
765     auto text = "\n\n\r\n".dup;
766     MemoryStream str = construct!MemoryStream();
767     scope(exit) destruct(str);
768     str.write(text.ptr, text.length);
769     str.position = 0;
770     auto ln0 = str.decodeLine.array;
771     assert(ln0 == "");
772     assert(str.position != str.size);
773     auto ln1 = str.decodeLine.array;
774     assert(ln1 == "");
775     assert(str.position != str.size);
776     auto ln2 = str.decodeLine.array;
777     assert(ln2 == "");
778     assert(str.position == str.size);
779 }
780 
781 unittest
782 {
783     import std.array: array;
784     auto text = "01\r\n23\n45".dup;
785     MemoryStream str = construct!MemoryStream();
786     scope(exit) destruct(str);
787     str.write(text.ptr, text.length);
788     str.position = 0;
789     auto _01 = str.decodeLine!(true).array;
790     assert(_01 == "01\r\n");
791     auto _23 = str.decodeLine!(true).array;
792     assert(_23 == "23\n");
793     auto _45 = str.decodeLine!(true).array;
794     assert(_45 == "45");
795 }
796 
797 /**
798  * Reads a line in a Stream, without decoding.
799  *
800  * Content is assumed to be encoded in UTF-8.
801  *
802  * Params:
803  *      keepTerminator = Indicates wether the line ending is included in the result.
804  *      buffLen = The buffer length, by default 64.
805  *      str = The Stream where a line is read.
806  * Returns:
807  *      An array of char.
808  */
809 const(char)[] readln(bool keepTerminator = false, size_t buffLen = 64)(Stream str)
810 in
811 {
812     assert(str !is null);
813 }
814 body
815 {
816     char[] result;
817     char[buffLen] buffer;
818     size_t count;
819     long strPos;
820     bool checkN;
821     _rd: while (true)
822     {
823         strPos = str.position;
824         count = str.read(buffer.ptr, buffLen);
825         foreach (immutable i; 0..count)
826         {
827             switch(buffer[i])
828             {
829             case '\r':
830                 checkN = true;
831                 break;
832             case '\n':
833                 result ~= buffer[0..i - ubyte(checkN)];
834                 str.position = strPos + i + 1;
835                 static if (keepTerminator)
836                 {
837                     if (checkN)
838                     {
839                         result ~= "\r\n";
840                         checkN = false;
841                     }
842                     else
843                         result ~= "\n";
844                 }
845                 break _rd;
846             default:
847             }
848         }
849         result ~= buffer[0..count];
850         if (count != buffLen)
851             break;
852     }
853     return result;
854 }
855 ///
856 unittest
857 {
858     auto text = "01\r\n23\n4à\n".dup;
859     MemoryStream str = construct!MemoryStream();
860     scope(exit) destruct(str);
861     str.write(text.ptr, text.length);
862     str.position = 0;
863     const _01 = str.readln;
864     assert(_01 == "01", _01);
865     const _23 = str.readln;
866     assert(_23 == "23");
867     const _45 = str.readln;
868     assert(_45 == "4à");
869     const term = str.readln;
870     assert(term == "", term);
871 }
872 
873 unittest
874 {
875     const text = "éà".dup;
876     MemoryStream str = construct!MemoryStream();
877     str.write(text.ptr, text.length);
878     str.position = 0;
879     scope(exit) destruct(str);
880     assert(str.readln == "éà");
881 }
882 
883 unittest
884 {
885     const text = "éàç\n".dup;
886     MemoryStream str = construct!MemoryStream();
887     str.write(text.ptr, text.length);
888     str.position = 0;
889     scope(exit) destruct(str);
890     assert(str.readln == "éàç");
891 }
892 
893 unittest
894 {
895     // 2 buffers
896     const text = "éàééééééééés23d1f32sfdséééééééééééééééééééééé\n".dup;
897     MemoryStream str = construct!MemoryStream();
898     str.write(text.ptr, text.length);
899     str.position = 0;
900     scope(exit) destruct(str);
901     assert(str.readln!(true) == text);
902 }
903 
904 unittest
905 {
906     auto text = "01\r\n23\n4à\r\n".dup;
907     MemoryStream str = construct!MemoryStream();
908     scope(exit) destruct(str);
909     str.write(text.ptr, text.length);
910     str.position = 0;
911     const _01 = str.readln!true;
912     assert(_01 == "01\r\n");
913     const _23 = str.readln!true;
914     assert(_23 == "23\n");
915     const _45 = str.readln!true;
916     assert(_45 == "4à\r\n");
917     const term = str.readln!true;
918     assert(term == "");
919 
920     text = "こんにちは\nは".dup;
921     str.clear;
922     str.write(text.ptr, text.length);
923     str.position = 0;
924     const _1 = str.readln;
925     assert(_1 == "こんにちは");
926     const _2 = str.readln;
927     assert(_2 == "は");
928 }
929 
930 
931 /**
932  * Writes any D array, or a D style chunck in a Stream.
933  *
934  * Params:
935  *      str = The target Stream.
936  *      value = Any D array. Its size and its type determine how many bytes to write.
937  */
938 void write(Stream str, const(void)[] value) @nogc
939 in
940 {
941     assert(str);
942     assert(value.length);
943     assert(value.ptr);
944 }
945 body
946 {
947     str.write(value.ptr, value.length);
948 }
949 ///
950 @nogc unittest
951 {
952     static immutable uint[] a = [0u,1u];
953     MemoryStream str = construct!MemoryStream;
954     write(str, a);
955     assert(str.size == 8);
956     destruct(str);
957 }
958 
959 /**
960  * Reads any D array, or a D style chunck from a Stream.
961  *
962  * Params:
963  *      str = The source Stream.
964  *      value = Any D array. Its size and its type determine how many bytes to read.
965  */
966 void read(Stream str, void[] value) @nogc
967 in
968 {
969     assert(str);
970     assert(value.length);
971     assert(value.ptr);
972 }
973 body
974 {
975     str.read(value.ptr, value.length);
976 }
977 ///
978 @nogc unittest
979 {
980     static uint[] b = [0,0];
981     static immutable uint[] a = [7u,8u];
982     MemoryStream str = construct!MemoryStream(a);
983     read(str, b);
984     assert(b[0] == 7);
985     assert(b[1] == 8);
986     destruct(str);
987 }
988 
989 /**
990  * Base Stream for a descendant that uses the operating system API.
991  *
992  * This class is not directly usable.
993  */
994 abstract class SystemStream: Stream, StreamPersist
995 {
996 
997     mixin inheritedDtor;
998 
999     private
1000     {
1001         StreamHandle _handle;
1002     }
1003     public
1004     {
1005         /// see the Stream interface.
1006         size_t read(Ptr buffer, size_t count) @nogc
1007         {
1008             if (!_handle.isHandleValid) return 0;
1009             version(Windows)
1010             {
1011                 uint cnt = cast(uint) count;
1012                 LARGE_INTEGER Li;
1013                 Li.QuadPart = count;
1014                 ReadFile(_handle, buffer, Li.LowPart, &cnt, null);
1015                 return cnt;
1016             }
1017             version(Posix)
1018             {
1019                 return core.sys.posix.unistd.read(_handle, buffer, count);
1020             }
1021         }
1022 
1023         /// see the Stream interface.
1024         size_t write(const Ptr buffer, size_t count) @nogc
1025         {
1026             if (!_handle.isHandleValid) return 0;
1027             version(Windows)
1028             {
1029                 uint cnt = cast(uint) count;
1030                 LARGE_INTEGER Li;
1031                 Li.QuadPart = count;
1032                 WriteFile(_handle, buffer, Li.LowPart, &cnt, null);
1033                 return cnt;
1034             }
1035             version(Posix)
1036             {
1037                 return core.sys.posix.unistd.write(_handle, buffer, count);
1038             }
1039         }
1040 
1041         /// see the Stream interface.
1042         long seek(long offset, SeekMode mode) @nogc
1043         {
1044             if (!_handle.isHandleValid) return 0;
1045             version(Windows)
1046             {
1047                 LARGE_INTEGER Li;
1048                 Li.QuadPart = offset;
1049                 Li.LowPart = SetFilePointer(_handle, Li.LowPart, &Li.HighPart, mode);
1050                 return Li.QuadPart;
1051             }
1052             version(Posix)
1053             {
1054                 return core.sys.posix.unistd.lseek64(_handle, offset, mode);
1055             }
1056         }
1057 
1058         /// ditto
1059         int seek(int offset, SeekMode mode) @nogc
1060         {
1061             return cast(int) seek(cast(long)offset, mode);
1062         }
1063 
1064         /// see the Stream interface.
1065         long size() @nogc
1066         {
1067             if (!_handle.isHandleValid) return 0;
1068 
1069             const long saved = seek(0L, SeekMode.cur);
1070             const long result = seek(0L, SeekMode.end);
1071             seek(saved, SeekMode.beg);
1072             return result;
1073         }
1074 
1075         /// ditto
1076         void size(long value) @nogc
1077         {
1078             if (!_handle.isHandleValid) return;
1079             if (size == value) return;
1080 
1081             version(Windows)
1082             {
1083                 LARGE_INTEGER Li;
1084                 Li.QuadPart = value;
1085                 SetFilePointer(_handle, Li.LowPart, &Li.HighPart, FILE_BEGIN);
1086                 SetEndOfFile(_handle);
1087             }
1088             version(Posix)
1089             {
1090                 ftruncate64(_handle, value);
1091             }
1092         }
1093 
1094         /// ditto
1095         void size(int value) @nogc
1096         {
1097             if (!_handle.isHandleValid) return;
1098             version(Windows)
1099             {
1100                 SetFilePointer(_handle, value, null, FILE_BEGIN);
1101                 SetEndOfFile(_handle);
1102             }
1103             version(Posix)
1104             {
1105                 ftruncate(_handle, value);
1106             }
1107         }
1108 
1109         /// see the Stream interface.
1110         long position() @nogc
1111         {
1112             return seek(0, SeekMode.cur);
1113         }
1114 
1115         /// ditto
1116         void position(long value)
1117         {
1118             immutable long sz = size;
1119             if (value >  sz) value = sz;
1120             seek(value, SeekMode.beg);
1121         }
1122 
1123         /// ditto
1124         void position(int value) @nogc
1125         {
1126             seek(value, SeekMode.beg);
1127         }
1128 
1129         /**
1130          * Exposes the handle for additional system stream operations.
1131          */
1132         const(StreamHandle) handle() @nogc
1133         {return _handle;}
1134 
1135         /// see the Stream interface.
1136         void clear() @nogc
1137         {
1138             size(0);
1139             position(0);
1140         }
1141 
1142         /// see the Stream interface.
1143         void saveToStream(Stream stream) @nogc
1144         {
1145             copyStream(this, stream);
1146         }
1147 
1148         /// see the Stream interface.
1149         void loadFromStream(Stream stream) @nogc
1150         {
1151             copyStream(stream, this);
1152         }
1153     }
1154 }
1155 
1156 /**
1157  * System stream specialized into reading and writing files, including huge ones
1158  * (up to 2^64 bytes). Several constructors are avalaible with predefined options.
1159  */
1160 class FileStream: SystemStream
1161 {
1162     mixin inheritedDtor;
1163 
1164     private
1165     {
1166         string _filename;
1167     }
1168     public
1169     {
1170         /**
1171          * Constructs the stream and call openPermissive().
1172          */
1173         this(const(char)[] aFilename, int creationMode = cmAlways)
1174         {
1175             openPermissive(aFilename, creationMode);
1176         }
1177 
1178         /**
1179          * Constructs the stream and call open().
1180          */
1181         this(const(char)[] aFilename, int access, int share, int creationMode)
1182         {
1183             open(aFilename, access, share, creationMode);
1184         }
1185 
1186         ~this()
1187         {
1188             closeFile;
1189         }
1190 
1191         /**
1192          * Opens a file for the current user. By default the file is always created or opened.
1193          */
1194         bool openStrict(const(char)[] aFilename, int creationMode = cmAlways)
1195         {
1196             version(Windows)
1197             {
1198                 _handle = CreateFileA(aFilename.toStringz, READ_WRITE, shNone,
1199                     (SECURITY_ATTRIBUTES*).init, cmToSystem(creationMode),
1200                     FILE_ATTRIBUTE_NORMAL, HANDLE.init);
1201             }
1202             version(Posix)
1203             {
1204                 _handle = core.sys.posix.fcntl.open(aFilename.toStringz,
1205                     O_RDWR | cmToSystem(creationMode), shNone);
1206             }
1207 
1208             if (!_handle.isHandleValid)
1209             {
1210                 throw new Exception(format("stream exception: cannot create or open '%s'", aFilename));
1211             }
1212             _filename = aFilename.dup;
1213             return _handle.isHandleValid;
1214         }
1215 
1216         /**
1217          * Opens a shared file. By default the file is always created or opened.
1218          */
1219         final bool openPermissive(const(char)[] aFilename, int creationMode = cmAlways)
1220         {
1221             version(Windows)
1222             {
1223                 _handle = CreateFileA(aFilename.toStringz, READ_WRITE, shAll,
1224                     (SECURITY_ATTRIBUTES*).init, cmToSystem(creationMode), FILE_ATTRIBUTE_NORMAL, HANDLE.init);
1225             }
1226             version(Posix)
1227             {
1228                 _handle = core.sys.posix.fcntl.open(aFilename.toStringz,
1229                     O_RDWR | cmToSystem(creationMode), shAll);
1230             }
1231 
1232             if (!_handle.isHandleValid)
1233             {
1234                 throw new Exception(format("stream exception: cannot create or open '%s'", aFilename));
1235             }
1236             _filename = aFilename.dup;
1237             return _handle.isHandleValid;
1238         }
1239 
1240         /**
1241          * The fully parametric open version. Do not throw. Under POSIX, access can
1242          * be already OR-ed with other, unrelated flags (e.g: O_NOFOLLOW or O_NONBLOCK).
1243          */
1244         bool open(const(char)[] aFilename, int access, int share, int creationMode)
1245         {
1246             version(Windows)
1247             {
1248                 _handle = CreateFileA(aFilename.toStringz, access, share,
1249                     (SECURITY_ATTRIBUTES*).init, cmToSystem(creationMode),
1250                     FILE_ATTRIBUTE_NORMAL, HANDLE.init);
1251             }
1252             version(Posix)
1253             {
1254                 _handle = core.sys.posix.fcntl.open(aFilename.toStringz,
1255                     access | cmToSystem(creationMode), share);
1256             }
1257             _filename = aFilename.dup;
1258             return _handle.isHandleValid;
1259         }
1260 
1261         /**
1262          * Closes the file and flushes any pending changes to the disk.
1263          * After the call, handle is not valid anymore.
1264          */
1265         void closeFile() @nogc
1266         {
1267             version(Windows)
1268             {
1269                 if (_handle.isHandleValid) CloseHandle(_handle);
1270                 _handle = INVALID_HANDLE_VALUE;
1271             }
1272             version(Posix)
1273             {
1274                 if (_handle.isHandleValid) core.sys.posix.unistd.close(_handle);
1275                 _handle = -1;
1276             }
1277             _filename = "";
1278         }
1279 
1280         /**
1281          * Exposes the filename.
1282          */
1283         string filename() @nogc {return _filename;}
1284     }
1285 }
1286 
1287 /**
1288  * Implements a stream of contiguous, GC-free, heap-memory.
1289  *
1290  * In theory its size can go up to 2^31 bytes (X86) or 2^63 bytes (X86_64).
1291  * This value is obviously limited by the amount of DRAM and the fragmentation.
1292  *
1293  * MemoryStream is also enhanced by implementing the interfaces StreamPersist
1294  * and FilePersist8. They allow to save the content either to another stream or
1295  * to a file and to load the content either from another Stream or from a file.
1296  */
1297 class MemoryStream: Stream, StreamPersist, FilePersist8
1298 {
1299 
1300     mixin inheritedDtor;
1301 
1302     private
1303     {
1304         size_t _size;
1305         @NoGc Ptr _memory;
1306 
1307         bool _freeFlag = true;
1308         size_t _position;
1309     }
1310     public
1311     {
1312         ///
1313         this() @nogc {}
1314 
1315         /**
1316          * Constructs a MemoryStream and write the input argument.
1317          * Params:
1318          *      a = Either an array, an input range, a basic variable or a Stream.
1319          *      Even if the argument is written, the position remains at 0.
1320          */
1321         this(A)(A a)
1322         {
1323             import std.traits: isArray;
1324             static if (isArray!A)
1325                 write(a.ptr, a.length * (ElementEncodingType!A).sizeof);
1326             else static if (isInputRange!A)
1327                 this.writeRange(a);
1328             else static if (isFixedSize!A)
1329                 write(&a, A.sizeof);
1330             else static if (is(A : Stream))
1331                 copyStream(a, this);
1332             else static assert(0, "unsupported MemoryStream __ctor argument");
1333 
1334             position = 0;
1335         }
1336 
1337         ~this() @nogc
1338         {
1339             if (_freeFlag && _memory)
1340                 freeMem(_memory);
1341         }
1342 
1343 // read & write ---------------------------------------------------------------+
1344 
1345         /// see the Stream interface.
1346         size_t read(Ptr buffer, size_t count) @nogc
1347         {
1348             if (count + _position > _size) count = _size - _position;
1349             moveMem(buffer, _memory + _position, count);
1350             _position += count;
1351             return count;
1352         }
1353 
1354         /// see the Stream interface.
1355         size_t write(const Ptr buffer, size_t count) @nogc
1356         {
1357             if (_position + count > _size) size(_position + count);
1358             moveMem(_memory + _position, buffer, count);
1359             _position += count;
1360             return count;
1361         }
1362 
1363 // -----------------------------------------------------------------------------
1364 // seek -----------------------------------------------------------------------+
1365 
1366         /// see the Stream interface.
1367         long seek(long offset, SeekMode mode) @nogc
1368         {
1369             with(SeekMode) final switch(mode)
1370             {
1371                 case beg:
1372                     _position = cast(typeof(_position)) offset;
1373                     if (_position > _size) _position = _size;
1374                     return _position;
1375                 case cur:
1376                     _position += offset;
1377                     if (_position > _size) _position = _size;
1378                     return _position;
1379                 case end:
1380                     return _size;
1381             }
1382         }
1383 
1384         /// ditto
1385         int seek(int offset, SeekMode mode) @nogc
1386         {
1387             const long longOffs = offset;
1388             return cast(int) seek(longOffs, mode);
1389         }
1390 
1391 // -----------------------------------------------------------------------------
1392 // size -----------------------------------------------------------------------+
1393 
1394         /// see the Stream interface.
1395         long size() @nogc
1396         {
1397             return _size;
1398         }
1399 
1400         /// ditto
1401         void size(long value) @nogc
1402         {
1403             if (_size == value) return;
1404             version(X86)
1405             {
1406                 if (value > int.max)
1407                     throwStaticEx!("cannot allocate more than 2^31 bytes");
1408             }
1409             if (value == 0)
1410             {
1411                 clear;
1412                 return;
1413             }
1414             _memory = reallocMem(_memory, cast(size_t) value);
1415             _size = cast(size_t)value;
1416         }
1417 
1418         /// ditto
1419         void size(int value) @nogc
1420         {
1421             size(cast(long) value);
1422         }
1423 
1424 // -----------------------------------------------------------------------------
1425 // position -------------------------------------------------------------------+
1426 
1427         /// see the Stream interface.
1428          long position() const @nogc
1429         {
1430             return _position;
1431         }
1432 
1433         /// ditto
1434         void position(long value) @nogc
1435         {
1436             seek(value, SeekMode.beg);
1437         }
1438 
1439         /// ditto
1440         void position(int value) @nogc
1441         {
1442             seek(value, SeekMode.beg);
1443         }
1444 
1445 // -----------------------------------------------------------------------------
1446 // misc -----------------------------------------------------------------------+
1447 
1448         /// see the Stream interface.
1449         void clear() @nogc
1450         {
1451             freeMem(_memory);
1452             _size = 0;
1453             _position = 0;
1454         }
1455 
1456         /**
1457          * Replaces the current memory.
1458          *
1459          * Params:
1460          *      ptr = The new memory.
1461          *      newSize = The new size.
1462          *      freeCurrent = If true the default, the current memory is freed.
1463          * Returns:
1464          *      The old memory, useful only if freeCurrent is set to false.
1465          */
1466         final Ptr setMemory(Ptr ptr, size_t newSize, bool freeCurrent = true)
1467         {
1468             Ptr result = _memory;
1469             if (!ptr) return result;
1470             if (freeCurrent || _freeFlag)
1471                 freeMem(_memory);
1472             _position = 0;
1473             _size = newSize;
1474             _memory = ptr;
1475             import core.memory: GC;
1476             _freeFlag = GC.addrOf(ptr) == null;
1477             return result;
1478         }
1479 
1480         /**
1481          * Access to the memory chunk.
1482          */
1483         final Ptr memory() @nogc
1484         {
1485             return _memory;
1486         }
1487 
1488         /**
1489          * Returns the stream content as a read-only ubyte array.
1490          */
1491         const(ubyte)[] ubytes() const @nogc
1492         {
1493             return cast(ubyte[]) _memory[0 .. _size];
1494         }
1495 
1496         /**
1497          * Returns the stream content as a read-only char array.
1498          */
1499         const(char[]) chars() const @nogc
1500         {
1501             return cast(char[]) _memory[0 .. _size];
1502         }
1503 
1504 // -----------------------------------------------------------------------------
1505 // StreamPersist --------------------------------------------------------------+
1506 
1507         /// see the StreamPersist interface.
1508         void saveToStream(Stream stream)
1509         {
1510             if (cast(MemoryStream) stream)
1511             {
1512                 auto target = cast(MemoryStream) stream;
1513                 auto immutable oldpos = target.position;
1514                 scope(exit) target.position = oldpos;
1515 
1516                 position = 0;
1517                 stream.size = size;
1518                 stream.position = 0;
1519 
1520                 size_t sz = cast(size_t) size;
1521                 size_t buffsz = 8192;
1522                 immutable size_t blocks = sz / buffsz;
1523                 size_t tail = sz - blocks * buffsz;
1524 
1525                 size_t pos;
1526                 foreach(immutable i; 0 .. blocks)
1527                 {
1528                     moveMem(target._memory + pos, _memory + pos, buffsz);
1529                     pos += buffsz;
1530                 }
1531                 if (tail) moveMem(target._memory + pos, _memory + pos, tail);
1532             }
1533             else
1534             {
1535                 this.copyStream(stream);
1536             }
1537         }
1538 
1539         /// see the StreamPersist interface.
1540         void loadFromStream(Stream stream)
1541         {
1542             if (auto source = cast(MemoryStream) stream)
1543                 source.saveToStream(this);
1544             else
1545                 copyStream(stream, this);
1546         }
1547 
1548 // -----------------------------------------------------------------------------
1549 // FilePersist8 ---------------------------------------------------------------+
1550 
1551         /// see the FilePersist8 interface.
1552         void saveToFile(const(char)[] aFilename)
1553         {
1554             version(Windows)
1555             {
1556                 auto hdl = CreateFileA( aFilename.toStringz, GENERIC_WRITE, 0,
1557                     (SECURITY_ATTRIBUTES*).init, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, HANDLE.init);
1558 
1559                 if (hdl == INVALID_HANDLE_VALUE)
1560                     throw new Exception(format("stream exception: cannot create or overwrite '%s'", aFilename));
1561 
1562                 scope(exit) CloseHandle(hdl);
1563                 uint numRead;
1564                 SetFilePointer(hdl, 0, null, FILE_BEGIN);
1565                 WriteFile(hdl, _memory, cast(uint)_size, &numRead, null);
1566 
1567                 if (numRead != _size)
1568                     throw new Exception(format("stream exception: '%s' is corrupted", aFilename));
1569             }
1570             version(Posix)
1571             {
1572                 import std.conv: octal;
1573                 auto hdl = open( aFilename.toStringz, O_CREAT | O_TRUNC | O_WRONLY, octal!666);
1574                 if (hdl <= -1)
1575                     throw new Exception(format("stream exception: cannot create or overwrite '%s'", aFilename));
1576 
1577                 scope(exit) core.sys.posix.unistd.close(hdl);
1578                 auto immutable numRead = core.sys.posix.unistd.write(hdl, _memory, _size);
1579                 ftruncate64(hdl, _size);
1580 
1581                 if (numRead != _size)
1582                     throw new Exception(format("stream exception: '%s' is corrupted", aFilename));
1583             }
1584         }
1585 
1586         /// see the FilePersist8 interface.
1587         void loadFromFile(const(char)[] aFilename)
1588         {
1589             version(Windows)
1590             {
1591                 auto hdl = CreateFileA(aFilename.toStringz, GENERIC_READ, 0,
1592                     (SECURITY_ATTRIBUTES*).init, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, HANDLE.init);
1593 
1594                 if (hdl == INVALID_HANDLE_VALUE)
1595                     throw new Exception(format("stream exception: cannot open '%s'", aFilename));
1596 
1597                 uint numRead;
1598                 scope(exit) CloseHandle(hdl);
1599                 size( SetFilePointer(hdl, 0, null, FILE_END));
1600                 SetFilePointer(hdl, 0, null, FILE_BEGIN);
1601                 ReadFile(hdl, _memory, cast(uint)_size, &numRead, null);
1602                 position = 0;
1603 
1604                 if (numRead != _size)
1605                     throw new Exception(format("stream exception: '%s' is not correctly loaded", aFilename));
1606             }
1607             version(Posix)
1608             {
1609                 import std.conv: octal;
1610                 auto hdl = open(aFilename.toStringz, O_CREAT | O_RDONLY, octal!666);
1611 
1612                 if (hdl <= -1)
1613                     throw new Exception(format("stream exception: cannot open '%s'", aFilename));
1614 
1615                 scope(exit) core.sys.posix.unistd.close(hdl);
1616                 size(core.sys.posix.unistd.lseek64(hdl, 0, SEEK_END));
1617                 core.sys.posix.unistd.lseek64(hdl, 0, SEEK_SET);
1618                 const size_t numRead = core.sys.posix.unistd.read(hdl, _memory, _size);
1619                 position = 0;
1620 
1621                 if (numRead != _size)
1622                     throw new Exception(format("stream exception: '%s' is not correctly loaded", aFilename));
1623             }
1624         }
1625 // ----
1626     }
1627 }
1628 
1629 unittest
1630 {
1631     // MemoryStream.setMemory
1632     Ptr mem = getMem(4096);
1633     MemoryStream str = construct!MemoryStream;
1634     scope(exit) destruct(str);
1635 
1636     str.size = 128;
1637     str.position = 128;
1638     str.setMemory(mem, 4096);
1639     assert(str.memory == mem);
1640     assert(str.size == 4096);
1641     assert(str.position == 0);
1642 
1643     auto arr = [0,1,2,3,4,5,6,7,8,9];
1644     str.setMemory(arr.ptr, arr.length * arr[0].sizeof, false);
1645     assert(str.memory == arr.ptr);
1646     assert(str.size == arr.length * arr[0].sizeof);
1647     assert(str.position == 0);
1648     str.position = arr[0].sizeof * 3;
1649     typeof(arr[0]) value;
1650     str.read(&value, value.sizeof);
1651     assert(value == arr[3]);
1652 }
1653 
1654 unittest
1655 {
1656     // Stream.opOpAssign!("~")
1657     auto str1 = construct!MemoryStream;
1658     auto str2 = construct!MemoryStream;
1659     scope(exit) destructEach(str1, str2);
1660     //
1661     auto dat1 = "1234";
1662     auto dat2 = "5678";
1663     str1.write(cast(void*) dat1.ptr, dat1.length);
1664     str2.write(cast(void*) dat2.ptr, dat2.length);
1665     str2.position = 0;
1666     str1 ~= str2;
1667     assert(str2.position == 0);
1668     assert(str1.size == dat1.length + dat2.length);
1669     auto dat3 = new char[](8);
1670     str1.position = 0;
1671     str1.read(cast(void*) dat3.ptr, dat3.length);
1672     assert(dat3 == "12345678");
1673 }
1674 
1675 unittest
1676 {
1677     import std.process: environment;
1678     if (environment.get("TRAVIS") == "true")
1679         return;
1680 
1681     auto sz = 0x1_FFFF_FFFFL;
1682     FileStream huge = construct!FileStream("huge.bin");
1683     scope(exit)
1684     {
1685         huge.destruct;
1686         import std.stdio: remove;
1687         remove("huge.bin");
1688     }
1689     huge.size(sz);
1690     huge.position = 0;
1691     assert(huge.size == sz);
1692 }
1693 
1694 unittest
1695 {
1696     import std.digest.md: md5Of;
1697     import std.file: remove;
1698 
1699     void test(T, A...)(A a)
1700     {
1701         uint len = 25_000;
1702         auto str = construct!T(a);
1703         scope (exit)  str.destruct;
1704         for (int i = 0; i < len; i++)
1705         {
1706             str.write(&i, i.sizeof);
1707             assert(str.position == (i + 1) * i.sizeof);
1708         }
1709         str.position = 0;
1710         assert(str.size == len * 4);
1711         while(str.position < str.size)
1712         {
1713             int g;
1714             auto c = str.read(&g, g.sizeof);
1715             assert(g == (str.position - 1) / g.sizeof );
1716         }
1717         str.clear;
1718         assert(str.size == 0);
1719         assert(str.position == 0);
1720         for (int i = 0; i < len; i++)
1721         {
1722             str.write(&i, i.sizeof);
1723             assert(str.position == (i + 1) * i.sizeof);
1724         }
1725         str.position = 0;
1726 
1727         static if (is(T == FileStream))
1728         {
1729             auto strcpy = construct!T("filestream2.txt");
1730         }
1731         else auto strcpy = construct!T(A);
1732         scope (exit) strcpy.destruct;
1733         strcpy.size = 100_000;
1734         assert(str.size == len * 4);
1735         strcpy.loadFromStream(str);
1736         assert(str.size == len * 4);
1737         assert(strcpy.size == str.size);
1738         strcpy.position = 0;
1739         str.position = 0;
1740         for (int i = 0; i < len; i++)
1741         {
1742             auto r0 = str.readInt;
1743             auto r1 = strcpy.readInt;
1744             assert(r0 == r1);
1745         }
1746         strcpy.position = 0;
1747         str.position = 0;
1748         assert(strcpy.size == len * 4);
1749 
1750         str.write("truncate the data".dup.ptr, 17);
1751         str.position = 0;
1752         strcpy.position = 0;
1753         ubyte[] food0, food1;
1754         food0.length = cast(size_t) str.size;
1755         food1.length = cast(size_t) strcpy.size;
1756         str.read(food0.ptr, food0.length);
1757         strcpy.read(food1.ptr,food1.length);
1758         ubyte[16] md5_0 = md5Of(food0);
1759         ubyte[16] md5_1 = md5Of(food1);
1760         assert(md5_0 != md5_1);
1761 
1762         static if (is(T == MemoryStream))
1763         {
1764             str.saveToFile("memstream.txt");
1765             str.clear;
1766             str.loadFromFile("memstream.txt");
1767             assert(str.size == strcpy.size);
1768             remove("memstream.txt");
1769         }
1770 
1771         str.position = 0;
1772         strcpy.position = 0;
1773         strcpy.saveToStream(str);
1774         str.position = 0;
1775         strcpy.position = 0;
1776         food0.length = cast(size_t) str.size;
1777         food1.length = cast(size_t) strcpy.size;
1778         str.read(food0.ptr,food0.length);
1779         strcpy.read(food1.ptr,food1.length);
1780         md5_0 = md5Of(food0);
1781         md5_1 = md5Of(food1);
1782         assert(md5_0 == md5_1);
1783 
1784         static if (is(T == MemoryStream))
1785         {
1786           str.clear;
1787           for(ubyte i = 0; i < 100; i++) str.write(&i, 1);
1788           for(ubyte i = 0; i < 100; i++) assert( str.ubytes[i] == i );
1789         }
1790 
1791         static if (is(T == FileStream))
1792         {
1793             str.closeFile;
1794             strcpy.closeFile;
1795             remove("filestream1.txt");
1796             remove("filestream2.txt");
1797         }
1798     }
1799     test!MemoryStream;
1800     test!FileStream("filestream1.txt");
1801 }
1802 
1803 unittest
1804 {
1805     // test ctor with input range
1806     import std.conv;
1807     uint a;
1808     int[2] b = [1,2];
1809     auto s1 = new MemoryStream(a);
1810     assert(s1.size == a.sizeof);
1811     auto s2 = new MemoryStream(b);
1812     assert(s2.size == b.sizeof);
1813     auto s3 = new MemoryStream(iota(0,2));
1814     auto s4 = new MemoryStream(s3);
1815 }
1816