/* * Copyright (c) 2001, 2002 * Pavel "EvilOne" Minayev * with buffering added by Ben Hinkle * * 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 std.stream; /* Class structure: * InputStream interface for reading * OutputStream interface for writing * Stream abstract base of stream implementations * File an OS file stream * BufferedStream a buffered stream wrapping another stream * BufferedFile a buffered File * MemoryStream a stream entirely stored in main memory * SliceStream a portion of another stream * TArrayStream a stream wrapping an array-like buffer */ // generic Stream exception, base class for all // other Stream exceptions class StreamException: Exception { this(char[] msg) { super(msg); } } alias StreamException StreamError; // for backwards compatibility // thrown when unable to read data from Stream class ReadException: StreamException { this(char[] msg) { super(msg); } } alias ReadException ReadError; // for backwards compatibility // thrown when unable to write data to Stream class WriteException: StreamException { this(char[] msg) { super(msg); } } alias WriteException WriteError; // for backwards compatibility // thrown when unable to move Stream pointer class SeekException: StreamException { this(char[] msg) { super(msg); } } alias SeekException SeekError; // for backwards compatibility // seek whence... enum SeekPos { Set, Current, End } import std.format; alias std.format.va_list va_list; private import std.c.stdio; alias std.c.stdio.va_list c_va_list; private import std.utf; version (Windows) { private import std.file; } // Interface for readable streams interface InputStream { // reads block of data of specified size, // throws ReadException on error void readExact(void* buffer, uint size); // reads block of data big enough to fill the given // array, returns actual number of bytes read uint read(ubyte[] buffer); // read a single value of desired type, // throw ReadException on error void read(out byte x); void read(out ubyte x); void read(out short x); void read(out ushort x); void read(out int x); void read(out uint x); void read(out long x); void read(out ulong x); void read(out float x); void read(out double x); void read(out real x); void read(out ireal x); void read(out creal x); void read(out char x); void read(out wchar x); // reads a string, written earlier by write() void read(out char[] s); // reads a Unicode string, written earlier by write() void read(out wchar[] s); // reads a line, terminated by either CR, LF, CR/LF, or EOF char[] readLine(); // reads a line, terminated by either CR, LF, CR/LF, or EOF // reusing the memory in result and reallocating if needed char[] readLine(char[] 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(); // 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(wchar[] result); // reads a string of given length, throws // ReadException on error char[] readString(uint length); // reads a Unicode string of given length, throws // ReadException on error wchar[] readStringW(uint length); // reads and returns next character from the stream, // handles characters pushed back by ungetc() char getc(); // reads and returns next Unicode character from the // stream, handles characters pushed back by ungetc() wchar getcw(); // pushes back character c into the stream; only has // effect on further calls to getc() and getcw() char ungetc(char c); // pushes back Unicode character c into the stream; only // has effect on further calls to getc() and getcw() wchar ungetcw(wchar c); int vscanf(char[] fmt, c_va_list args); int scanf(char[] format, ...); uint available(); } // Interface for writable streams interface OutputStream { // writes block of data of specified size, // throws WriteException on error void writeExact(void* buffer, uint size); // writes the given array of bytes, returns // actual number of bytes written uint write(ubyte[] buffer); // write a single value of desired type, // throw WriteException on error void write(byte x); void write(ubyte x); void write(short x); void write(ushort x); void write(int x); void write(uint x); void write(long x); void write(ulong x); void write(float x); void write(double x); void write(real x); void write(ireal x); void write(creal x); void write(char x); void write(wchar x); // writes a string, together with its length void write(char[] s); // writes a Unicode string, together with its length void write(wchar[] s); // writes a line, throws WriteException on error void writeLine(char[] s); // writes a UNICODE line, throws WriteException on error void writeLineW(wchar[] s); // writes a string, throws WriteException on error void writeString(char[] s); // writes a UNICODE string, throws WriteException on error void writeStringW(wchar[] s); // writes data to stream using vprintf() syntax, // returns number of bytes written uint vprintf(char[] format, c_va_list args); // writes data to stream using printf() syntax, // returns number of bytes written uint printf(char[] format, ...); // writes data to stream using writef() syntax, void writef(...); // writes data with trailing newline void writefln(...); } // base class for all streams; not really abstract, // but its instances will do nothing useful class Stream : InputStream, OutputStream { private import std.string, crc32, std.c.stdlib, std.c.stdio; // stream abilities bit readable = false; bit writeable = false; bit seekable = false; private bit isopen = true; this() {} // reads block of data of specified size, // returns actual number of bytes read abstract uint readBlock(void* buffer, uint size); // reads block of data of specified size, // throws ReadException on error void readExact(void* buffer, uint size) { uint readsize = readBlock(buffer, size); if (readsize != size) 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 uint read(ubyte[] buffer) { return readBlock(buffer, 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 ireal 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); } // reads a string, written earlier by write() void read(out char[] s) { int len; read(len); s = readString(len); } // reads a Unicode string, written earlier by write() void read(out wchar[] s) { int 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) { uint strlen = 0; try { char ch = getc(); while (readable) { switch (ch) { case '\r': { ch = getc(); if (ch != '\n') ungetc(ch); } case '\n': result.length = strlen; return result; default: if (strlen < result.length) { result[strlen] = ch; } else { result ~= ch; } strlen++; } ch = getc(); } } catch (ReadException e) { // either this is end of stream, which is okay, // or something bad occured while reading if (!eof()) throw e; } 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) { uint strlen = 0; try { wchar c = getcw(); while (readable) { switch (c) { case '\r': { c = getcw(); if (c != '\n') ungetcw(c); } case '\n': result.length = strlen; return result; default: if (strlen < result.length) { result[strlen] = c; } else { result ~= c; } strlen++; } c = getcw(); } } catch (ReadException e) { // either this is end of stream, which is okay, // or something bad occured while reading if (!eof()) throw e; } result.length = strlen; return result; } // reads a string of given length, throws // ReadException on error char[] readString(uint length) { char[] result = new char[length]; readExact(result, length); return result; } // reads a Unicode string of given length, throws // ReadException on error wchar[] readStringW(uint length) { wchar[] result = new wchar[length]; readExact(result, result.length * wchar.sizeof); return result; } // unget buffer private wchar[] unget; // reads and returns next character from the stream, // handles characters pushed back by ungetc() char getc() { char c; if (unget.length > 1) { c = unget[unget.length - 1]; unget.length = unget.length - 1; } else read(c); return c; } // reads and returns next Unicode character from the // stream, handles characters pushed back by ungetc() wchar getcw() { wchar c; if (unget.length > 1) { c = unget[unget.length - 1]; unget.length = unget.length - 1; } else read(c); return c; } // pushes back character c into the stream; only has // effect on further calls to getc() and getcw() char ungetc(char 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) { // 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 vscanf(char[] fmt, c_va_list args) { void** arg = cast(void**) args; int count = 0, i = 0; try { char c = getc(); while (i < fmt.length) { if (fmt[i] == '%') // a field { i++; bit 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; // D string? bit dstr = false; if (fmt[i] == '.') { i++; if (fmt[i] == '*') { dstr = true; i++; } } // read the modifier char modifier = fmt[i]; if (modifier == 'h' || modifier == 'l' || modifier == 'L') i++; else modifier = 0; // 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++; } bit neg = false; if (c == '-') { neg = true; c = getc(); count++; } else if (c == '+') { c = getc(); count++; } char ifmt = 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 (isoctdigit(c) && width) { n = n * 010 + (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; } if (neg) n = -n; // check the modifier and cast the pointer // to appropriate type switch (modifier) { case 'h': // short { *cast(short*)*arg = n; } break; case 'L': // long { *cast(long*)*arg = n; } break; default: // int *cast(int*)*arg = n; } i++; } break; case 'f': // float case 'F': case 'e': case 'E': case 'g': case 'G': { while (iswhite(c)) { c = getc(); count++; } bit neg = false; if (c == '-') { neg = true; c = getc(); count++; } else if (c == '+') { c = getc(); count++; } real n = 0; while (isdigit(c) && width) { n = n * 10 + (c - '0'); width--; c = getc(); count++; } if (width && c == '.') { width--; c = getc(); count++; double frac = 1; while (isdigit(c) && width) { n = n * 10 + (c - '0'); frac *= 10; width--; c = getc(); count++; } n /= frac; } if (width && (c == 'e' || c == 'E')) { width--; c = getc(); count++; if (width) { bit 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--) n /= 10; } else { while (exp--) n *= 10; } } } if (neg) n = -n; // check the modifier and cast the pointer // to appropriate type switch (modifier) { case 'l': // double { *cast(double*)*arg = n; } break; case 'L': // real { *cast(real*)*arg = n; } break; default: // float *cast(float*)*arg = n; } i++; } break; case 's': // ANSI string { while (iswhite(c)) { c = getc(); count++; } char[] s; while (!iswhite(c)) { s ~= c; c = getc(); count++; } if (dstr) // D string (char[]) *cast(char[]*)*arg = s; else // C string (char*) { s ~= 0; (cast(char*)*arg)[0 .. s.length] = s[]; } i++; } break; case 'c': // character(s) { char* s = cast(char*)*arg; if (width < 0) width = 1; else while (iswhite(c)) { c = getc(); count++; } while (width--) { *(s++) = c; c = getc(); count++; } i++; } break; case 'n': // number of chars read so far { *cast(int*)*arg = count; i++; } break; default: // read character as is goto nws; } arg++; } 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); } catch (ReadException e) { // either this is end of stream, which is okay, // or something bad occured while reading if (!eof()) throw e; } return count; } int scanf(char[] format, ...) { c_va_list ap; ap = cast(c_va_list) &format; ap += format.sizeof; return vscanf(format, ap); } // returns estimated number of bytes available for immediate reading uint available() { return 0; } // writes block of data of specified size, // returns actual number of bytes written abstract uint writeBlock(void* buffer, uint size); // writes block of data of specified size, // throws WriteException on error void writeExact(void* buffer, uint size) { if (writeBlock(buffer, size) != size) throw new WriteException("unable to write to stream"); } // writes the given array of bytes, returns // actual number of bytes written uint write(ubyte[] buffer) { return writeBlock(buffer, 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(ireal 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); } // writes a string, together with its length void write(char[] s) { write(s.length); writeString(s); } // writes a Unicode string, together with its length void write(wchar[] s) { write(s.length); writeStringW(s); } // writes a line, throws WriteException on error void writeLine(char[] s) { writeString(s); version (Win32) writeString("\r\n"); else version (Mac) writeString("\r"); else writeString("\n"); } // writes a UNICODE line, throws WriteException on error void writeLineW(wchar[] s) { writeStringW(s); version (Win32) writeStringW("\r\n"); else version (Mac) writeStringW("\r"); else writeStringW("\n"); } // writes a string, throws WriteException on error void writeString(char[] s) { writeExact(s, s.length); } // writes a UNICODE string, throws WriteException on error void writeStringW(wchar[] s) { writeExact(s, s.length * wchar.sizeof); } // writes data to stream using vprintf() syntax, // returns number of bytes written uint vprintf(char[] format, c_va_list args) { // shamelessly stolen from OutBuffer, // by Walter's permission char[1024] buffer; char* p = buffer; char* f = toStringz(format); uint psize = buffer.length; int count; while (true) { version (Win32) { count = _vsnprintf(p, psize, f, args); if (count != -1) break; psize *= 2; p = cast(char*) alloca(psize); } else version (linux) { 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 uint printf(char[] format, ...) { c_va_list ap; ap = cast(c_va_list) &format; ap += format.sizeof; return vprintf(format, ap); } private void doFormatCallback(dchar c) { char[4] buf; char[] b; b = std.utf.toUTF8(buf, c); writeString(b); } // writes data to stream using writef() syntax, void writef(...) { doFormat(&doFormatCallback,_arguments,_argptr); } // writes data with trailing newline void writefln(...) { doFormat(&doFormatCallback,_arguments,_argptr); writeLine(""); } // copies all data from given stream into this one, // may throw ReadException or WriteException on failure void copyFrom(Stream s) { uint pos = position(); s.position(0); copyFrom(s, s.size()); s.position(pos); } // copies specified number of bytes from given stream into // this one, may throw ReadException or WriteException on failure void copyFrom(Stream s, uint count) { ubyte[] buf; buf.length = s.size(); s.readExact(buf, buf.length); writeExact(buf, buf.length); } // moves pointer to given position, relative to beginning of stream, // end of stream, or current position, returns new position abstract ulong seek(long offset, SeekPos whence); // seek from the beginning of the stream. ulong seekSet(long offset) { return seek (offset, SeekPos.Set); } // seek from the current point in the stream. ulong seekCur(long offset) { return seek (offset, SeekPos.Current); } // seek from the end of the stream. ulong seekEnd(long offset) { return seek (offset, SeekPos.End); } // sets position void position(ulong pos) { seek(pos, SeekPos.Set); } // returns current position ulong position() { return seek(0, SeekPos.Current); } // returns size of stream ulong size() { ulong pos = position(), result = seek(0, SeekPos.End); position(pos); return result; } // returns true if end of stream is reached, false otherwise bit eof() { return position() == size(); } // returns true if the stream is open bit 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(); isopen = false; } // creates a string in memory containing copy of stream data override char[] toString() { if (!isopen) return super.toString(); uint pos = position(); char[] result; result.length = size(); position(0); readBlock(result, result.length); position(pos); return result; } // calculates CRC-32 of data in stream override uint toHash() { if (!isopen) return super.toHash(); ulong pos = position(); uint crc = init_crc32 (); position(0); for (long i = 0; i < size(); i++) { ubyte c; read(c); crc = update_crc32(c, crc); } position(pos); return crc; } } // A stream that wraps a source stream in a buffer class BufferedStream : Stream { Stream s; // source stream ubyte[] buffer; // buffer, if any uint bufferCurPos; // current position in buffer uint bufferLen; // amount of data in buffer bit bufferDirty = false; uint 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); } const uint DefaultBufferSize = 8192; this(Stream source, uint bufferSize = DefaultBufferSize) { super(); if (bufferSize) buffer = new ubyte[bufferSize]; s = source; updateAttribs(); } void updateAttribs() { if (s !== null) { readable = s.readable; writeable = s.writeable; seekable = s.seekable; isopen = s.isOpen(); } else { readable = writeable = seekable = false; isopen = false; } streamPos = 0; bufferLen = bufferSourcePos = bufferCurPos = 0; bufferDirty = false; } // close source and stream override void close() { if (isopen) { super.close(); s.close(); updateAttribs(); } } // reads block of data of specified size using any buffered data // returns actual number of bytes read override uint readBlock(void* result, uint size) { ubyte* buf = cast(ubyte*)result; uint readsize = 0; if (bufferCurPos + size <= bufferLen) { // buffer has all the data so copy it buf[0 .. size] = buffer[bufferCurPos .. bufferCurPos+size]; bufferCurPos += size; readsize = size; goto ExitRead; } readsize = bufferLen - bufferCurPos; if (readsize > 0) { // buffer has some data so copy what is left buf[0 .. readsize] = buffer[bufferCurPos .. bufferLen]; buf += readsize; bufferCurPos += readsize; size -= readsize; } flush(); if (size >= buffer.length) { // buffer can't hold the data so fill output buffer directly uint siz = s.readBlock(buf, size); readsize += siz; streamPos += siz; } else { // read a new block into buffer bufferLen = s.readBlock(buffer, buffer.length); if (bufferLen < size) size = bufferLen; buf[0 .. size] = buffer[0 .. size]; bufferSourcePos = bufferLen; streamPos += bufferLen; bufferCurPos = size; readsize += size; } ExitRead: return readsize; } // write block of data of specified size // returns actual number of bytes written override uint writeBlock(void* result, uint size) { ubyte* buf = cast(ubyte*)result; uint writesize = 0; if (bufferLen == 0) { // buffer is empty so fill it if possible if ((size < buffer.length) && (readable)) { // read in data if the buffer is currently empty bufferLen = s.readBlock(buffer,buffer.length); bufferSourcePos = bufferLen; streamPos += bufferLen; } else if (size >= buffer.length) { // buffer can't hold the data so write it directly and exit writesize = s.writeBlock(buf,size); streamPos += writesize; goto ExitWrite; } } if (bufferCurPos + size <= buffer.length) { // buffer has space for all the data so copy it and exit buffer[bufferCurPos .. bufferCurPos+size] = buf[0 .. size]; bufferCurPos += size; bufferLen = bufferCurPos > bufferLen ? bufferCurPos : bufferLen; writesize = size; 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; size -= writesize; bufferDirty = true; } assert(bufferCurPos == buffer.length); assert(bufferLen == buffer.length); flush(); writesize += writeBlock(buf,size); ExitWrite: return writesize; } override ulong seek(long offset, SeekPos whence) in { assert(seekable); assert(s.seekable); } body { if ((whence != SeekPos.Current) || (offset + bufferCurPos < 0) || (offset + bufferCurPos >= bufferLen)) { flush(); streamPos = s.seek(offset,whence); } else { bufferCurPos += offset; } return streamPos-bufferSourcePos+bufferCurPos; } override void flush() out { assert(bufferCurPos == 0); assert(bufferSourcePos == 0); assert(bufferLen == 0); } body { super.flush(); if (writeable && bufferDirty) { if (bufferSourcePos != 0) { // move actual file pointer to front of buffer streamPos = s.seek(-bufferSourcePos, SeekPos.Current); } // write buffer out bufferSourcePos = s.writeBlock(buffer,bufferLen); if (bufferSourcePos != bufferLen) { throw new WriteException("Unable to write to stream"); } } long diff = bufferCurPos-bufferSourcePos; if (diff != 0) { // 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 bit eof() { if ((buffer.length == 0) || !readable) { return super.eof(); } if (bufferCurPos == bufferLen) { if ((bufferLen != buffer.length) && (bufferLen != 0)) { return true; } } else return false; uint res = s.readBlock(buffer,buffer.length); bufferSourcePos = bufferLen = res; streamPos += res; bufferCurPos = 0; return res == 0; } // returns size of stream ulong size() { flush(); return s.size(); } // returns estimated number of bytes available for immediate reading uint available() { return bufferLen - bufferCurPos; } } // generic File error, base class for all // other File exceptions class StreamFileException: StreamException { this(char[] msg) { super(msg); } } // thrown when unable to open file class OpenException: StreamFileException { this(char[] msg) { super(msg); } } // access modes; may be or'ed enum FileMode { In = 1, Out = 2, OutNew = 6, // includes FileMode.Out Append = 10 // includes FileMode.Out } version (Win32) { private import std.c.windows.windows; // BVH: should be part of windows.d extern (Windows) void FlushFileBuffers(HANDLE hFile); } version (linux) { private import std.c.linux.linux; alias int HANDLE; } // just a file on disk without buffering class File: Stream { version (Win32) { private HANDLE hFile; } version (linux) { private HANDLE hFile = -1; } this() { super(); version (Win32) { hFile = null; } version (linux) { hFile = -1; } isopen = false; } // opens existing handle; use with care! this(HANDLE hFile, FileMode mode) { super(); this.hFile = hFile; readable = cast(bit)(mode & FileMode.In); writeable = cast(bit)(mode & FileMode.Out); } // opens file in requested mode this(char[] filename, FileMode mode = FileMode.In) { this(); open(filename, mode); } // opens file in requested mode void open(char[] filename, FileMode mode = FileMode.In) { close(); int access, share, createMode; parseMode(mode, access, share, createMode); seekable = true; readable = cast(bit)(mode & FileMode.In); writeable = cast(bit)(mode & FileMode.Out); version (Win32) { if (std.file.useWfuncs) { hFile = CreateFileW(std.utf.toUTF16z(filename), access, share, null, createMode, 0, null); } else { hFile = CreateFileA(std.file.toMBSz(filename), access, share, null, createMode, 0, null); } isopen = hFile != INVALID_HANDLE_VALUE; } version (linux) { hFile = std.c.linux.linux.open(toStringz(filename), access | createMode, share); isopen = hFile != -1; } if (!isopen) throw new OpenException("file '" ~ filename ~ "' not found"); else if ((mode & FileMode.Append) == FileMode.Append) seekEnd(0); } private void parseMode(int mode, out int access, out int share, out int createMode) { version (Win32) { if (mode & FileMode.In) { access |= GENERIC_READ; share |= FILE_SHARE_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 (linux) { if (mode & FileMode.In) { access = O_RDONLY; share = 0660; } if (mode & FileMode.Out) { createMode = O_CREAT; // will create if not present access = O_WRONLY; share = 0660; } if (access == (O_WRONLY | O_RDONLY)) { access = O_RDWR; } if ((mode & FileMode.OutNew) == FileMode.OutNew) { access |= O_TRUNC; // resets file } } } // creates file for writing void create(char[] filename) { create(filename, FileMode.OutNew); } // creates file in requested mode void create(char[] filename, FileMode mode) { close(); open(filename, mode | FileMode.OutNew); } override void flush() { super.flush(); version (Win32) { FlushFileBuffers(hFile); } } // closes file, if it is open; otherwise, does nothing override void close() { if (isopen && hFile) { version (Win32) { CloseHandle(hFile); hFile = null; } version (linux) { std.c.linux.linux.close(hFile); hFile = -1; } readable = writeable = seekable = false; isopen = false; } } // destructor, closes file if still opened ~this() { close(); } version (Win32) { // returns size of stream ulong size() { uint sizehi; uint sizelow = GetFileSize(hFile,&sizehi); return (sizehi << 32)+sizelow; } } override uint readBlock(void* buffer, uint size) // since in-blocks are not inherited, redefine them in { assert(readable); } body { version (Win32) { ReadFile(hFile, buffer, size, &size, null); } version (linux) { size = std.c.linux.linux.read(hFile, buffer, size); if (size == -1) size = 0; } return size; } override uint writeBlock(void* buffer, uint size) // since in-blocks are not inherited, redefine them in { assert(writeable); } body { version (Win32) { WriteFile(hFile, buffer, size, &size, null); } version (linux) { size = std.c.linux.linux.write(hFile, buffer, size); } return size; } override ulong seek(long offset, SeekPos rel) // since in-blocks are not inherited, redefine them in { assert(seekable); } body { version (Win32) { uint result = SetFilePointer(hFile, offset, null, rel); if (result == 0xFFFFFFFF) throw new SeekException("unable to move file pointer"); } version (linux) { ulong result = lseek(hFile, offset, rel); if (result == 0xFFFFFFFF) throw new SeekException("unable to move file pointer"); } return result; } // OS-specific property, just in case somebody wants // to mess with underlying API HANDLE handle() { return hFile; } // run a few tests unittest { File file = new File; int i = 666; file.create("stream.$$$"); // 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 (Win32) assert(file.position() == 19 + 13 + 4); version (linux) 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.$$$"); // should be ok to read assert(file.readable); char[] line = file.readLine(); char[] exp = "Testing stream.d:"; assert(line[0] == 'T'); assert(line.length == exp.length); assert(!std.string.cmp(line, "Testing stream.d:")); // jump over "Hello, " file.seek(7, SeekPos.Current); version (Win32) assert(file.position() == 19 + 7); version (linux) assert(file.position() == 18 + 7); assert(!std.string.cmp(file.readString(6), "world!")); i = 0; file.read(i); assert(i == 666); // string#1 + string#2 + int should give exacly that version (Win32) assert(file.position() == 19 + 13 + 4); version (linux) assert(file.position() == 18 + 13 + 4); // we must be at the end of file assert(file.eof()); file.close(); remove("stream.$$$"); } } // a buffered file on disk class BufferedFile: BufferedStream { // opens file for reading this() { super(new File()); } // opens file in requested mode and buffer size this(char[] filename, FileMode mode = FileMode.In, uint bufferSize = DefaultBufferSize) { super(new File(filename,mode),bufferSize); } // opens file for reading with requested buffer size this(File file, uint bufferSize = DefaultBufferSize) { super(file,bufferSize); } // opens existing handle; use with care! this(HANDLE hFile, FileMode mode, uint buffersize) { super(new File(hFile,mode),buffersize); } // opens file in requested mode void open(char[] filename, FileMode mode = FileMode.In) in { assert(!(s is null)); } body { File sf = cast(File)s; sf.open(filename,mode); updateAttribs(); } // creates file in requested mode void create(char[] filename, FileMode mode = FileMode.Out) { File sf = cast(File)s; sf.create(filename,mode); updateAttribs(); } // run a few tests same as File unittest { BufferedFile file = new BufferedFile; int i = 666; file.create("stream.$$$"); // 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 (Win32) assert(file.position() == 19 + 13 + 4); version (linux) 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.$$$"); // should be ok to read assert(file.readable); assert(!std.string.cmp(file.readLine(), "Testing stream.d:")); // jump over "Hello, " file.seek(7, SeekPos.Current); version (Win32) assert(file.position() == 19 + 7); version (linux) assert(file.position() == 18 + 7); assert(!std.string.cmp(file.readString(6), "world!")); i = 0; file.read(i); assert(i == 666); // string#1 + string#2 + int should give exacly that version (Win32) assert(file.position() == 19 + 13 + 4); version (linux) assert(file.position() == 18 + 13 + 4); // we must be at the end of file assert(file.eof()); file.close(); remove("stream.$$$"); } } // Parameterized stream class that wraps an array-like type. // The Buffer type must support .length, opIndex and opSlice class TArrayStream(Buffer): Stream { Buffer buf; // current data uint len; // current data length uint cur; // current file position // use this buffer, non-copying. this(Buffer buf) { super (); this.buf = buf; this.len = buf.length; readable = writeable = seekable = true; } override uint readBlock(void* buffer, uint size) { ubyte* cbuf = cast(ubyte*) buffer; if (len - cur < size) size = len - cur; ubyte[] ubuf = cast(ubyte[])buf[cur .. cur + size]; cbuf[0 .. size] = ubuf[]; cur += size; return size; } override uint writeBlock(void* buffer, uint size) { ubyte* cbuf = cast(ubyte*) buffer; ubyte[] ubuf = cast(ubyte[])buf[cur .. cur + size]; ubuf[] = cbuf[0 .. size]; cur += size; if (cur > len) len = cur; return size; } override ulong seek(long offset, SeekPos rel) { long scur; // signed to saturate to 0 properly switch (rel) { case SeekPos.Set: scur = offset; break; case SeekPos.Current: scur = cur + offset; break; case SeekPos.End: scur = len + offset; break; } if (scur < 0) cur = 0; else if (scur > len) cur = len; else cur = scur; return cur; } } /* Test the TArrayStream */ unittest { char[100] buf; TArrayStream!(char[]) m; m = new TArrayStream!(char[]) (buf); 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) == 4); assert (m.readString (4) == "o, w"); m.writeString ("ie"); assert (buf[0..12] == "Hello, wield"); } // virtual stream residing in memory class MemoryStream: TArrayStream!(ubyte[]) { // clear to an empty buffer. this() { this(cast(ubyte[]) null); } // use this buffer, non-copying. this(ubyte[] buf) { super (buf); } // use this buffer, non-copying. this(byte[] buf) { this(cast(ubyte[]) buf); } // use this buffer, non-copying. this(char[] buf) { this(cast(ubyte[]) buf); } // ensure the stream can hold this many bytes. void reserve(uint count) { if (cur + count > buf.length) buf.length = (cur + count) * 2; } // returns pointer to stream data ubyte[] data() { return buf [0 .. len]; } override uint writeBlock(void* buffer, uint size) { reserve(size); return super.writeBlock(buffer,size); } override char[] toString() { return cast(char[]) data (); } /* Test the whole class. */ unittest { MemoryStream m; m = new MemoryStream (); m.writeString ("Hello, world"); assert (m.position () == 12); assert (m.seekSet (0) == 0); assert (m.seekCur (4) == 4); assert (m.seekEnd (-8) == 4); 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); m.writeString ("Foo foo foo foo foo foo foo"); assert (m.position () == 42); m.seekSet(0); m.writef("%d %d %s",100,345,"hello"); char[] str = m.toString; assert (str[0..13] == "100 345 hello"); } } /**************************** BVH 8/04: currently fails due to MmFile being auto, so leaving commented out until MmFile can be used in objects import std.mmfile; // stream wrapping memory-mapped files alias TArrayStream!(MmFile) MmFileStream; unittest { MmFile mf = new MmFile("testing.txt"); 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) == 4); 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); m.writeString ("Foo foo foo foo foo foo foo"); assert (m.position () == 42); } ********************************/ // slices off a portion of another stream, making seeking // relative to the boundaries of the slice. class SliceStream : Stream { Stream base; // stream to base this off of. ulong low; // low stream offset. ulong high; // high stream offset. bit bounded; // upper-bounded by high. bit nestClose; // if set, close base when closing this stream. // set the base stream and the low offset but leave the high unbounded. this (Stream base, ulong low) in { assert (base !== null); assert (low <= base.size ()); } body { super (); this.base = base; this.low = low; this.high = 0; this.bounded = false; readable = base.readable; writeable = base.writeable; seekable = base.seekable; } // set the base stream, the low offset, and the high offset. this (Stream base, ulong low, ulong high) in { assert (base !== null); assert (low <= high); assert (high <= base.size ()); } body { super (); this.base = base; this.low = low; this.high = high; this.bounded = true; readable = base.readable; writeable = base.writeable; seekable = base.seekable; } override void close () { try { if (base !== null && nestClose) base.close (); } finally base = null; } override uint readBlock (void *buffer, uint size) in { assert (readable); } body { if (bounded) { ulong pos = base.position (); if (pos > high) return 0; if (size > high - pos) size = high - pos; } return base.readBlock (buffer, size); } override uint writeBlock (void *buffer, uint size) in { assert (writeable); } body { if (bounded) { ulong pos = base.position (); if (pos > high) return 0; if (size > high - pos) size = high - pos; } return base.writeBlock (buffer, size); } override ulong seek(long offset, SeekPos rel) in { assert (seekable); } body { long output; switch (rel) { case SeekPos.Set: output = low + offset; break; case SeekPos.Current: output = base.position () + offset; break; case SeekPos.End: if (bounded) output = high + offset; else { output = base.seek (offset, SeekPos.End); assert (output >= low); return output - low; } } if (output < low) output = low; else if (bounded && output > high) output = high; output = base.seek (output, SeekPos.Set); assert (output >= low); return output - low; } /* Test the whole class. */ unittest { MemoryStream m; SliceStream s; m = new MemoryStream ((cast(char[])"Hello, world").dup); s = new SliceStream (m, 4, 8); assert (s.size () == 4); assert (s.writeBlock (cast(char *) "Vroom", 5) == 4); assert (s.position () == 4); assert (s.seekEnd (-2) == 2); assert (s.seekEnd (2) == 4); s = new SliceStream (m, 4); assert (s.size () == 8); assert (s.toString () == "Vrooorld"); s.seekEnd (0); s.writeString (", etcetera."); assert (s.position () == 19); assert (s.seekSet (0) == 0); assert (m.position () == 4); } } // helper functions private bit iswhite(char c) { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; } private bit isdigit(char c) { return c >= '0' && c <= '9'; } private bit isoctdigit(char c) { return c >= '0' && c <= '7'; } private bit ishexdigit(char c) { return isdigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } // standard IO devices File stdin, stdout, stderr; version (Win32) { // API imports private extern(Windows) { private import std.c.windows.windows; HANDLE GetStdHandle(DWORD); } static this() { // open standard I/O devices stdin = new File(GetStdHandle(-10), FileMode.In); stdout = new File(GetStdHandle(-11), FileMode.Out); stderr = new File(GetStdHandle(-12), FileMode.Out); } } version (linux) { static this() { // open standard I/O devices stdin = new File(0, FileMode.In); stdout = new File(1, FileMode.Out); stderr = new File(2, FileMode.Out); } } import std.string; import std.file;