mirror of
https://github.com/dlang/phobos.git
synced 2025-04-30 15:10:46 +03:00
1498 lines
31 KiB
D
1498 lines
31 KiB
D
/*
|
|
* Copyright (c) 2001, 2002
|
|
* Pavel "EvilOne" Minayev
|
|
*
|
|
* 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;
|
|
|
|
// generic Stream error, base class for all
|
|
// other Stream exceptions
|
|
class StreamError: Error
|
|
{
|
|
this(char[] msg) { super(msg); }
|
|
}
|
|
|
|
// thrown when unable to read data from Stream
|
|
class ReadError: StreamError
|
|
{
|
|
this(char[] msg) { super(msg); }
|
|
}
|
|
|
|
// thrown when unable to write data to Stream
|
|
class WriteError: StreamError
|
|
{
|
|
this(char[] msg) { super(msg); }
|
|
}
|
|
|
|
// thrown when unable to move Stream pointer
|
|
class SeekError: StreamError
|
|
{
|
|
this(char[] msg) { super(msg); }
|
|
}
|
|
|
|
// seek whence...
|
|
enum SeekPos
|
|
{
|
|
Set,
|
|
Current,
|
|
End
|
|
}
|
|
|
|
// base class for all streams; not really abstract,
|
|
// but its instances will do nothing useful
|
|
class Stream
|
|
{
|
|
private import std.string, crc32, std.c.stdlib, std.c.stdio;
|
|
|
|
// for compatibility
|
|
deprecated enum: SeekPos
|
|
{
|
|
set = SeekPos.Set,
|
|
current = SeekPos.Current,
|
|
end = SeekPos.End
|
|
}
|
|
|
|
// stream abilities
|
|
bit readable = false;
|
|
bit writeable = false;
|
|
bit seekable = false;
|
|
|
|
this() { }
|
|
|
|
// close the stream somehow; the default
|
|
// does nothing
|
|
void close()
|
|
{
|
|
}
|
|
|
|
// 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 ReadError on error
|
|
void readExact(void* buffer, uint size)
|
|
{
|
|
if (readBlock(buffer, size) != size)
|
|
throw new ReadError("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 ReadError on error
|
|
void read(out byte x) { readExact(&x, x.size); }
|
|
void read(out ubyte x) { readExact(&x, x.size); }
|
|
void read(out short x) { readExact(&x, x.size); }
|
|
void read(out ushort x) { readExact(&x, x.size); }
|
|
void read(out int x) { readExact(&x, x.size); }
|
|
void read(out uint x) { readExact(&x, x.size); }
|
|
void read(out long x) { readExact(&x, x.size); }
|
|
void read(out ulong x) { readExact(&x, x.size); }
|
|
void read(out float x) { readExact(&x, x.size); }
|
|
void read(out double x) { readExact(&x, x.size); }
|
|
void read(out real x) { readExact(&x, x.size); }
|
|
void read(out ireal x) { readExact(&x, x.size); }
|
|
void read(out creal x) { readExact(&x, x.size); }
|
|
void read(out char x) { readExact(&x, x.size); }
|
|
void read(out wchar x) { readExact(&x, x.size); }
|
|
|
|
// 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()
|
|
{
|
|
char[] result;
|
|
try
|
|
{
|
|
char ch = getc();
|
|
while (readable)
|
|
{
|
|
switch (ch)
|
|
{
|
|
case '\r':
|
|
{
|
|
ch = getc();
|
|
if (ch != '\n')
|
|
ungetc(ch);
|
|
}
|
|
|
|
case '\n':
|
|
return result;
|
|
|
|
default:
|
|
result ~= ch;
|
|
}
|
|
ch = getc();
|
|
}
|
|
}
|
|
catch (ReadError e)
|
|
{
|
|
// either this is end of stream, which is okay,
|
|
// or something bad occured while reading
|
|
if (!eof())
|
|
throw e;
|
|
}
|
|
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()
|
|
{
|
|
wchar[] result;
|
|
try
|
|
{
|
|
wchar c = getcw();
|
|
while (readable)
|
|
{
|
|
switch (c)
|
|
{
|
|
case '\r':
|
|
{
|
|
c = getcw();
|
|
if (c != '\n')
|
|
ungetcw(c);
|
|
}
|
|
|
|
case '\n':
|
|
return result;
|
|
|
|
default:
|
|
result ~= c;
|
|
}
|
|
c = getcw();
|
|
}
|
|
}
|
|
catch (ReadError e)
|
|
{
|
|
// either this is end of stream, which is okay,
|
|
// or something bad occured while reading
|
|
if (!eof())
|
|
throw e;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// reads a string of given length, throws
|
|
// ReadError on error
|
|
char[] readString(uint length)
|
|
{
|
|
char[] result = new char[length];
|
|
readExact(result, length);
|
|
return result;
|
|
}
|
|
|
|
// reads a Unicode string of given length, throws
|
|
// ReadError on error
|
|
wchar[] readStringW(uint length)
|
|
{
|
|
wchar[] result = new wchar[length];
|
|
readExact(result, result.length * wchar.size);
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
unget ~= c;
|
|
return c;
|
|
}
|
|
|
|
int vscanf(char[] fmt, va_list args)
|
|
{
|
|
void** arg = cast(void**) args;
|
|
int count = 0, i = 0;
|
|
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);
|
|
return count;
|
|
}
|
|
|
|
int scanf(char[] format, ...)
|
|
{
|
|
va_list ap;
|
|
ap = cast(va_list) &format;
|
|
ap += format.size;
|
|
return vscanf(format, ap);
|
|
}
|
|
|
|
// 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 WriteError on error
|
|
void writeExact(void* buffer, uint size)
|
|
{
|
|
if (writeBlock(buffer, size) != size)
|
|
throw new WriteError("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 WriteError on error
|
|
void write(byte x) { writeExact(&x, x.size); }
|
|
void write(ubyte x) { writeExact(&x, x.size); }
|
|
void write(short x) { writeExact(&x, x.size); }
|
|
void write(ushort x) { writeExact(&x, x.size); }
|
|
void write(int x) { writeExact(&x, x.size); }
|
|
void write(uint x) { writeExact(&x, x.size); }
|
|
void write(long x) { writeExact(&x, x.size); }
|
|
void write(ulong x) { writeExact(&x, x.size); }
|
|
void write(float x) { writeExact(&x, x.size); }
|
|
void write(double x) { writeExact(&x, x.size); }
|
|
void write(real x) { writeExact(&x, x.size); }
|
|
void write(ireal x) { writeExact(&x, x.size); }
|
|
void write(creal x) { writeExact(&x, x.size); }
|
|
void write(char x) { writeExact(&x, x.size); }
|
|
void write(wchar x) { writeExact(&x, x.size); }
|
|
|
|
// 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 WriteError 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 WriteError on error
|
|
void writeLineW(wchar[] s)
|
|
{
|
|
writeStringW(s);
|
|
version (Win32)
|
|
writeStringW("\r\n");
|
|
/+
|
|
else version (Mac)
|
|
writeStringW("\r");
|
|
+/
|
|
else // probably *NIX
|
|
writeStringW("\n");
|
|
}
|
|
|
|
// writes a string, throws WriteError on error
|
|
void writeString(char[] s)
|
|
{
|
|
writeExact(s, s.length);
|
|
}
|
|
|
|
// writes a UNICODE string, throws WriteError on error
|
|
void writeStringW(wchar[] s)
|
|
{
|
|
writeExact(s, s.length * wchar.size);
|
|
}
|
|
|
|
// writes data to stream using vprintf() syntax,
|
|
// returns number of bytes written
|
|
uint vprintf(char[] format, 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;
|
|
/+if (p != buffer)
|
|
c.stdlib.free(p);
|
|
p = (char *) c.stdlib.malloc(psize); // buffer too small, try again with larger size
|
|
+/
|
|
p = cast(char*) alloca(psize);
|
|
}
|
|
else
|
|
throw new Error("unsupported platform");
|
|
}
|
|
writeString(p[0 .. count]);
|
|
/+
|
|
version (linux)
|
|
{
|
|
if (p != buffer)
|
|
c.stdlib.free(p);
|
|
}
|
|
+/
|
|
return count;
|
|
}
|
|
|
|
// writes data to stream using printf() syntax,
|
|
// returns number of bytes written
|
|
uint printf(char[] format, ...)
|
|
{
|
|
va_list ap;
|
|
ap = cast(va_list) &format;
|
|
ap += format.size;
|
|
return vprintf(format, ap);
|
|
}
|
|
|
|
// copies all data from given stream into this one,
|
|
// may throw ReadError or WriteError 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 ReadError or WriteError 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(); }
|
|
|
|
// creates a string in memory containing copy of stream data
|
|
override char[] 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()
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
// generic File error, base class for all
|
|
// other File exceptions
|
|
class StreamFileError: StreamError
|
|
{
|
|
this(char[] msg) { super(msg); }
|
|
}
|
|
|
|
// thrown when unable to open file
|
|
class OpenError: StreamFileError
|
|
{
|
|
this(char[] msg) { super(msg); }
|
|
}
|
|
|
|
// thrown when unable to create file
|
|
class CreateError: StreamFileError
|
|
{
|
|
this(char[] msg) { super(msg); }
|
|
}
|
|
|
|
// access modes; may be or'ed
|
|
enum FileMode
|
|
{
|
|
In = 1,
|
|
Out = 2,
|
|
// for compatibility with older versions
|
|
input = In,
|
|
output = Out
|
|
}
|
|
|
|
// just a file on disk
|
|
class File: Stream
|
|
{
|
|
version (Win32)
|
|
{
|
|
private import std.c.windows.windows;
|
|
private HANDLE hFile;
|
|
}
|
|
version (linux)
|
|
{
|
|
private import std.c.linux.linux;
|
|
alias int HANDLE;
|
|
private HANDLE hFile = -1;
|
|
}
|
|
|
|
// for compatibility with old versions...
|
|
deprecated enum: FileMode
|
|
{
|
|
toread = FileMode.In,
|
|
towrite = FileMode.Out
|
|
}
|
|
|
|
this()
|
|
{
|
|
version (Win32)
|
|
{
|
|
hFile = null;
|
|
}
|
|
version (linux)
|
|
{
|
|
hFile = -1;
|
|
}
|
|
}
|
|
|
|
// opens existing handle; use with care!
|
|
this(HANDLE hFile, FileMode mode)
|
|
{
|
|
this.hFile = hFile;
|
|
readable = cast(bit)(mode & FileMode.In);
|
|
writeable = cast(bit)(mode & FileMode.Out);
|
|
}
|
|
|
|
// opens file for reading
|
|
this(char[] filename) { this(); open(filename); }
|
|
|
|
// opens file in requested mode
|
|
this(char[] filename, FileMode mode) { this(); open(filename, mode); }
|
|
|
|
// destructor, closes file if still opened
|
|
~this() { close(); }
|
|
|
|
// opens file for reading
|
|
void open(char[] filename) { open(filename, FileMode.In); }
|
|
|
|
// opens file in requested mode
|
|
void open(char[] filename, FileMode mode)
|
|
{
|
|
close();
|
|
int access = 0, share = 0;
|
|
version (Win32)
|
|
{
|
|
if (mode & FileMode.In)
|
|
{
|
|
readable = true;
|
|
access |= GENERIC_READ;
|
|
share |= FILE_SHARE_READ;
|
|
}
|
|
if (mode & FileMode.Out)
|
|
{
|
|
writeable = true;
|
|
access |= GENERIC_WRITE;
|
|
}
|
|
seekable = true;
|
|
hFile = CreateFileA(toStringz(filename), access, share,
|
|
null, OPEN_EXISTING, 0, null);
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
throw new OpenError("file '" ~ filename ~ "' not found");
|
|
}
|
|
version (linux)
|
|
{
|
|
if (mode & FileMode.In)
|
|
{
|
|
readable = true;
|
|
access = O_RDONLY;
|
|
share = 0660;
|
|
}
|
|
if (mode & FileMode.Out)
|
|
{
|
|
writeable = true;
|
|
access = O_CREAT | O_WRONLY;
|
|
share = 0660;
|
|
}
|
|
seekable = true;
|
|
hFile = std.c.linux.linux.open(toStringz(filename), access, share);
|
|
if (hFile == -1)
|
|
throw new OpenError("file '" ~ filename ~ "' not found");
|
|
}
|
|
}
|
|
|
|
// creates file for writing
|
|
void create(char[] filename) { create(filename, FileMode.Out); }
|
|
|
|
// creates file in requested mode
|
|
void create(char[] filename, FileMode mode)
|
|
{
|
|
close();
|
|
int access = 0, share = 0;
|
|
version (Win32)
|
|
{
|
|
if (mode & FileMode.In)
|
|
{
|
|
access |= GENERIC_READ;
|
|
share |= FILE_SHARE_READ;
|
|
}
|
|
if (mode & FileMode.Out)
|
|
{
|
|
access |= GENERIC_WRITE;
|
|
}
|
|
hFile = CreateFileA(toStringz(filename), access, share,
|
|
null, CREATE_ALWAYS, 0, null);
|
|
seekable = true;
|
|
readable = cast(bit)(mode & FileMode.In);
|
|
writeable = cast(bit)(mode & FileMode.Out);
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
throw new CreateError("unable to create file '" ~ filename ~ "'");
|
|
}
|
|
version (linux)
|
|
{
|
|
if (mode & FileMode.In)
|
|
{
|
|
readable = true;
|
|
access = O_RDONLY;
|
|
share = 0660;
|
|
}
|
|
if (mode & FileMode.Out)
|
|
{
|
|
writeable = true;
|
|
access = O_CREAT | O_WRONLY | O_TRUNC;
|
|
share = 0660;
|
|
}
|
|
seekable = true;
|
|
hFile = std.c.linux.linux.open(toStringz(filename), access, share);
|
|
if (hFile == -1)
|
|
throw new OpenError("file '" ~ filename ~ "' not found");
|
|
}
|
|
}
|
|
|
|
// closes file, if it is open; otherwise, does nothing
|
|
override void close()
|
|
{
|
|
if (hFile)
|
|
{
|
|
version (Win32)
|
|
{
|
|
CloseHandle(hFile);
|
|
hFile = null;
|
|
}
|
|
version (linux)
|
|
{
|
|
std.c.linux.linux.close(hFile);
|
|
hFile = -1;
|
|
}
|
|
readable = writeable = seekable = false;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
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 SeekError("unable to move file pointer");
|
|
}
|
|
version (linux)
|
|
{
|
|
uint result = lseek(hFile, offset, rel);
|
|
if (result == 0xFFFFFFFF)
|
|
throw new SeekError("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);
|
|
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.$$$");
|
|
}
|
|
}
|
|
|
|
// virtual stream residing in memory
|
|
class MemoryStream: Stream
|
|
{
|
|
ubyte[] buf; // current data
|
|
uint len; // current data length
|
|
uint cur; // current file position
|
|
|
|
// clear to an empty buffer.
|
|
this() { this((ubyte[]) null); }
|
|
|
|
// use this buffer, non-copying.
|
|
this(ubyte[] buf)
|
|
{
|
|
super ();
|
|
this.buf = buf;
|
|
this.len = buf.length;
|
|
readable = writeable = seekable = true;
|
|
}
|
|
|
|
// use this buffer, non-copying.
|
|
this(byte[] buf) { this((ubyte[]) buf); }
|
|
|
|
// use this buffer, non-copying.
|
|
this(char[] buf) { this((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 readBlock(void* buffer, uint size)
|
|
// since in-blocks are not inherited, redefine them
|
|
in
|
|
{
|
|
assert(readable);
|
|
}
|
|
body
|
|
{
|
|
ubyte* cbuf = cast(ubyte*) buffer;
|
|
if (len - cur < size)
|
|
size = len - cur;
|
|
cbuf[0 .. size] = buf[cur .. cur + size];
|
|
cur += size;
|
|
return size;
|
|
}
|
|
|
|
override uint writeBlock(void* buffer, uint size)
|
|
// since in-blocks are not inherited, redefine them
|
|
in
|
|
{
|
|
assert(writeable);
|
|
}
|
|
body
|
|
{
|
|
ubyte* cbuf = cast(ubyte*) buffer;
|
|
|
|
reserve (size);
|
|
buf[cur .. cur + size] = cbuf[0 .. size];
|
|
cur += size;
|
|
if (cur > len)
|
|
len = cur;
|
|
return size;
|
|
}
|
|
|
|
override ulong seek(long offset, SeekPos rel)
|
|
// since in-blocks are not inherited, redefine them
|
|
in
|
|
{
|
|
assert(seekable);
|
|
}
|
|
body
|
|
{
|
|
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;
|
|
}
|
|
|
|
override char[] toString()
|
|
{
|
|
return (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 ((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 ((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;
|