From 39b78293cbab8ce9df020a6754d3aae6624dab71 Mon Sep 17 00:00:00 2001 From: Ralph Amissah Date: Mon, 22 Aug 2016 10:31:34 -0400 Subject: sdlang uses lexer.d which uses stream.d which phobos is to drop 2016-10 * stream & its depends: src/undead/stream.d src/undead/internal/file.d src/undead/doformat.d * https://github.com/DigitalMars/undeaD/blob/master/src/undead/ --- src/undead/stream.d | 3071 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3071 insertions(+) create mode 100644 src/undead/stream.d (limited to 'src/undead/stream.d') diff --git a/src/undead/stream.d b/src/undead/stream.d new file mode 100644 index 0000000..e31b381 --- /dev/null +++ b/src/undead/stream.d @@ -0,0 +1,3071 @@ +// 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."); + } +} -- cgit v1.2.3