// 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 write. */ 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 std.format. * * 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: std.format. * 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 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."); } }