// Written in the D programming language /** * $(RED Deprecated: This module is considered out-dated and not up to Phobos' * current standards.) * * Source: $(PHOBOSSRC std/_stream.d) * Macros: * WIKI = Phobos/StdStream */ /* * Copyright (c) 2001-2005 * Pavel "EvilOne" Minayev * with buffering and endian support added by Ben Hinkle * with buffered readLine performance improvements by Dave Fladebo * with opApply inspired by (and mostly copied from) Regan Heath * with bug fixes and MemoryStream/SliceStream enhancements by Derick Eddington * * Permission to use, copy, modify, distribute and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appear in all copies and * that both that copyright notice and this permission notice appear * in supporting documentation. Author makes no representations about * the suitability of this software for any purpose. It is provided * "as is" without express or implied warranty. */ module undead.stream; import std.internal.cstring; /* Class structure: * InputStream interface for reading * OutputStream interface for writing * Stream abstract base of stream implementations * File an OS file stream * FilterStream a base-class for wrappers around another stream * BufferedStream a buffered stream wrapping another stream * BufferedFile a buffered File * EndianStream a wrapper stream for swapping byte order and BOMs * SliceStream a portion of another stream * MemoryStream a stream entirely stored in main memory * TArrayStream a stream wrapping an array-like buffer */ /// A base class for stream exceptions. class StreamException: Exception { /// Construct a StreamException with given error message. this(string msg) { super(msg); } } /// Thrown when unable to read data from Stream. class ReadException: StreamException { /// Construct a ReadException with given error message. this(string msg) { super(msg); } } /// Thrown when unable to write data to Stream. class WriteException: StreamException { /// Construct a WriteException with given error message. this(string msg) { super(msg); } } /// Thrown when unable to move Stream pointer. class SeekException: StreamException { /// Construct a SeekException with given error message. this(string msg) { super(msg); } } // seek whence... enum SeekPos { Set, Current, End } private { import std.conv; import std.algorithm; import std.ascii; //import std.format; import std.system; // for Endian enumeration import std.utf; import core.bitop; // for bswap import core.vararg; import std.file; import undead.internal.file; import undead.doformat; } /// InputStream is the interface for readable streams. interface InputStream { /*** * Read exactly size bytes into the buffer. * * Throws a ReadException if it is not correct. */ void readExact(void* buffer, size_t size); /*** * Read a block of data big enough to fill the given array buffer. * * Returns: the actual number of bytes read. Unfilled bytes are not modified. */ size_t read(ubyte[] buffer); /*** * Read a basic type or counted string. * * Throw a ReadException if it could not be read. * Outside of byte, ubyte, and char, the format is * implementation-specific and should not be used except as opposite actions * to write. */ void read(out byte x); void read(out ubyte x); /// ditto void read(out short x); /// ditto void read(out ushort x); /// ditto void read(out int x); /// ditto void read(out uint x); /// ditto void read(out long x); /// ditto void read(out ulong x); /// ditto void read(out float x); /// ditto void read(out double x); /// ditto void read(out real x); /// ditto void read(out ifloat x); /// ditto void read(out idouble x); /// ditto void read(out ireal x); /// ditto void read(out cfloat x); /// ditto void read(out cdouble x); /// ditto void read(out creal x); /// ditto void read(out char x); /// ditto void read(out wchar x); /// ditto void read(out dchar x); /// ditto // reads a string, written earlier by write() void read(out char[] s); /// ditto // reads a Unicode string, written earlier by write() void read(out wchar[] s); /// ditto /*** * Read a line that is terminated with some combination of carriage return and * line feed or end-of-file. * * The terminators are not included. The wchar version * is identical. The optional buffer parameter is filled (reallocating * it if necessary) and a slice of the result is returned. */ char[] readLine(); char[] readLine(char[] result); /// ditto wchar[] readLineW(); /// ditto wchar[] readLineW(wchar[] result); /// ditto /*** * Overload foreach statements to read the stream line by line and call the * supplied delegate with each line or with each line with line number. * * The string passed in line may be reused between calls to the delegate. * Line numbering starts at 1. * Breaking out of the foreach will leave the stream * position at the beginning of the next line to be read. * For example, to echo a file line-by-line with line numbers run: * ------------------------------------ * Stream file = new BufferedFile("sample.txt"); * foreach(ulong n, char[] line; file) * { * writefln("line %d: %s", n, line); * } * file.close(); * ------------------------------------ */ // iterate through the stream line-by-line int opApply(scope int delegate(ref char[] line) dg); int opApply(scope int delegate(ref ulong n, ref char[] line) dg); /// ditto int opApply(scope int delegate(ref wchar[] line) dg); /// ditto int opApply(scope int delegate(ref ulong n, ref wchar[] line) dg); /// ditto /// Read a string of the given length, /// throwing ReadException if there was a problem. char[] readString(size_t length); /*** * Read a string of the given length, throwing ReadException if there was a * problem. * * The file format is implementation-specific and should not be used * except as opposite actions to <b>write</b>. */ wchar[] readStringW(size_t length); /*** * Read and return the next character in the stream. * * This is the only method that will handle ungetc properly. * getcw's format is implementation-specific. * If EOF is reached then getc returns char.init and getcw returns wchar.init. */ char getc(); wchar getcw(); /// ditto /*** * Push a character back onto the stream. * * They will be returned in first-in last-out order from getc/getcw. * Only has effect on further calls to getc() and getcw(). */ char ungetc(char c); wchar ungetcw(wchar c); /// ditto /*** * Scan a string from the input using a similar form to C's scanf * and <a href="std_format.html">std.format</a>. * * An argument of type string is interpreted as a format string. * All other arguments must be pointer types. * If a format string is not present a default will be supplied computed from * the base type of the pointer type. An argument of type string* is filled * (possibly with appending characters) and a slice of the result is assigned * back into the argument. For example the following readf statements * are equivalent: * -------------------------- * int x; * double y; * string s; * file.readf(&x, " hello ", &y, &s); * file.readf("%d hello %f %s", &x, &y, &s); * file.readf("%d hello %f", &x, &y, "%s", &s); * -------------------------- */ int vreadf(TypeInfo[] arguments, va_list args); int readf(...); /// ditto /// Retrieve the number of bytes available for immediate reading. @property size_t available(); /*** * Return whether the current file position is the same as the end of the * file. * * This does not require actually reading past the end, as with stdio. For * non-seekable streams this might only return true after attempting to read * past the end. */ @property bool eof(); @property bool isOpen(); /// Return true if the stream is currently open. } /// Interface for writable streams. interface OutputStream { /*** * Write exactly size bytes from buffer, or throw a WriteException if that * could not be done. */ void writeExact(const void* buffer, size_t size); /*** * Write as much of the buffer as possible, * returning the number of bytes written. */ size_t write(const(ubyte)[] buffer); /*** * Write a basic type. * * Outside of byte, ubyte, and char, the format is implementation-specific * and should only be used in conjunction with read. * Throw WriteException on error. */ void write(byte x); void write(ubyte x); /// ditto void write(short x); /// ditto void write(ushort x); /// ditto void write(int x); /// ditto void write(uint x); /// ditto void write(long x); /// ditto void write(ulong x); /// ditto void write(float x); /// ditto void write(double x); /// ditto void write(real x); /// ditto void write(ifloat x); /// ditto void write(idouble x); /// ditto void write(ireal x); /// ditto void write(cfloat x); /// ditto void write(cdouble x); /// ditto void write(creal x); /// ditto void write(char x); /// ditto void write(wchar x); /// ditto void write(dchar x); /// ditto /*** * Writes a string, together with its length. * * The format is implementation-specific * and should only be used in conjunction with read. * Throw WriteException on error. */ void write(const(char)[] s); void write(const(wchar)[] s); /// ditto /*** * Write a line of text, * appending the line with an operating-system-specific line ending. * * Throws WriteException on error. */ void writeLine(const(char)[] s); /*** * Write a line of text, * appending the line with an operating-system-specific line ending. * * The format is implementation-specific. * Throws WriteException on error. */ void writeLineW(const(wchar)[] s); /*** * Write a string of text. * * Throws WriteException if it could not be fully written. */ void writeString(const(char)[] s); /*** * Write a string of text. * * The format is implementation-specific. * Throws WriteException if it could not be fully written. */ void writeStringW(const(wchar)[] s); /*** * Print a formatted string into the stream using printf-style syntax, * returning the number of bytes written. */ size_t vprintf(const(char)[] format, va_list args); size_t printf(const(char)[] format, ...); /// ditto /*** * Print a formatted string into the stream using writef-style syntax. * References: <a href="std_format.html">std.format</a>. * Returns: self to chain with other stream commands like flush. */ OutputStream writef(...); OutputStream writefln(...); /// ditto OutputStream writefx(TypeInfo[] arguments, va_list argptr, int newline = false); /// ditto void flush(); /// Flush pending output if appropriate. void close(); /// Close the stream, flushing output if appropriate. @property bool isOpen(); /// Return true if the stream is currently open. } /*** * Stream is the base abstract class from which the other stream classes derive. * * Stream's byte order is the format native to the computer. * * Reading: * These methods require that the readable flag be set. * Problems with reading result in a ReadException being thrown. * Stream implements the InputStream interface in addition to the * readBlock method. * * Writing: * These methods require that the writeable flag be set. Problems with writing * result in a WriteException being thrown. Stream implements the OutputStream * interface in addition to the following methods: * writeBlock * copyFrom * copyFrom * * Seeking: * These methods require that the seekable flag be set. * Problems with seeking result in a SeekException being thrown. * seek, seekSet, seekCur, seekEnd, position, size, toString, toHash */ // not really abstract, but its instances will do nothing useful class Stream : InputStream, OutputStream { private import std.string, std.digest.crc, core.stdc.stdlib, core.stdc.stdio; // stream abilities bool readable = false; /// Indicates whether this stream can be read from. bool writeable = false; /// Indicates whether this stream can be written to. bool seekable = false; /// Indicates whether this stream can be seeked within. protected bool isopen = true; /// Indicates whether this stream is open. protected bool readEOF = false; /** Indicates whether this stream is at eof * after the last read attempt. */ protected bool prevCr = false; /** For a non-seekable stream indicates that * the last readLine or readLineW ended on a * '\r' character. */ this() {} /*** * Read up to size bytes into the buffer and return the number of bytes * actually read. A return value of 0 indicates end-of-file. */ abstract size_t readBlock(void* buffer, size_t size); // reads block of data of specified size, // throws ReadException on error void readExact(void* buffer, size_t size) { for(;;) { if (!size) return; size_t readsize = readBlock(buffer, size); // return 0 on eof if (readsize == 0) break; buffer += readsize; size -= readsize; } if (size != 0) throw new ReadException("not enough data in stream"); } // reads block of data big enough to fill the given // array, returns actual number of bytes read size_t read(ubyte[] buffer) { return readBlock(buffer.ptr, buffer.length); } // read a single value of desired type, // throw ReadException on error void read(out byte x) { readExact(&x, x.sizeof); } void read(out ubyte x) { readExact(&x, x.sizeof); } void read(out short x) { readExact(&x, x.sizeof); } void read(out ushort x) { readExact(&x, x.sizeof); } void read(out int x) { readExact(&x, x.sizeof); } void read(out uint x) { readExact(&x, x.sizeof); } void read(out long x) { readExact(&x, x.sizeof); } void read(out ulong x) { readExact(&x, x.sizeof); } void read(out float x) { readExact(&x, x.sizeof); } void read(out double x) { readExact(&x, x.sizeof); } void read(out real x) { readExact(&x, x.sizeof); } void read(out ifloat x) { readExact(&x, x.sizeof); } void read(out idouble x) { readExact(&x, x.sizeof); } void read(out ireal x) { readExact(&x, x.sizeof); } void read(out cfloat x) { readExact(&x, x.sizeof); } void read(out cdouble x) { readExact(&x, x.sizeof); } void read(out creal x) { readExact(&x, x.sizeof); } void read(out char x) { readExact(&x, x.sizeof); } void read(out wchar x) { readExact(&x, x.sizeof); } void read(out dchar x) { readExact(&x, x.sizeof); } // reads a string, written earlier by write() void read(out char[] s) { size_t len; read(len); s = readString(len); } // reads a Unicode string, written earlier by write() void read(out wchar[] s) { size_t len; read(len); s = readStringW(len); } // reads a line, terminated by either CR, LF, CR/LF, or EOF char[] readLine() { return readLine(null); } // reads a line, terminated by either CR, LF, CR/LF, or EOF // reusing the memory in buffer if result will fit and otherwise // allocates a new string char[] readLine(char[] result) { size_t strlen = 0; char ch = getc(); while (readable) { switch (ch) { case '\r': if (seekable) { ch = getc(); if (ch != '\n') ungetc(ch); } else { prevCr = true; } goto case; case '\n': case char.init: result.length = strlen; return result; default: if (strlen < result.length) { result[strlen] = ch; } else { result ~= ch; } strlen++; } ch = getc(); } result.length = strlen; return result; } // reads a Unicode line, terminated by either CR, LF, CR/LF, // or EOF; pretty much the same as the above, working with // wchars rather than chars wchar[] readLineW() { return readLineW(null); } // reads a Unicode line, terminated by either CR, LF, CR/LF, // or EOF; // fills supplied buffer if line fits and otherwise allocates a new string. wchar[] readLineW(wchar[] result) { size_t strlen = 0; wchar c = getcw(); while (readable) { switch (c) { case '\r': if (seekable) { c = getcw(); if (c != '\n') ungetcw(c); } else { prevCr = true; } goto case; case '\n': case wchar.init: result.length = strlen; return result; default: if (strlen < result.length) { result[strlen] = c; } else { result ~= c; } strlen++; } c = getcw(); } result.length = strlen; return result; } // iterate through the stream line-by-line - due to Regan Heath int opApply(scope int delegate(ref char[] line) dg) { int res = 0; char[128] buf; while (!eof) { char[] line = readLine(buf); res = dg(line); if (res) break; } return res; } // iterate through the stream line-by-line with line count and string int opApply(scope int delegate(ref ulong n, ref char[] line) dg) { int res = 0; ulong n = 1; char[128] buf; while (!eof) { auto line = readLine(buf); res = dg(n,line); if (res) break; n++; } return res; } // iterate through the stream line-by-line with wchar[] int opApply(scope int delegate(ref wchar[] line) dg) { int res = 0; wchar[128] buf; while (!eof) { auto line = readLineW(buf); res = dg(line); if (res) break; } return res; } // iterate through the stream line-by-line with line count and wchar[] int opApply(scope int delegate(ref ulong n, ref wchar[] line) dg) { int res = 0; ulong n = 1; wchar[128] buf; while (!eof) { auto line = readLineW(buf); res = dg(n,line); if (res) break; n++; } return res; } // reads a string of given length, throws // ReadException on error char[] readString(size_t length) { char[] result = new char[length]; readExact(result.ptr, length); return result; } // reads a Unicode string of given length, throws // ReadException on error wchar[] readStringW(size_t length) { auto result = new wchar[length]; readExact(result.ptr, result.length * wchar.sizeof); return result; } // unget buffer private wchar[] unget; final bool ungetAvailable() { return unget.length > 1; } // reads and returns next character from the stream, // handles characters pushed back by ungetc() // returns char.init on eof. char getc() { char c; if (prevCr) { prevCr = false; c = getc(); if (c != '\n') return c; } if (unget.length > 1) { c = cast(char)unget[unget.length - 1]; unget.length = unget.length - 1; } else { readBlock(&c,1); } return c; } // reads and returns next Unicode character from the // stream, handles characters pushed back by ungetc() // returns wchar.init on eof. wchar getcw() { wchar c; if (prevCr) { prevCr = false; c = getcw(); if (c != '\n') return c; } if (unget.length > 1) { c = unget[unget.length - 1]; unget.length = unget.length - 1; } else { void* buf = &c; size_t n = readBlock(buf,2); if (n == 1 && readBlock(buf+1,1) == 0) throw new ReadException("not enough data in stream"); } return c; } // pushes back character c into the stream; only has // effect on further calls to getc() and getcw() char ungetc(char c) { if (c == c.init) return c; // first byte is a dummy so that we never set length to 0 if (unget.length == 0) unget.length = 1; unget ~= c; return c; } // pushes back Unicode character c into the stream; only // has effect on further calls to getc() and getcw() wchar ungetcw(wchar c) { if (c == c.init) return c; // first byte is a dummy so that we never set length to 0 if (unget.length == 0) unget.length = 1; unget ~= c; return c; } int vreadf(TypeInfo[] arguments, va_list args) { string fmt; int j = 0; int count = 0, i = 0; char c; bool firstCharacter = true; while ((j < arguments.length || i < fmt.length) && !eof) { if(firstCharacter) { c = getc(); firstCharacter = false; } if (fmt.length == 0 || i == fmt.length) { i = 0; if (arguments[j] is typeid(string) || arguments[j] is typeid(char[]) || arguments[j] is typeid(const(char)[])) { fmt = va_arg!(string)(args); j++; continue; } else if (arguments[j] is typeid(int*) || arguments[j] is typeid(byte*) || arguments[j] is typeid(short*) || arguments[j] is typeid(long*)) { fmt = "%d"; } else if (arguments[j] is typeid(uint*) || arguments[j] is typeid(ubyte*) || arguments[j] is typeid(ushort*) || arguments[j] is typeid(ulong*)) { fmt = "%d"; } else if (arguments[j] is typeid(float*) || arguments[j] is typeid(double*) || arguments[j] is typeid(real*)) { fmt = "%f"; } else if (arguments[j] is typeid(char[]*) || arguments[j] is typeid(wchar[]*) || arguments[j] is typeid(dchar[]*)) { fmt = "%s"; } else if (arguments[j] is typeid(char*)) { fmt = "%c"; } } if (fmt[i] == '%') { // a field i++; bool suppress = false; if (fmt[i] == '*') { // suppress assignment suppress = true; i++; } // read field width int width = 0; while (isDigit(fmt[i])) { width = width * 10 + (fmt[i] - '0'); i++; } if (width == 0) width = -1; // skip any modifier if present if (fmt[i] == 'h' || fmt[i] == 'l' || fmt[i] == 'L') i++; // check the typechar and act accordingly switch (fmt[i]) { case 'd': // decimal/hexadecimal/octal integer case 'D': case 'u': case 'U': case 'o': case 'O': case 'x': case 'X': case 'i': case 'I': { while (isWhite(c)) { c = getc(); count++; } bool neg = false; if (c == '-') { neg = true; c = getc(); count++; } else if (c == '+') { c = getc(); count++; } char ifmt = cast(char)(fmt[i] | 0x20); if (ifmt == 'i') { // undetermined base if (c == '0') { // octal or hex c = getc(); count++; if (c == 'x' || c == 'X') { // hex ifmt = 'x'; c = getc(); count++; } else { // octal ifmt = 'o'; } } else // decimal ifmt = 'd'; } long n = 0; switch (ifmt) { case 'd': // decimal case 'u': { while (isDigit(c) && width) { n = n * 10 + (c - '0'); width--; c = getc(); count++; } } break; case 'o': { // octal while (isOctalDigit(c) && width) { n = n * 8 + (c - '0'); width--; c = getc(); count++; } } break; case 'x': { // hexadecimal while (isHexDigit(c) && width) { n *= 0x10; if (isDigit(c)) n += c - '0'; else n += 0xA + (c | 0x20) - 'a'; width--; c = getc(); count++; } } break; default: assert(0); } if (neg) n = -n; if (arguments[j] is typeid(int*)) { int* p = va_arg!(int*)(args); *p = cast(int)n; } else if (arguments[j] is typeid(short*)) { short* p = va_arg!(short*)(args); *p = cast(short)n; } else if (arguments[j] is typeid(byte*)) { byte* p = va_arg!(byte*)(args); *p = cast(byte)n; } else if (arguments[j] is typeid(long*)) { long* p = va_arg!(long*)(args); *p = n; } else if (arguments[j] is typeid(uint*)) { uint* p = va_arg!(uint*)(args); *p = cast(uint)n; } else if (arguments[j] is typeid(ushort*)) { ushort* p = va_arg!(ushort*)(args); *p = cast(ushort)n; } else if (arguments[j] is typeid(ubyte*)) { ubyte* p = va_arg!(ubyte*)(args); *p = cast(ubyte)n; } else if (arguments[j] is typeid(ulong*)) { ulong* p = va_arg!(ulong*)(args); *p = cast(ulong)n; } j++; i++; } break; case 'f': // float case 'F': case 'e': case 'E': case 'g': case 'G': { while (isWhite(c)) { c = getc(); count++; } bool neg = false; if (c == '-') { neg = true; c = getc(); count++; } else if (c == '+') { c = getc(); count++; } real r = 0; while (isDigit(c) && width) { r = r * 10 + (c - '0'); width--; c = getc(); count++; } if (width && c == '.') { width--; c = getc(); count++; double frac = 1; while (isDigit(c) && width) { r = r * 10 + (c - '0'); frac *= 10; width--; c = getc(); count++; } r /= frac; } if (width && (c == 'e' || c == 'E')) { width--; c = getc(); count++; if (width) { bool expneg = false; if (c == '-') { expneg = true; width--; c = getc(); count++; } else if (c == '+') { width--; c = getc(); count++; } real exp = 0; while (isDigit(c) && width) { exp = exp * 10 + (c - '0'); width--; c = getc(); count++; } if (expneg) { while (exp--) r /= 10; } else { while (exp--) r *= 10; } } } if(width && (c == 'n' || c == 'N')) { width--; c = getc(); count++; if(width && (c == 'a' || c == 'A')) { width--; c = getc(); count++; if(width && (c == 'n' || c == 'N')) { width--; c = getc(); count++; r = real.nan; } } } if(width && (c == 'i' || c == 'I')) { width--; c = getc(); count++; if(width && (c == 'n' || c == 'N')) { width--; c = getc(); count++; if(width && (c == 'f' || c == 'F')) { width--; c = getc(); count++; r = real.infinity; } } } if (neg) r = -r; if (arguments[j] is typeid(float*)) { float* p = va_arg!(float*)(args); *p = r; } else if (arguments[j] is typeid(double*)) { double* p = va_arg!(double*)(args); *p = r; } else if (arguments[j] is typeid(real*)) { real* p = va_arg!(real*)(args); *p = r; } j++; i++; } break; case 's': { // string while (isWhite(c)) { c = getc(); count++; } char[] s; char[]* p; size_t strlen; if (arguments[j] is typeid(char[]*)) { p = va_arg!(char[]*)(args); s = *p; } while (!isWhite(c) && c != char.init) { if (strlen < s.length) { s[strlen] = c; } else { s ~= c; } strlen++; c = getc(); count++; } s = s[0 .. strlen]; if (arguments[j] is typeid(char[]*)) { *p = s; } else if (arguments[j] is typeid(char*)) { s ~= 0; auto q = va_arg!(char*)(args); q[0 .. s.length] = s[]; } else if (arguments[j] is typeid(wchar[]*)) { auto q = va_arg!(const(wchar)[]*)(args); *q = toUTF16(s); } else if (arguments[j] is typeid(dchar[]*)) { auto q = va_arg!(const(dchar)[]*)(args); *q = toUTF32(s); } j++; i++; } break; case 'c': { // character(s) char* s = va_arg!(char*)(args); if (width < 0) width = 1; else while (isWhite(c)) { c = getc(); count++; } while (width-- && !eof) { *(s++) = c; c = getc(); count++; } j++; i++; } break; case 'n': { // number of chars read so far int* p = va_arg!(int*)(args); *p = count; j++; i++; } break; default: // read character as is goto nws; } } else if (isWhite(fmt[i])) { // skip whitespace while (isWhite(c)) c = getc(); i++; } else { // read character as is nws: if (fmt[i] != c) break; c = getc(); i++; } } ungetc(c); return count; } int readf(...) { return vreadf(_arguments, _argptr); } // returns estimated number of bytes available for immediate reading @property size_t available() { return 0; } /*** * Write up to size bytes from buffer in the stream, returning the actual * number of bytes that were written. */ abstract size_t writeBlock(const void* buffer, size_t size); // writes block of data of specified size, // throws WriteException on error void writeExact(const void* buffer, size_t size) { const(void)* p = buffer; for(;;) { if (!size) return; size_t writesize = writeBlock(p, size); if (writesize == 0) break; p += writesize; size -= writesize; } if (size != 0) throw new WriteException("unable to write to stream"); } // writes the given array of bytes, returns // actual number of bytes written size_t write(const(ubyte)[] buffer) { return writeBlock(buffer.ptr, buffer.length); } // write a single value of desired type, // throw WriteException on error void write(byte x) { writeExact(&x, x.sizeof); } void write(ubyte x) { writeExact(&x, x.sizeof); } void write(short x) { writeExact(&x, x.sizeof); } void write(ushort x) { writeExact(&x, x.sizeof); } void write(int x) { writeExact(&x, x.sizeof); } void write(uint x) { writeExact(&x, x.sizeof); } void write(long x) { writeExact(&x, x.sizeof); } void write(ulong x) { writeExact(&x, x.sizeof); } void write(float x) { writeExact(&x, x.sizeof); } void write(double x) { writeExact(&x, x.sizeof); } void write(real x) { writeExact(&x, x.sizeof); } void write(ifloat x) { writeExact(&x, x.sizeof); } void write(idouble x) { writeExact(&x, x.sizeof); } void write(ireal x) { writeExact(&x, x.sizeof); } void write(cfloat x) { writeExact(&x, x.sizeof); } void write(cdouble x) { writeExact(&x, x.sizeof); } void write(creal x) { writeExact(&x, x.sizeof); } void write(char x) { writeExact(&x, x.sizeof); } void write(wchar x) { writeExact(&x, x.sizeof); } void write(dchar x) { writeExact(&x, x.sizeof); } // writes a string, together with its length void write(const(char)[] s) { write(s.length); writeString(s); } // writes a Unicode string, together with its length void write(const(wchar)[] s) { write(s.length); writeStringW(s); } // writes a line, throws WriteException on error void writeLine(const(char)[] s) { writeString(s); version (Windows) writeString("\r\n"); else version (Mac) writeString("\r"); else writeString("\n"); } // writes a Unicode line, throws WriteException on error void writeLineW(const(wchar)[] s) { writeStringW(s); version (Windows) writeStringW("\r\n"); else version (Mac) writeStringW("\r"); else writeStringW("\n"); } // writes a string, throws WriteException on error void writeString(const(char)[] s) { writeExact(s.ptr, s.length); } // writes a Unicode string, throws WriteException on error void writeStringW(const(wchar)[] s) { writeExact(s.ptr, s.length * wchar.sizeof); } // writes data to stream using vprintf() syntax, // returns number of bytes written size_t vprintf(const(char)[] format, va_list args) { // shamelessly stolen from OutBuffer, // by Walter's permission char[1024] buffer; char* p = buffer.ptr; // Can't use `tempCString()` here as it will result in compilation error: // "cannot mix core.std.stdlib.alloca() and exception handling". auto f = toStringz(format); size_t psize = buffer.length; size_t count; while (true) { version (Windows) { count = vsnprintf(p, psize, f, args); if (count != -1) break; psize *= 2; p = cast(char*) alloca(psize); } else version (Posix) { count = vsnprintf(p, psize, f, args); if (count == -1) psize *= 2; else if (count >= psize) psize = count + 1; else break; p = cast(char*) alloca(psize); } else throw new Exception("unsupported platform"); } writeString(p[0 .. count]); return count; } // writes data to stream using printf() syntax, // returns number of bytes written size_t printf(const(char)[] format, ...) { va_list ap; va_start(ap, format); auto result = vprintf(format, ap); va_end(ap); return result; } private void doFormatCallback(dchar c) { char[4] buf; auto b = std.utf.toUTF8(buf, c); writeString(b); } // writes data to stream using writef() syntax, OutputStream writef(...) { return writefx(_arguments,_argptr,0); } // writes data with trailing newline OutputStream writefln(...) { return writefx(_arguments,_argptr,1); } // writes data with optional trailing newline OutputStream writefx(TypeInfo[] arguments, va_list argptr, int newline=false) { doFormat(&doFormatCallback,arguments,argptr); if (newline) writeLine(""); return this; } /*** * Copies all data from s into this stream. * This may throw ReadException or WriteException on failure. * This restores the file position of s so that it is unchanged. */ void copyFrom(Stream s) { if (seekable) { ulong pos = s.position; s.position = 0; copyFrom(s, s.size); s.position = pos; } else { ubyte[128] buf; while (!s.eof) { size_t m = s.readBlock(buf.ptr, buf.length); writeExact(buf.ptr, m); } } } /*** * Copy a specified number of bytes from the given stream into this one. * This may throw ReadException or WriteException on failure. * Unlike the previous form, this doesn't restore the file position of s. */ void copyFrom(Stream s, ulong count) { ubyte[128] buf; while (count > 0) { size_t n = cast(size_t)(count<buf.length ? count : buf.length); s.readExact(buf.ptr, n); writeExact(buf.ptr, n); count -= n; } } /*** * Change the current position of the stream. whence is either SeekPos.Set, in which case the offset is an absolute index from the beginning of the stream, SeekPos.Current, in which case the offset is a delta from the current position, or SeekPos.End, in which case the offset is a delta from the end of the stream (negative or zero offsets only make sense in that case). This returns the new file position. */ abstract ulong seek(long offset, SeekPos whence); /*** * Aliases for their normal seek counterparts. */ ulong seekSet(long offset) { return seek (offset, SeekPos.Set); } ulong seekCur(long offset) { return seek (offset, SeekPos.Current); } /// ditto ulong seekEnd(long offset) { return seek (offset, SeekPos.End); } /// ditto /*** * Sets file position. Equivalent to calling seek(pos, SeekPos.Set). */ @property void position(ulong pos) { seek(cast(long)pos, SeekPos.Set); } /*** * Returns current file position. Equivalent to seek(0, SeekPos.Current). */ @property ulong position() { return seek(0, SeekPos.Current); } /*** * Retrieve the size of the stream in bytes. * The stream must be seekable or a SeekException is thrown. */ @property ulong size() { assertSeekable(); ulong pos = position, result = seek(0, SeekPos.End); position = pos; return result; } // returns true if end of stream is reached, false otherwise @property bool eof() { // for unseekable streams we only know the end when we read it if (readEOF && !ungetAvailable()) return true; else if (seekable) return position == size; else return false; } // returns true if the stream is open @property bool isOpen() { return isopen; } // flush the buffer if writeable void flush() { if (unget.length > 1) unget.length = 1; // keep at least 1 so that data ptr stays } // close the stream somehow; the default just flushes the buffer void close() { if (isopen) flush(); readEOF = prevCr = isopen = readable = writeable = seekable = false; } /*** * Read the entire stream and return it as a string. * If the stream is not seekable the contents from the current position to eof * is read and returned. */ override string toString() { if (!readable) return super.toString(); try { size_t pos; size_t rdlen; size_t blockSize; char[] result; if (seekable) { ulong orig_pos = position; scope(exit) position = orig_pos; position = 0; blockSize = cast(size_t)size; result = new char[blockSize]; while (blockSize > 0) { rdlen = readBlock(&result[pos], blockSize); pos += rdlen; blockSize -= rdlen; } } else { blockSize = 4096; result = new char[blockSize]; while ((rdlen = readBlock(&result[pos], blockSize)) > 0) { pos += rdlen; blockSize += rdlen; result.length = result.length + blockSize; } } return cast(string) result[0 .. pos]; } catch (Throwable) { return super.toString(); } } /*** * Get a hash of the stream by reading each byte and using it in a CRC-32 * checksum. */ override size_t toHash() @trusted { if (!readable || !seekable) return super.toHash(); try { ulong pos = position; scope(exit) position = pos; CRC32 crc; crc.start(); position = 0; ulong len = size; for (ulong i = 0; i < len; i++) { ubyte c; read(c); crc.put(c); } union resUnion { size_t hash; ubyte[4] crcVal; } resUnion res; res.crcVal = crc.finish(); return res.hash; } catch (Throwable) { return super.toHash(); } } // helper for checking that the stream is readable final protected void assertReadable() { if (!readable) throw new ReadException("Stream is not readable"); } // helper for checking that the stream is writeable final protected void assertWriteable() { if (!writeable) throw new WriteException("Stream is not writeable"); } // helper for checking that the stream is seekable final protected void assertSeekable() { if (!seekable) throw new SeekException("Stream is not seekable"); } unittest { // unit test for Issue 3363 import std.stdio; immutable fileName = undead.internal.file.deleteme ~ "-issue3363.txt"; auto w = File(fileName, "w"); scope (exit) remove(fileName.ptr); w.write("one two three"); w.close(); auto r = File(fileName, "r"); const(char)[] constChar; string str; char[] chars; r.readf("%s %s %s", &constChar, &str, &chars); assert (constChar == "one", constChar); assert (str == "two", str); assert (chars == "three", chars); } unittest { //unit tests for Issue 1668 void tryFloatRoundtrip(float x, string fmt = "", string pad = "") { auto s = new MemoryStream(); s.writef(fmt, x, pad); s.position = 0; float f; assert(s.readf(&f)); assert(x == f || (x != x && f != f)); //either equal or both NaN } tryFloatRoundtrip(1.0); tryFloatRoundtrip(1.0, "%f"); tryFloatRoundtrip(1.0, "", " "); tryFloatRoundtrip(1.0, "%f", " "); tryFloatRoundtrip(3.14); tryFloatRoundtrip(3.14, "%f"); tryFloatRoundtrip(3.14, "", " "); tryFloatRoundtrip(3.14, "%f", " "); float nan = float.nan; tryFloatRoundtrip(nan); tryFloatRoundtrip(nan, "%f"); tryFloatRoundtrip(nan, "", " "); tryFloatRoundtrip(nan, "%f", " "); float inf = 1.0/0.0; tryFloatRoundtrip(inf); tryFloatRoundtrip(inf, "%f"); tryFloatRoundtrip(inf, "", " "); tryFloatRoundtrip(inf, "%f", " "); tryFloatRoundtrip(-inf); tryFloatRoundtrip(-inf,"%f"); tryFloatRoundtrip(-inf, "", " "); tryFloatRoundtrip(-inf, "%f", " "); } } /*** * A base class for streams that wrap a source stream with additional * functionality. * * The method implementations forward read/write/seek calls to the * source stream. A FilterStream can change the position of the source stream * arbitrarily and may not keep the source stream state in sync with the * FilterStream, even upon flushing and closing the FilterStream. It is * recommended to not make any assumptions about the state of the source position * and read/write state after a FilterStream has acted upon it. Specifc subclasses * of FilterStream should document how they modify the source stream and if any * invariants hold true between the source and filter. */ class FilterStream : Stream { private Stream s; // source stream /// Property indicating when this stream closes to close the source stream as /// well. /// Defaults to true. bool nestClose = true; /// Construct a FilterStream for the given source. this(Stream source) { s = source; resetSource(); } // source getter/setter /*** * Get the current source stream. */ final Stream source(){return s;} /*** * Set the current source stream. * * Setting the source stream closes this stream before attaching the new * source. Attaching an open stream reopens this stream and resets the stream * state. */ void source(Stream s) { close(); this.s = s; resetSource(); } /*** * Indicates the source stream changed state and that this stream should reset * any readable, writeable, seekable, isopen and buffering flags. */ void resetSource() { if (s !is null) { readable = s.readable; writeable = s.writeable; seekable = s.seekable; isopen = s.isOpen; } else { readable = writeable = seekable = false; isopen = false; } readEOF = prevCr = false; } // read from source override size_t readBlock(void* buffer, size_t size) { size_t res = s.readBlock(buffer,size); readEOF = res == 0; return res; } // write to source override size_t writeBlock(const void* buffer, size_t size) { return s.writeBlock(buffer,size); } // close stream override void close() { if (isopen) { super.close(); if (nestClose) s.close(); } } // seek on source override ulong seek(long offset, SeekPos whence) { readEOF = false; return s.seek(offset,whence); } override @property size_t available() { return s.available; } override void flush() { super.flush(); s.flush(); } } /*** * This subclass is for buffering a source stream. * * A buffered stream must be * closed explicitly to ensure the final buffer content is written to the source * stream. The source stream position is changed according to the block size so * reading or writing to the BufferedStream may not change the source stream * position by the same amount. */ class BufferedStream : FilterStream { ubyte[] buffer; // buffer, if any size_t bufferCurPos; // current position in buffer size_t bufferLen; // amount of data in buffer bool bufferDirty = false; size_t bufferSourcePos; // position in buffer of source stream position ulong streamPos; // absolute position in source stream /* Example of relationship between fields: * * s ...01234567890123456789012EOF * buffer |-- --| * bufferCurPos | * bufferLen |-- --| * bufferSourcePos | * */ invariant() { assert(bufferSourcePos <= bufferLen); assert(bufferCurPos <= bufferLen); assert(bufferLen <= buffer.length); } enum size_t DefaultBufferSize = 8192; /*** * Create a buffered stream for the stream source with the buffer size * bufferSize. */ this(Stream source, size_t bufferSize = DefaultBufferSize) { super(source); if (bufferSize) buffer = new ubyte[bufferSize]; } override protected void resetSource() { super.resetSource(); streamPos = 0; bufferLen = bufferSourcePos = bufferCurPos = 0; bufferDirty = false; } // reads block of data of specified size using any buffered data // returns actual number of bytes read override size_t readBlock(void* result, size_t len) { if (len == 0) return 0; assertReadable(); ubyte* outbuf = cast(ubyte*)result; size_t readsize = 0; if (bufferCurPos + len < bufferLen) { // buffer has all the data so copy it outbuf[0 .. len] = buffer[bufferCurPos .. bufferCurPos+len]; bufferCurPos += len; readsize = len; goto ExitRead; } readsize = bufferLen - bufferCurPos; if (readsize > 0) { // buffer has some data so copy what is left outbuf[0 .. readsize] = buffer[bufferCurPos .. bufferLen]; outbuf += readsize; bufferCurPos += readsize; len -= readsize; } flush(); if (len >= buffer.length) { // buffer can't hold the data so fill output buffer directly size_t siz = super.readBlock(outbuf, len); readsize += siz; streamPos += siz; } else { // read a new block into buffer bufferLen = super.readBlock(buffer.ptr, buffer.length); if (bufferLen < len) len = bufferLen; outbuf[0 .. len] = buffer[0 .. len]; bufferSourcePos = bufferLen; streamPos += bufferLen; bufferCurPos = len; readsize += len; } ExitRead: return readsize; } // write block of data of specified size // returns actual number of bytes written override size_t writeBlock(const void* result, size_t len) { assertWriteable(); ubyte* buf = cast(ubyte*)result; size_t writesize = 0; if (bufferLen == 0) { // buffer is empty so fill it if possible if ((len < buffer.length) && (readable)) { // read in data if the buffer is currently empty bufferLen = s.readBlock(buffer.ptr, buffer.length); bufferSourcePos = bufferLen; streamPos += bufferLen; } else if (len >= buffer.length) { // buffer can't hold the data so write it directly and exit writesize = s.writeBlock(buf,len); streamPos += writesize; goto ExitWrite; } } if (bufferCurPos + len <= buffer.length) { // buffer has space for all the data so copy it and exit buffer[bufferCurPos .. bufferCurPos+len] = buf[0 .. len]; bufferCurPos += len; bufferLen = bufferCurPos > bufferLen ? bufferCurPos : bufferLen; writesize = len; bufferDirty = true; goto ExitWrite; } writesize = buffer.length - bufferCurPos; if (writesize > 0) { // buffer can take some data buffer[bufferCurPos .. buffer.length] = buf[0 .. writesize]; bufferCurPos = bufferLen = buffer.length; buf += writesize; len -= writesize; bufferDirty = true; } assert(bufferCurPos == buffer.length); assert(bufferLen == buffer.length); flush(); writesize += writeBlock(buf,len); ExitWrite: return writesize; } override ulong seek(long offset, SeekPos whence) { assertSeekable(); if ((whence != SeekPos.Current) || (offset + bufferCurPos < 0) || (offset + bufferCurPos >= bufferLen)) { flush(); streamPos = s.seek(offset,whence); } else { bufferCurPos += offset; } readEOF = false; return streamPos-bufferSourcePos+bufferCurPos; } // Buffered readLine - Dave Fladebo // reads a line, terminated by either CR, LF, CR/LF, or EOF // reusing the memory in buffer if result will fit, otherwise // will reallocate (using concatenation) template TreadLine(T) { T[] readLine(T[] inBuffer) { size_t lineSize = 0; bool haveCR = false; T c = '\0'; size_t idx = 0; ubyte* pc = cast(ubyte*)&c; L0: for(;;) { size_t start = bufferCurPos; L1: foreach(ubyte b; buffer[start .. bufferLen]) { bufferCurPos++; pc[idx] = b; if(idx < T.sizeof - 1) { idx++; continue L1; } else { idx = 0; } if(c == '\n' || haveCR) { if(haveCR && c != '\n') bufferCurPos--; break L0; } else { if(c == '\r') { haveCR = true; } else { if(lineSize < inBuffer.length) { inBuffer[lineSize] = c; } else { inBuffer ~= c; } lineSize++; } } } flush(); size_t res = super.readBlock(buffer.ptr, buffer.length); if(!res) break L0; // EOF bufferSourcePos = bufferLen = res; streamPos += res; } return inBuffer[0 .. lineSize]; } } // template TreadLine(T) override char[] readLine(char[] inBuffer) { if (ungetAvailable()) return super.readLine(inBuffer); else return TreadLine!(char).readLine(inBuffer); } alias readLine = Stream.readLine; override wchar[] readLineW(wchar[] inBuffer) { if (ungetAvailable()) return super.readLineW(inBuffer); else return TreadLine!(wchar).readLine(inBuffer); } alias readLineW = Stream.readLineW; override void flush() out { assert(bufferCurPos == 0); assert(bufferSourcePos == 0); assert(bufferLen == 0); } body { if (writeable && bufferDirty) { if (bufferSourcePos != 0 && seekable) { // move actual file pointer to front of buffer streamPos = s.seek(-bufferSourcePos, SeekPos.Current); } // write buffer out bufferSourcePos = s.writeBlock(buffer.ptr, bufferLen); if (bufferSourcePos != bufferLen) { throw new WriteException("Unable to write to stream"); } } super.flush(); long diff = cast(long)bufferCurPos-bufferSourcePos; if (diff != 0 && seekable) { // move actual file pointer to current position streamPos = s.seek(diff, SeekPos.Current); } // reset buffer data to be empty bufferSourcePos = bufferCurPos = bufferLen = 0; bufferDirty = false; } // returns true if end of stream is reached, false otherwise override @property bool eof() { if ((buffer.length == 0) || !readable) { return super.eof; } // some simple tests to avoid flushing if (ungetAvailable() || bufferCurPos != bufferLen) return false; if (bufferLen == buffer.length) flush(); size_t res = super.readBlock(&buffer[bufferLen],buffer.length-bufferLen); bufferSourcePos += res; bufferLen += res; streamPos += res; return readEOF; } // returns size of stream override @property ulong size() { if (bufferDirty) flush(); return s.size; } // returns estimated number of bytes available for immediate reading override @property size_t available() { return bufferLen - bufferCurPos; } } /// An exception for File errors. class StreamFileException: StreamException { /// Construct a StreamFileException with given error message. this(string msg) { super(msg); } } /// An exception for errors during File.open. class OpenException: StreamFileException { /// Construct an OpenFileException with given error message. this(string msg) { super(msg); } } /// Specifies the $(LREF File) access mode used when opening the file. enum FileMode { In = 1, /// Opens the file for reading. Out = 2, /// Opens the file for writing. OutNew = 6, /// Opens the file for writing, creates a new file if it doesn't exist. Append = 10 /// Opens the file for writing, appending new data to the end of the file. } version (Windows) { private import core.sys.windows.windows; extern (Windows) { void FlushFileBuffers(HANDLE hFile); DWORD GetFileType(HANDLE hFile); } } version (Posix) { private import core.sys.posix.fcntl; private import core.sys.posix.unistd; alias HANDLE = int; } /// This subclass is for unbuffered file system streams. class File: Stream { version (Windows) { private HANDLE hFile; } version (Posix) { private HANDLE hFile = -1; } this() { super(); version (Windows) { hFile = null; } version (Posix) { hFile = -1; } isopen = false; } // opens existing handle; use with care! this(HANDLE hFile, FileMode mode) { super(); this.hFile = hFile; readable = cast(bool)(mode & FileMode.In); writeable = cast(bool)(mode & FileMode.Out); version(Windows) { seekable = GetFileType(hFile) == 1; // FILE_TYPE_DISK } else { auto result = lseek(hFile, 0, 0); seekable = (result != ~0); } } /*** * Create the stream with no open file, an open file in read mode, or an open * file with explicit file mode. * mode, if given, is a combination of FileMode.In * (indicating a file that can be read) and FileMode.Out (indicating a file * that can be written). * Opening a file for reading that doesn't exist will error. * Opening a file for writing that doesn't exist will create the file. * The FileMode.OutNew mode will open the file for writing and reset the * length to zero. * The FileMode.Append mode will open the file for writing and move the * file position to the end of the file. */ this(string filename, FileMode mode = FileMode.In) { this(); open(filename, mode); } /*** * Open a file for the stream, in an identical manner to the constructors. * If an error occurs an OpenException is thrown. */ void open(string filename, FileMode mode = FileMode.In) { close(); int access, share, createMode; parseMode(mode, access, share, createMode); seekable = true; readable = cast(bool)(mode & FileMode.In); writeable = cast(bool)(mode & FileMode.Out); version (Windows) { hFile = CreateFileW(filename.tempCStringW(), access, share, null, createMode, 0, null); isopen = hFile != INVALID_HANDLE_VALUE; } version (Posix) { hFile = core.sys.posix.fcntl.open(filename.tempCString(), access | createMode, share); isopen = hFile != -1; } if (!isopen) throw new OpenException(cast(string) ("Cannot open or create file '" ~ filename ~ "'")); else if ((mode & FileMode.Append) == FileMode.Append) seekEnd(0); } private void parseMode(int mode, out int access, out int share, out int createMode) { version (Windows) { share |= FILE_SHARE_READ | FILE_SHARE_WRITE; if (mode & FileMode.In) { access |= GENERIC_READ; createMode = OPEN_EXISTING; } if (mode & FileMode.Out) { access |= GENERIC_WRITE; createMode = OPEN_ALWAYS; // will create if not present } if ((mode & FileMode.OutNew) == FileMode.OutNew) { createMode = CREATE_ALWAYS; // resets file } } version (Posix) { share = octal!666; if (mode & FileMode.In) { access = O_RDONLY; } if (mode & FileMode.Out) { createMode = O_CREAT; // will create if not present access = O_WRONLY; } if (access == (O_WRONLY | O_RDONLY)) { access = O_RDWR; } if ((mode & FileMode.OutNew) == FileMode.OutNew) { access |= O_TRUNC; // resets file } } } /// Create a file for writing. void create(string filename) { create(filename, FileMode.OutNew); } /// ditto void create(string filename, FileMode mode) { close(); open(filename, mode | FileMode.OutNew); } /// Close the current file if it is open; otherwise it does nothing. override void close() { if (isopen) { super.close(); if (hFile) { version (Windows) { CloseHandle(hFile); hFile = null; } else version (Posix) { core.sys.posix.unistd.close(hFile); hFile = -1; } } } } // destructor, closes file if still opened ~this() { close(); } version (Windows) { // returns size of stream override @property ulong size() { assertSeekable(); uint sizehi; uint sizelow = GetFileSize(hFile,&sizehi); return (cast(ulong)sizehi << 32) + sizelow; } } override size_t readBlock(void* buffer, size_t size) { assertReadable(); version (Windows) { auto dwSize = to!DWORD(size); ReadFile(hFile, buffer, dwSize, &dwSize, null); size = dwSize; } else version (Posix) { size = core.sys.posix.unistd.read(hFile, buffer, size); if (size == -1) size = 0; } readEOF = (size == 0); return size; } override size_t writeBlock(const void* buffer, size_t size) { assertWriteable(); version (Windows) { auto dwSize = to!DWORD(size); WriteFile(hFile, buffer, dwSize, &dwSize, null); size = dwSize; } else version (Posix) { size = core.sys.posix.unistd.write(hFile, buffer, size); if (size == -1) size = 0; } return size; } override ulong seek(long offset, SeekPos rel) { assertSeekable(); version (Windows) { int hi = cast(int)(offset>>32); uint low = SetFilePointer(hFile, cast(int)offset, &hi, rel); if ((low == INVALID_SET_FILE_POINTER) && (GetLastError() != 0)) throw new SeekException("unable to move file pointer"); ulong result = (cast(ulong)hi << 32) + low; } else version (Posix) { auto result = lseek(hFile, cast(off_t)offset, rel); if (result == cast(typeof(result))-1) throw new SeekException("unable to move file pointer"); } readEOF = false; return cast(ulong)result; } /*** * For a seekable file returns the difference of the size and position and * otherwise returns 0. */ override @property size_t available() { if (seekable) { ulong lavail = size - position; if (lavail > size_t.max) lavail = size_t.max; return cast(size_t)lavail; } return 0; } // OS-specific property, just in case somebody wants // to mess with underlying API HANDLE handle() { return hFile; } // run a few tests unittest { import std.internal.cstring : tempCString; File file = new File; int i = 666; auto stream_file = undead.internal.file.deleteme ~ "-stream.$$$"; file.create(stream_file); // should be ok to write assert(file.writeable); file.writeLine("Testing stream.d:"); file.writeString("Hello, world!"); file.write(i); // string#1 + string#2 + int should give exacly that version (Windows) assert(file.position == 19 + 13 + 4); version (Posix) assert(file.position == 18 + 13 + 4); // we must be at the end of file assert(file.eof); file.close(); // no operations are allowed when file is closed assert(!file.readable && !file.writeable && !file.seekable); file.open(stream_file); // should be ok to read assert(file.readable); assert(file.available == file.size); char[] line = file.readLine(); char[] exp = "Testing stream.d:".dup; assert(line[0] == 'T'); assert(line.length == exp.length); assert(!std.algorithm.cmp(line, "Testing stream.d:")); // jump over "Hello, " file.seek(7, SeekPos.Current); version (Windows) assert(file.position == 19 + 7); version (Posix) assert(file.position == 18 + 7); assert(!std.algorithm.cmp(file.readString(6), "world!")); i = 0; file.read(i); assert(i == 666); // string#1 + string#2 + int should give exacly that version (Windows) assert(file.position == 19 + 13 + 4); version (Posix) assert(file.position == 18 + 13 + 4); // we must be at the end of file assert(file.eof); file.close(); file.open(stream_file,FileMode.OutNew | FileMode.In); file.writeLine("Testing stream.d:"); file.writeLine("Another line"); file.writeLine(""); file.writeLine("That was blank"); file.position = 0; char[][] lines; foreach(char[] line; file) { lines ~= line.dup; } assert( lines.length == 4 ); assert( lines[0] == "Testing stream.d:"); assert( lines[1] == "Another line"); assert( lines[2] == ""); assert( lines[3] == "That was blank"); file.position = 0; lines = new char[][4]; foreach(ulong n, char[] line; file) { lines[cast(size_t)(n-1)] = line.dup; } assert( lines[0] == "Testing stream.d:"); assert( lines[1] == "Another line"); assert( lines[2] == ""); assert( lines[3] == "That was blank"); file.close(); remove(stream_file.tempCString()); } } /*** * This subclass is for buffered file system streams. * * It is a convenience class for wrapping a File in a BufferedStream. * A buffered stream must be closed explicitly to ensure the final buffer * content is written to the file. */ class BufferedFile: BufferedStream { /// opens file for reading this() { super(new File()); } /// opens file in requested mode and buffer size this(string filename, FileMode mode = FileMode.In, size_t bufferSize = DefaultBufferSize) { super(new File(filename,mode),bufferSize); } /// opens file for reading with requested buffer size this(File file, size_t bufferSize = DefaultBufferSize) { super(file,bufferSize); } /// opens existing handle; use with care! this(HANDLE hFile, FileMode mode, size_t buffersize = DefaultBufferSize) { super(new File(hFile,mode),buffersize); } /// opens file in requested mode void open(string filename, FileMode mode = FileMode.In) { File sf = cast(File)s; sf.open(filename,mode); resetSource(); } /// creates file in requested mode void create(string filename, FileMode mode = FileMode.OutNew) { File sf = cast(File)s; sf.create(filename,mode); resetSource(); } // run a few tests same as File unittest { import std.internal.cstring : tempCString; BufferedFile file = new BufferedFile; int i = 666; auto stream_file = undead.internal.file.deleteme ~ "-stream.$$$"; file.create(stream_file); // should be ok to write assert(file.writeable); file.writeLine("Testing stream.d:"); file.writeString("Hello, world!"); file.write(i); // string#1 + string#2 + int should give exacly that version (Windows) assert(file.position == 19 + 13 + 4); version (Posix) assert(file.position == 18 + 13 + 4); // we must be at the end of file assert(file.eof); long oldsize = cast(long)file.size; file.close(); // no operations are allowed when file is closed assert(!file.readable && !file.writeable && !file.seekable); file.open(stream_file); // should be ok to read assert(file.readable); // test getc/ungetc and size char c1 = file.getc(); file.ungetc(c1); assert( file.size == oldsize ); assert(!std.algorithm.cmp(file.readLine(), "Testing stream.d:")); // jump over "Hello, " file.seek(7, SeekPos.Current); version (Windows) assert(file.position == 19 + 7); version (Posix) assert(file.position == 18 + 7); assert(!std.algorithm.cmp(file.readString(6), "world!")); i = 0; file.read(i); assert(i == 666); // string#1 + string#2 + int should give exacly that version (Windows) assert(file.position == 19 + 13 + 4); version (Posix) assert(file.position == 18 + 13 + 4); // we must be at the end of file assert(file.eof); file.close(); remove(stream_file.tempCString()); } } /// UTF byte-order-mark signatures enum BOM { UTF8, /// UTF-8 UTF16LE, /// UTF-16 Little Endian UTF16BE, /// UTF-16 Big Endian UTF32LE, /// UTF-32 Little Endian UTF32BE, /// UTF-32 Big Endian } private enum int NBOMS = 5; immutable Endian[NBOMS] BOMEndian = [ std.system.endian, Endian.littleEndian, Endian.bigEndian, Endian.littleEndian, Endian.bigEndian ]; immutable ubyte[][NBOMS] ByteOrderMarks = [ [0xEF, 0xBB, 0xBF], [0xFF, 0xFE], [0xFE, 0xFF], [0xFF, 0xFE, 0x00, 0x00], [0x00, 0x00, 0xFE, 0xFF] ]; /*** * This subclass wraps a stream with big-endian or little-endian byte order * swapping. * * UTF Byte-Order-Mark (BOM) signatures can be read and deduced or * written. * Note that an EndianStream should not be used as the source of another * FilterStream since a FilterStream call the source with byte-oriented * read/write requests and the EndianStream will not perform any byte swapping. * The EndianStream reads and writes binary data (non-getc functions) in a * one-to-one * manner with the source stream so the source stream's position and state will be * kept in sync with the EndianStream if only non-getc functions are called. */ class EndianStream : FilterStream { Endian endian; /// Endianness property of the source stream. /*** * Create the endian stream for the source stream source with endianness end. * The default endianness is the native byte order. * The Endian type is defined * in the std.system module. */ this(Stream source, Endian end = std.system.endian) { super(source); endian = end; } /*** * Return -1 if no BOM and otherwise read the BOM and return it. * * If there is no BOM or if bytes beyond the BOM are read then the bytes read * are pushed back onto the ungetc buffer or ungetcw buffer. * Pass ungetCharSize == 2 to use * ungetcw instead of ungetc when no BOM is present. */ int readBOM(int ungetCharSize = 1) { ubyte[4] BOM_buffer; int n = 0; // the number of read bytes int result = -1; // the last match or -1 for (int i=0; i < NBOMS; ++i) { int j; immutable ubyte[] bom = ByteOrderMarks[i]; for (j=0; j < bom.length; ++j) { if (n <= j) { // have to read more if (eof) break; readExact(&BOM_buffer[n++],1); } if (BOM_buffer[j] != bom[j]) break; } if (j == bom.length) // found a match result = i; } ptrdiff_t m = 0; if (result != -1) { endian = BOMEndian[result]; // set stream endianness m = ByteOrderMarks[result].length; } if ((ungetCharSize == 1 && result == -1) || (result == BOM.UTF8)) { while (n-- > m) ungetc(BOM_buffer[n]); } else { // should eventually support unget for dchar as well if (n & 1) // make sure we have an even number of bytes readExact(&BOM_buffer[n++],1); while (n > m) { n -= 2; wchar cw = *(cast(wchar*)&BOM_buffer[n]); fixBO(&cw,2); ungetcw(cw); } } return result; } /*** * Correct the byte order of buffer to match native endianness. * size must be even. */ final void fixBO(const(void)* buffer, size_t size) { if (endian != std.system.endian) { ubyte* startb = cast(ubyte*)buffer; uint* start = cast(uint*)buffer; switch (size) { case 0: break; case 2: { ubyte x = *startb; *startb = *(startb+1); *(startb+1) = x; break; } case 4: { *start = bswap(*start); break; } default: { uint* end = cast(uint*)(buffer + size - uint.sizeof); while (start < end) { uint x = bswap(*start); *start = bswap(*end); *end = x; ++start; --end; } startb = cast(ubyte*)start; ubyte* endb = cast(ubyte*)end; auto len = uint.sizeof - (startb - endb); if (len > 0) fixBO(startb,len); } } } } /*** * Correct the byte order of the given buffer in blocks of the given size and * repeated the given number of times. * size must be even. */ final void fixBlockBO(void* buffer, uint size, size_t repeat) { while (repeat--) { fixBO(buffer,size); buffer += size; } } override void read(out byte x) { readExact(&x, x.sizeof); } override void read(out ubyte x) { readExact(&x, x.sizeof); } override void read(out short x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } override void read(out ushort x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } override void read(out int x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } override void read(out uint x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } override void read(out long x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } override void read(out ulong x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } override void read(out float x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } override void read(out double x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } override void read(out real x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } override void read(out ifloat x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } override void read(out idouble x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } override void read(out ireal x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } override void read(out cfloat x) { readExact(&x, x.sizeof); fixBlockBO(&x,float.sizeof,2); } override void read(out cdouble x) { readExact(&x, x.sizeof); fixBlockBO(&x,double.sizeof,2); } override void read(out creal x) { readExact(&x, x.sizeof); fixBlockBO(&x,real.sizeof,2); } override void read(out char x) { readExact(&x, x.sizeof); } override void read(out wchar x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } override void read(out dchar x) { readExact(&x, x.sizeof); fixBO(&x,x.sizeof); } override wchar getcw() { wchar c; if (prevCr) { prevCr = false; c = getcw(); if (c != '\n') return c; } if (unget.length > 1) { c = unget[unget.length - 1]; unget.length = unget.length - 1; } else { void* buf = &c; size_t n = readBlock(buf,2); if (n == 1 && readBlock(buf+1,1) == 0) throw new ReadException("not enough data in stream"); fixBO(&c,c.sizeof); } return c; } override wchar[] readStringW(size_t length) { wchar[] result = new wchar[length]; readExact(result.ptr, length * wchar.sizeof); fixBlockBO(result.ptr, wchar.sizeof, length); return result; } /// Write the specified BOM b to the source stream. void writeBOM(BOM b) { immutable ubyte[] bom = ByteOrderMarks[b]; writeBlock(bom.ptr, bom.length); } override void write(byte x) { writeExact(&x, x.sizeof); } override void write(ubyte x) { writeExact(&x, x.sizeof); } override void write(short x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } override void write(ushort x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } override void write(int x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } override void write(uint x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } override void write(long x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } override void write(ulong x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } override void write(float x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } override void write(double x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } override void write(real x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } override void write(ifloat x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } override void write(idouble x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } override void write(ireal x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } override void write(cfloat x) { fixBlockBO(&x,float.sizeof,2); writeExact(&x, x.sizeof); } override void write(cdouble x) { fixBlockBO(&x,double.sizeof,2); writeExact(&x, x.sizeof); } override void write(creal x) { fixBlockBO(&x,real.sizeof,2); writeExact(&x, x.sizeof); } override void write(char x) { writeExact(&x, x.sizeof); } override void write(wchar x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } override void write(dchar x) { fixBO(&x,x.sizeof); writeExact(&x, x.sizeof); } override void writeStringW(const(wchar)[] str) { foreach(wchar cw;str) { fixBO(&cw,2); s.writeExact(&cw, 2); } } override @property bool eof() { return s.eof && !ungetAvailable(); } override @property ulong size() { return s.size; } unittest { MemoryStream m; m = new MemoryStream (); EndianStream em = new EndianStream(m,Endian.bigEndian); uint x = 0x11223344; em.write(x); assert( m.data[0] == 0x11 ); assert( m.data[1] == 0x22 ); assert( m.data[2] == 0x33 ); assert( m.data[3] == 0x44 ); em.position = 0; ushort x2 = 0x5566; em.write(x2); assert( m.data[0] == 0x55 ); assert( m.data[1] == 0x66 ); em.position = 0; static ubyte[12] x3 = [1,2,3,4,5,6,7,8,9,10,11,12]; em.fixBO(x3.ptr,12); if (std.system.endian == Endian.littleEndian) { assert( x3[0] == 12 ); assert( x3[1] == 11 ); assert( x3[2] == 10 ); assert( x3[4] == 8 ); assert( x3[5] == 7 ); assert( x3[6] == 6 ); assert( x3[8] == 4 ); assert( x3[9] == 3 ); assert( x3[10] == 2 ); assert( x3[11] == 1 ); } em.endian = Endian.littleEndian; em.write(x); assert( m.data[0] == 0x44 ); assert( m.data[1] == 0x33 ); assert( m.data[2] == 0x22 ); assert( m.data[3] == 0x11 ); em.position = 0; em.write(x2); assert( m.data[0] == 0x66 ); assert( m.data[1] == 0x55 ); em.position = 0; em.fixBO(x3.ptr,12); if (std.system.endian == Endian.bigEndian) { assert( x3[0] == 12 ); assert( x3[1] == 11 ); assert( x3[2] == 10 ); assert( x3[4] == 8 ); assert( x3[5] == 7 ); assert( x3[6] == 6 ); assert( x3[8] == 4 ); assert( x3[9] == 3 ); assert( x3[10] == 2 ); assert( x3[11] == 1 ); } em.writeBOM(BOM.UTF8); assert( m.position == 3 ); assert( m.data[0] == 0xEF ); assert( m.data[1] == 0xBB ); assert( m.data[2] == 0xBF ); em.writeString ("Hello, world"); em.position = 0; assert( m.position == 0 ); assert( em.readBOM() == BOM.UTF8 ); assert( m.position == 3 ); assert( em.getc() == 'H' ); em.position = 0; em.writeBOM(BOM.UTF16BE); assert( m.data[0] == 0xFE ); assert( m.data[1] == 0xFF ); em.position = 0; em.writeBOM(BOM.UTF16LE); assert( m.data[0] == 0xFF ); assert( m.data[1] == 0xFE ); em.position = 0; em.writeString ("Hello, world"); em.position = 0; assert( em.readBOM() == -1 ); assert( em.getc() == 'H' ); assert( em.getc() == 'e' ); assert( em.getc() == 'l' ); assert( em.getc() == 'l' ); em.position = 0; } } /*** * Parameterized subclass that wraps an array-like buffer with a stream * interface. * * The type Buffer must support the length property, opIndex and opSlice. * Compile in release mode when directly instantiating a TArrayStream to avoid * link errors. */ class TArrayStream(Buffer): Stream { Buffer buf; // current data ulong len; // current data length ulong cur; // current file position /// Create the stream for the the buffer buf. Non-copying. this(Buffer buf) { super (); this.buf = buf; this.len = buf.length; readable = writeable = seekable = true; } // ensure subclasses don't violate this invariant() { assert(len <= buf.length); assert(cur <= len); } override size_t readBlock(void* buffer, size_t size) { assertReadable(); ubyte* cbuf = cast(ubyte*) buffer; if (len - cur < size) size = cast(size_t)(len - cur); ubyte[] ubuf = cast(ubyte[])buf[cast(size_t)cur .. cast(size_t)(cur + size)]; cbuf[0 .. size] = ubuf[]; cur += size; return size; } override size_t writeBlock(const void* buffer, size_t size) { assertWriteable(); ubyte* cbuf = cast(ubyte*) buffer; ulong blen = buf.length; if (cur + size > blen) size = cast(size_t)(blen - cur); ubyte[] ubuf = cast(ubyte[])buf[cast(size_t)cur .. cast(size_t)(cur + size)]; ubuf[] = cbuf[0 .. size]; cur += size; if (cur > len) len = cur; return size; } override ulong seek(long offset, SeekPos rel) { assertSeekable(); long scur; // signed to saturate to 0 properly switch (rel) { case SeekPos.Set: scur = offset; break; case SeekPos.Current: scur = cast(long)(cur + offset); break; case SeekPos.End: scur = cast(long)(len + offset); break; default: assert(0); } if (scur < 0) cur = 0; else if (scur > len) cur = len; else cur = cast(ulong)scur; return cur; } override @property size_t available () { return cast(size_t)(len - cur); } /// Get the current memory data in total. @property ubyte[] data() { if (len > size_t.max) throw new StreamException("Stream too big"); const(void)[] res = buf[0 .. cast(size_t)len]; return cast(ubyte[])res; } override string toString() { // assume data is UTF8 return to!(string)(cast(char[])data); } } /* Test the TArrayStream */ unittest { char[100] buf; TArrayStream!(char[]) m; m = new TArrayStream!(char[]) (buf); assert (m.isOpen); m.writeString ("Hello, world"); assert (m.position == 12); assert (m.available == 88); assert (m.seekSet (0) == 0); assert (m.available == 100); assert (m.seekCur (4) == 4); assert (m.available == 96); assert (m.seekEnd (-8) == 92); assert (m.available == 8); assert (m.size == 100); assert (m.seekSet (4) == 4); assert (m.readString (4) == "o, w"); m.writeString ("ie"); assert (buf[0..12] == "Hello, wield"); assert (m.position == 10); assert (m.available == 90); assert (m.size == 100); m.seekSet (0); assert (m.printf ("Answer is %d", 42) == 12); assert (buf[0..12] == "Answer is 42"); } /// This subclass reads and constructs an array of bytes in memory. class MemoryStream: TArrayStream!(ubyte[]) { /// Create the output buffer and setup for reading, writing, and seeking. // clear to an empty buffer. this() { this(cast(ubyte[]) null); } /*** * Create the output buffer and setup for reading, writing, and seeking. * Load it with specific input data. */ this(ubyte[] buf) { super (buf); } this(byte[] buf) { this(cast(ubyte[]) buf); } /// ditto this(char[] buf) { this(cast(ubyte[]) buf); } /// ditto /// Ensure the stream can write count extra bytes from cursor position without an allocation. void reserve(size_t count) { if (cur + count > buf.length) buf.length = cast(uint)((cur + count) * 2); } override size_t writeBlock(const void* buffer, size_t size) { reserve(size); return super.writeBlock(buffer,size); } unittest { MemoryStream m; m = new MemoryStream (); assert (m.isOpen); m.writeString ("Hello, world"); assert (m.position == 12); assert (m.seekSet (0) == 0); assert (m.available == 12); assert (m.seekCur (4) == 4); assert (m.available == 8); assert (m.seekEnd (-8) == 4); assert (m.available == 8); assert (m.size == 12); assert (m.readString (4) == "o, w"); m.writeString ("ie"); assert (cast(char[]) m.data == "Hello, wield"); m.seekEnd (0); m.writeString ("Foo"); assert (m.position == 15); assert (m.available == 0); m.writeString ("Foo foo foo foo foo foo foo"); assert (m.position == 42); m.position = 0; assert (m.available == 42); m.writef("%d %d %s",100,345,"hello"); auto str = m.toString(); assert (str[0..13] == "100 345 hello", str[0 .. 13]); assert (m.available == 29); assert (m.position == 13); MemoryStream m2; m.position = 3; m2 = new MemoryStream (); m2.writeString("before"); m2.copyFrom(m,10); str = m2.toString(); assert (str[0..16] == "before 345 hello"); m2.position = 3; m2.copyFrom(m); auto str2 = m.toString(); str = m2.toString(); assert (str == ("bef" ~ str2)); } } import std.mmfile; /*** * This subclass wraps a memory-mapped file with the stream API. * See std.mmfile module. */ class MmFileStream : TArrayStream!(MmFile) { /// Create stream wrapper for file. this(MmFile file) { super (file); MmFile.Mode mode = file.mode(); writeable = mode > MmFile.Mode.read; } override void flush() { if (isopen) { super.flush(); buf.flush(); } } override void close() { if (isopen) { super.close(); delete buf; buf = null; } } } unittest { auto test_file = undead.internal.file.deleteme ~ "-testing.txt"; MmFile mf = new MmFile(test_file,MmFile.Mode.readWriteNew,100,null); MmFileStream m; m = new MmFileStream (mf); m.writeString ("Hello, world"); assert (m.position == 12); assert (m.seekSet (0) == 0); assert (m.seekCur (4) == 4); assert (m.seekEnd (-8) == 92); assert (m.size == 100); assert (m.seekSet (4)); assert (m.readString (4) == "o, w"); m.writeString ("ie"); ubyte[] dd = m.data; assert ((cast(char[]) dd)[0 .. 12] == "Hello, wield"); m.position = 12; m.writeString ("Foo"); assert (m.position == 15); m.writeString ("Foo foo foo foo foo foo foo"); assert (m.position == 42); m.close(); mf = new MmFile(test_file); m = new MmFileStream (mf); assert (!m.writeable); char[] str = m.readString(12); assert (str == "Hello, wield"); m.close(); std.file.remove(test_file); } /*** * This subclass slices off a portion of another stream, making seeking relative * to the boundaries of the slice. * * It could be used to section a large file into a * set of smaller files, such as with tar archives. Reading and writing a * SliceStream does not modify the position of the source stream if it is * seekable. */ class SliceStream : FilterStream { private { ulong pos; // our position relative to low ulong low; // low stream offset. ulong high; // high stream offset. bool bounded; // upper-bounded by high. } /*** * Indicate both the source stream to use for reading from and the low part of * the slice. * * The high part of the slice is dependent upon the end of the source * stream, so that if you write beyond the end it resizes the stream normally. */ this (Stream s, ulong low) in { assert (low <= s.size); } body { super(s); this.low = low; this.high = 0; this.bounded = false; } /*** * Indicate the high index as well. * * Attempting to read or write past the high * index results in the end being clipped off. */ this (Stream s, ulong low, ulong high) in { assert (low <= high); assert (high <= s.size); } body { super(s); this.low = low; this.high = high; this.bounded = true; } invariant() { if (bounded) assert (pos <= high - low); else // size() does not appear to be const, though it should be assert (pos <= (cast()s).size - low); } override size_t readBlock (void *buffer, size_t size) { assertReadable(); if (bounded && size > high - low - pos) size = cast(size_t)(high - low - pos); ulong bp = s.position; if (seekable) s.position = low + pos; size_t ret = super.readBlock(buffer, size); if (seekable) { pos = s.position - low; s.position = bp; } return ret; } override size_t writeBlock (const void *buffer, size_t size) { assertWriteable(); if (bounded && size > high - low - pos) size = cast(size_t)(high - low - pos); ulong bp = s.position; if (seekable) s.position = low + pos; size_t ret = s.writeBlock(buffer, size); if (seekable) { pos = s.position - low; s.position = bp; } return ret; } override ulong seek(long offset, SeekPos rel) { assertSeekable(); long spos; switch (rel) { case SeekPos.Set: spos = offset; break; case SeekPos.Current: spos = cast(long)(pos + offset); break; case SeekPos.End: if (bounded) spos = cast(long)(high - low + offset); else spos = cast(long)(s.size - low + offset); break; default: assert(0); } if (spos < 0) pos = 0; else if (bounded && spos > high - low) pos = high - low; else if (!bounded && spos > s.size - low) pos = s.size - low; else pos = cast(ulong)spos; readEOF = false; return pos; } override @property size_t available() { size_t res = s.available; ulong bp = s.position; if (bp <= pos+low && pos+low <= bp+res) { if (!bounded || bp+res <= high) return cast(size_t)(bp + res - pos - low); else if (high <= bp+res) return cast(size_t)(high - pos - low); } return 0; } unittest { MemoryStream m; SliceStream s; m = new MemoryStream ((cast(char[])"Hello, world").dup); s = new SliceStream (m, 4, 8); assert (s.size == 4); assert (m.position == 0); assert (s.position == 0); assert (m.available == 12); assert (s.available == 4); assert (s.writeBlock (cast(char *) "Vroom", 5) == 4); assert (m.position == 0); assert (s.position == 4); assert (m.available == 12); assert (s.available == 0); assert (s.seekEnd (-2) == 2); assert (s.available == 2); assert (s.seekEnd (2) == 4); assert (s.available == 0); assert (m.position == 0); assert (m.available == 12); m.seekEnd(0); m.writeString("\nBlaho"); assert (m.position == 18); assert (m.available == 0); assert (s.position == 4); assert (s.available == 0); s = new SliceStream (m, 4); assert (s.size == 14); assert (s.toString () == "Vrooorld\nBlaho"); s.seekEnd (0); assert (s.available == 0); s.writeString (", etcetera."); assert (s.position == 25); assert (s.seekSet (0) == 0); assert (s.size == 25); assert (m.position == 18); assert (m.size == 29); assert (m.toString() == "HellVrooorld\nBlaho, etcetera."); } }