mirror of
https://github.com/dlang/phobos.git
synced 2025-04-28 14:10:30 +03:00

std.contracts: added functions pointsTo() std.numeric: minor unittest fixes. std.bitmanip: fixed code bloat issue, reintroduced FloatRep and DoubleRep. std.conv: minor simplification of implementation. std.regexp: added reference to ECMA standard in the documentation. std.getopt: changed return type from bool to void, error is signaled by use of exceptions. std.functional: added unaryFun, binaryFun, adjoin. std.string: updated documentation, changed code to compile with warnings enabled. std.traits: changed FieldTypeTuple; added RepresentationTypeTuple, hasAliasing; fixed bug 1826; added call to flush() from within write; fixed unlisted bug in lines(). std.algorithm: added map, reduce, filter, inPlace, move, swap, overwriteAdjacent, find, findRange, findBoyerMoore, findAdjacent, findAmong, findAmongSorted, canFind, canFindAmong, canFindAmongSorted, count, equal, overlap, min, max, mismatch, EditOp, none, substitute, insert, remove, levenshteinDistance, levenshteinDistanceAndPath, copy, copyIf, iterSwap, swapRanges, reverse, rotate, SwapStrategy, Unstable, Semistable, Stable, eliminate, partition, nthElement, sort, schwartzSort, partialSort, isSorted, makeIndex, schwartzMakeIndex, lowerBound, upperBound, equalRange, canFindSorted. std.thread: fixed so it compiles with warnings enabled. std.file: made getSize() faster under Linux. std.random: fixed so it compiles with warnings enabled; improved function uniform so it deduces type generated from its arguments. std.format: added fixes to make formatting work with const data. std.path: minor documentation changes.
1293 lines
34 KiB
D
1293 lines
34 KiB
D
|
|
// Written in the D programming language.
|
|
|
|
/* Written by Walter Bright and Andrei Alexandrescu
|
|
* http://www.digitalmars.com/d
|
|
* Placed in the Public Domain.
|
|
*/
|
|
|
|
/********************************
|
|
* Standard I/O functions that extend $(B std.c.stdio).
|
|
* $(B std.c.stdio) is $(D_PARAM public)ally imported when importing
|
|
* $(B std.stdio).
|
|
*
|
|
* Authors:
|
|
*
|
|
* $(WEB digitalmars.com, Walter Bright), $(WEB erdani.org, Andrei
|
|
Alexandrescu)
|
|
*
|
|
* Macros:
|
|
* WIKI=Phobos/StdStdio
|
|
*/
|
|
|
|
module std.stdio;
|
|
|
|
public import std.c.stdio;
|
|
|
|
import std.format;
|
|
import std.utf;
|
|
import std.string;
|
|
import std.gc;
|
|
import std.c.stdlib;
|
|
import std.c.string;
|
|
import std.c.stddef;
|
|
import std.conv;
|
|
import std.traits;
|
|
import std.contracts;
|
|
import std.file;
|
|
import std.typetuple;
|
|
|
|
version (DigitalMars)
|
|
{
|
|
version (Windows)
|
|
{
|
|
// Specific to the way Digital Mars C does stdio
|
|
version = DIGITAL_MARS_STDIO;
|
|
}
|
|
}
|
|
|
|
version (DIGITAL_MARS_STDIO)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
// Specific to the way Gnu C does stdio
|
|
version = GCC_IO;
|
|
import std.c.linux.linux;
|
|
}
|
|
|
|
version (DIGITAL_MARS_STDIO)
|
|
{
|
|
extern (C)
|
|
{
|
|
/* **
|
|
* Digital Mars under-the-hood C I/O functions
|
|
*/
|
|
int _fputc_nlock(int, FILE*);
|
|
int _fputwc_nlock(int, FILE*);
|
|
int _fgetc_nlock(FILE*);
|
|
int _fgetwc_nlock(FILE*);
|
|
int __fp_lock(FILE*);
|
|
void __fp_unlock(FILE*);
|
|
}
|
|
alias _fputc_nlock FPUTC;
|
|
alias _fputwc_nlock FPUTWC;
|
|
alias _fgetc_nlock FGETC;
|
|
alias _fgetwc_nlock FGETWC;
|
|
|
|
alias __fp_lock FLOCK;
|
|
alias __fp_unlock FUNLOCK;
|
|
}
|
|
else version (GCC_IO)
|
|
{
|
|
/* **
|
|
* Gnu under-the-hood C I/O functions; see
|
|
* http://www.gnu.org/software/libc/manual/html_node/I_002fO-on-Streams.html#I_002fO-on-Streams
|
|
*/
|
|
extern (C)
|
|
{
|
|
int fputc_unlocked(int, FILE*);
|
|
int fputwc_unlocked(wchar_t, FILE*);
|
|
int fgetc_unlocked(FILE*);
|
|
int fgetwc_unlocked(FILE*);
|
|
void flockfile(FILE*);
|
|
void funlockfile(FILE*);
|
|
ssize_t getline(char**, size_t*, FILE*);
|
|
ssize_t getdelim (char**, size_t*, int, FILE*);
|
|
|
|
private size_t fwrite_unlocked(const(void)* ptr,
|
|
size_t size, size_t n, FILE *stream);
|
|
}
|
|
|
|
alias fputc_unlocked FPUTC;
|
|
alias fputwc_unlocked FPUTWC;
|
|
alias fgetc_unlocked FGETC;
|
|
alias fgetwc_unlocked FGETWC;
|
|
|
|
alias flockfile FLOCK;
|
|
alias funlockfile FUNLOCK;
|
|
}
|
|
else
|
|
{
|
|
static assert(0, "unsupported C I/O system");
|
|
}
|
|
|
|
|
|
/*********************
|
|
* Thrown if I/O errors happen.
|
|
*/
|
|
class StdioException : Exception
|
|
{
|
|
uint errno; // operating system error code
|
|
|
|
this(string msg)
|
|
{
|
|
super(msg);
|
|
}
|
|
|
|
this(uint errno)
|
|
{
|
|
version (linux)
|
|
{ char[80] buf = void;
|
|
auto s = std.string.strerror_r(errno, buf.ptr, buf.length);
|
|
}
|
|
else
|
|
{
|
|
auto s = std.string.strerror(errno);
|
|
}
|
|
super(std.string.toString(s).idup);
|
|
}
|
|
|
|
static void opCall(string msg)
|
|
{
|
|
throw new StdioException(msg);
|
|
}
|
|
|
|
static void opCall()
|
|
{
|
|
throw new StdioException(getErrno());
|
|
}
|
|
}
|
|
|
|
private
|
|
void writefx(FILE* fp, TypeInfo[] arguments, void* argptr, int newline=false)
|
|
{
|
|
int orientation = fwide(fp, 0);
|
|
|
|
/* Do the file stream locking at the outermost level
|
|
* rather than character by character.
|
|
*/
|
|
FLOCK(fp);
|
|
scope(exit) FUNLOCK(fp);
|
|
|
|
if (orientation <= 0) // byte orientation or no orientation
|
|
{
|
|
void putc(dchar c)
|
|
{
|
|
if (c <= 0x7F)
|
|
{
|
|
FPUTC(c, fp);
|
|
}
|
|
else
|
|
{ char[4] buf;
|
|
auto b = std.utf.toUTF8(buf, c);
|
|
for (size_t i = 0; i < b.length; i++)
|
|
FPUTC(b[i], fp);
|
|
}
|
|
}
|
|
|
|
std.format.doFormat(&putc, arguments, argptr);
|
|
if (newline)
|
|
FPUTC('\n', fp);
|
|
}
|
|
else if (orientation > 0) // wide orientation
|
|
{
|
|
version (Windows)
|
|
{
|
|
void putcw(dchar c)
|
|
{
|
|
assert(isValidDchar(c));
|
|
if (c <= 0xFFFF)
|
|
{
|
|
FPUTWC(c, fp);
|
|
}
|
|
else
|
|
{ wchar[2] buf;
|
|
|
|
buf[0] = cast(wchar) ((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
|
|
buf[1] = cast(wchar) (((c - 0x10000) & 0x3FF) + 0xDC00);
|
|
FPUTWC(buf[0], fp);
|
|
FPUTWC(buf[1], fp);
|
|
}
|
|
}
|
|
}
|
|
else version (linux)
|
|
{
|
|
void putcw(dchar c)
|
|
{
|
|
FPUTWC(c, fp);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
static assert(0);
|
|
}
|
|
|
|
std.format.doFormat(&putcw, arguments, argptr);
|
|
if (newline)
|
|
FPUTWC('\n', fp);
|
|
}
|
|
}
|
|
|
|
/***********************************
|
|
* If the first argument $(D_PARAM args[0]) is a $(D_PARAM FILE*), for
|
|
* each argument $(D_PARAM arg) in $(D_PARAM args[1..$]), format the
|
|
* argument (as per $(LINK2 std_conv.html, to!(string)(arg))) and
|
|
* write the resulting string to $(D_PARAM args[0]). If $(D_PARAM
|
|
* args[0]) is not a $(D_PARAM FILE*), the call is equivalent to
|
|
* $(D_PARAM write(stdout, args)).
|
|
*
|
|
* A call without any arguments will fail to compile. In the
|
|
* exceedingly rare case you'd want to print a $(D_PARAM FILE*) to
|
|
* $(D_PARAM stdout) as a hex pointer, $(D_PARAM write("", myFilePtr))
|
|
* will do the trick.
|
|
*
|
|
* In case of an I/O error, throws an StdioException.
|
|
*/
|
|
void write(T...)(T args)
|
|
{
|
|
static if (is(typeof(args[0]) : FILE*))
|
|
{
|
|
alias args[0] target;
|
|
static const first = 1;
|
|
}
|
|
else
|
|
{
|
|
alias stdout target;
|
|
static const first = 0;
|
|
}
|
|
writef(target, "", args[first .. $]);
|
|
static if (args.length && is(typeof(args[$ - 1]) : dchar)) {
|
|
if (args[$ - 1] == '\n') fflush(target);
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
void[] buf;
|
|
write(buf);
|
|
// test write
|
|
string file = "dmd-build-test.deleteme.txt";
|
|
FILE* f = fopen(file, "w");
|
|
assert(f, getcwd());
|
|
scope(exit) { std.file.remove(file); }
|
|
write(f, "Hello, ", "world number ", 42, "!");
|
|
fclose(f) == 0 || assert(false);
|
|
assert(cast(char[]) std.file.read(file) == "Hello, world number 42!");
|
|
// test write on stdout
|
|
auto saveStdout = stdout;
|
|
scope(exit) stdout = saveStdout;
|
|
stdout = fopen(file, "w");
|
|
assert(stdout);
|
|
Object obj;
|
|
write("Hello, ", "world number ", 42, "! ", obj);
|
|
fclose(stdout) == 0 || assert(false);
|
|
auto result = cast(char[]) std.file.read(file);
|
|
assert(result == "Hello, world number 42! null", result);
|
|
}
|
|
|
|
/***********************************
|
|
* Equivalent to $(D_PARAM write(args, '\n')). Calling $(D_PARAM
|
|
* writeln) without arguments is valid and just prints a newline to
|
|
* the standard output.
|
|
*/
|
|
void writeln(T...)(T args)
|
|
{
|
|
write(args, '\n');
|
|
}
|
|
|
|
unittest
|
|
{
|
|
// test writeln
|
|
string file = "dmd-build-test.deleteme.txt";
|
|
FILE* f = fopen(file, "w");
|
|
assert(f);
|
|
scope(exit) { std.file.remove(file); }
|
|
writeln(f, "Hello, ", "world number ", 42, "!");
|
|
fclose(f) == 0 || assert(false);
|
|
assert(cast(char[]) std.file.read(file) == "Hello, world number 42!\n");
|
|
// test writeln on stdout
|
|
auto saveStdout = stdout;
|
|
scope(exit) stdout = saveStdout;
|
|
stdout = fopen(file, "w");
|
|
assert(stdout);
|
|
writeln("Hello, ", "world number ", 42, "!");
|
|
fclose(stdout) == 0 || assert(false);
|
|
assert(cast(char[]) std.file.read(file) == "Hello, world number 42!\n");
|
|
}
|
|
|
|
/***********************************
|
|
* If the first argument $(D_PARAM args[0]) is a $(D_PARAM FILE*), use
|
|
* $(LINK2 std_format.html#format-string, the format specifier) in
|
|
* $(D_PARAM args[1]) to control the formatting of $(D_PARAM
|
|
* args[2..$]), and write the resulting string to $(D_PARAM args[0]).
|
|
* If $(D_PARAM arg[0]) is not a $(D_PARAM FILE*), the call is
|
|
* equivalent to $(D_PARAM writef(stdout, args)).
|
|
*
|
|
|
|
IMPORTANT:
|
|
|
|
New behavior starting with D 2.006: unlike previous versions,
|
|
$(D_PARAM writef) (and also $(D_PARAM writefln)) only scans its first
|
|
string argument for format specifiers, but not subsequent string
|
|
arguments. This decision was made because the old behavior made it
|
|
unduly hard to simply print string variables that occasionally
|
|
embedded percent signs.
|
|
|
|
Also new starting with 2.006 is support for positional
|
|
parameters with
|
|
$(LINK2 http://opengroup.org/onlinepubs/009695399/functions/printf.html,
|
|
POSIX) syntax.
|
|
|
|
Example:
|
|
|
|
-------------------------
|
|
writef("Date: %2$s %1$s", "October", 5); // "Date: 5 October"
|
|
------------------------
|
|
|
|
The positional and non-positional styles can be mixed in the same
|
|
format string. (POSIX leaves this behavior undefined.) The internal
|
|
counter for non-positional parameters tracks the next parameter after
|
|
the largest positional parameter already used.
|
|
|
|
New starting with 2.008: raw format specifiers. Using the "%r"
|
|
specifier makes $(D_PARAM writef) simply write the binary
|
|
representation of the argument. Use "%-r" to write numbers in little
|
|
endian format, "%+r" to write numbers in big endian format, and "%r"
|
|
to write numbers in platform-native format.
|
|
|
|
*/
|
|
|
|
void writef(T...)(T args)
|
|
{
|
|
PrivateFileWriter!(char) w;
|
|
static const errorMessage =
|
|
"You must pass a formatting string as the first"
|
|
" argument to writef. If no formatting is needed,"
|
|
" you may want to use write.";
|
|
static if (is(typeof(args[0]) : FILE*))
|
|
{
|
|
alias args[0] target;
|
|
static const first = 1;
|
|
}
|
|
else
|
|
{
|
|
alias stdout target;
|
|
static const first = 0;
|
|
}
|
|
w.backend = target;
|
|
FLOCK(w.backend);
|
|
scope(exit) FUNLOCK(w.backend);
|
|
static if (!isSomeString!(T[first]))
|
|
{
|
|
// compatibility hack
|
|
std.format.formattedWrite(w, "", args[first .. $]);
|
|
}
|
|
else
|
|
{
|
|
std.format.formattedWrite(w, args[first .. $]);
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
// test writef
|
|
string file = "dmd-build-test.deleteme.txt";
|
|
auto f = fopen(file, "w");
|
|
assert(f);
|
|
scope(exit) { std.file.remove(file); }
|
|
writef(f, "Hello, %s world number %s!", "nice", 42);
|
|
fclose(f) == 0 || assert(false);
|
|
assert(cast(char[]) std.file.read(file) == "Hello, nice world number 42!");
|
|
// test write on stdout
|
|
auto saveStdout = stdout;
|
|
scope(exit) stdout = saveStdout;
|
|
stdout = fopen(file, "w");
|
|
assert(stdout);
|
|
writef("Hello, %s world number %s!", "nice", 42);
|
|
fclose(stdout) == 0 || assert(false);
|
|
assert(cast(char[]) std.file.read(file) == "Hello, nice world number 42!");
|
|
}
|
|
|
|
/***********************************
|
|
* Equivalent to $(D_PARAM writef(args, '\n')).
|
|
*/
|
|
void writefln(T...)(T args)
|
|
{
|
|
writef(args, '\n');
|
|
}
|
|
|
|
unittest
|
|
{
|
|
// test writefln
|
|
string file = "dmd-build-test.deleteme.txt";
|
|
FILE* f = fopen(file, "w");
|
|
assert(f);
|
|
scope(exit) { std.file.remove(file); }
|
|
writefln(f, "Hello, %s world number %s!", "nice", 42);
|
|
fclose(f) == 0 || assert(false);
|
|
assert(cast(char[]) std.file.read(file) == "Hello, nice world number 42!\n");
|
|
// test write on stdout
|
|
auto saveStdout = stdout;
|
|
scope(exit) stdout = saveStdout;
|
|
stdout = fopen(file, "w");
|
|
assert(stdout);
|
|
writefln("Hello, %s world number %s!", "nice", 42);
|
|
foreach (F ; TypeTuple!(ifloat, idouble, ireal))
|
|
{
|
|
F a = 5i;
|
|
F b = a % 2;
|
|
writefln(b);
|
|
}
|
|
fclose(stdout) == 0 || assert(false);
|
|
auto read = cast(char[]) std.file.read(file);
|
|
assert(read == "Hello, nice world number 42!\n1\n1\n1\n", read);
|
|
}
|
|
|
|
/***********************************
|
|
* Kept for backward compatibility. Use $(D_PARAM writef) instead.
|
|
*/
|
|
void fwritef(FILE* fp, ...)
|
|
{
|
|
writefx(fp, _arguments, _argptr, 0);
|
|
}
|
|
|
|
/***********************************
|
|
* Kept for backward compatibility. Use $(D_PARAM writefln) instead.
|
|
*/
|
|
void fwritefln(FILE* fp, ...)
|
|
{
|
|
writefx(fp, _arguments, _argptr, 1);
|
|
}
|
|
|
|
/**********************************
|
|
* Read line from stream $(D_PARAM fp).
|
|
* Returns:
|
|
* $(D_PARAM null) for end of file,
|
|
* $(D_PARAM char[]) for line read from $(D_PARAM fp), including terminating character
|
|
* Params:
|
|
* $(D_PARAM fp) = input stream
|
|
* $(D_PARAM terminator) = line terminator, '\n' by default
|
|
* Throws:
|
|
* $(D_PARAM StdioException) on error
|
|
* Example:
|
|
* Reads $(D_PARAM stdin) and writes it to $(D_PARAM stdout).
|
|
---
|
|
import std.stdio;
|
|
|
|
int main()
|
|
{
|
|
char[] buf;
|
|
while ((buf = readln()) != null)
|
|
write(buf);
|
|
return 0;
|
|
}
|
|
---
|
|
*/
|
|
string readln(FILE* fp = stdin, dchar terminator = '\n')
|
|
{
|
|
char[] buf;
|
|
readln(fp, buf, terminator);
|
|
return assumeUnique(buf);
|
|
}
|
|
|
|
/**********************************
|
|
* Read line from stream $(D_PARAM fp) and write it to $(D_PARAM
|
|
* buf[]), including terminating character.
|
|
*
|
|
* This is often faster than $(D_PARAM readln(FILE*)) because the buffer
|
|
* is reused each call. Note that reusing the buffer means that
|
|
* the previous contents of it need to be copied if needed.
|
|
* Params:
|
|
* $(D_PARAM fp) = input stream
|
|
* $(D_PARAM buf) = buffer used to store the resulting line data. buf
|
|
* is resized as necessary.
|
|
* Returns:
|
|
* 0 for end of file, otherwise
|
|
* number of characters read
|
|
* Throws:
|
|
* $(D_PARAM StdioException) on error
|
|
* Example:
|
|
* Reads $(D_PARAM stdin) and writes it to $(D_PARAM stdout).
|
|
---
|
|
import std.stdio;
|
|
|
|
int main()
|
|
{
|
|
char[] buf;
|
|
while (readln(stdin, buf))
|
|
write(buf);
|
|
return 0;
|
|
}
|
|
---
|
|
This method is more efficient than the one in the previous example
|
|
because $(D_PARAM readln(stdin, buf)) reuses (if possible) memory
|
|
allocated by $(D_PARAM buf), whereas $(D_PARAM buf = readln()) makes a
|
|
new memory allocation with every line.
|
|
*/
|
|
size_t readln(FILE* fp, inout char[] buf, dchar terminator = '\n')
|
|
{
|
|
version (DIGITAL_MARS_STDIO)
|
|
{
|
|
FLOCK(fp);
|
|
scope(exit) FUNLOCK(fp);
|
|
|
|
if (__fhnd_info[fp._file] & FHND_WCHAR)
|
|
{ /* Stream is in wide characters.
|
|
* Read them and convert to chars.
|
|
*/
|
|
static assert(wchar_t.sizeof == 2);
|
|
buf.length = 0;
|
|
int c2;
|
|
for (int c; (c = FGETWC(fp)) != -1; )
|
|
{
|
|
if ((c & ~0x7F) == 0)
|
|
{ buf ~= c;
|
|
if (c == terminator)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (c >= 0xD800 && c <= 0xDBFF)
|
|
{
|
|
if ((c2 = FGETWC(fp)) != -1 ||
|
|
c2 < 0xDC00 && c2 > 0xDFFF)
|
|
{
|
|
StdioException("unpaired UTF-16 surrogate");
|
|
}
|
|
c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00);
|
|
}
|
|
std.utf.encode(buf, c);
|
|
}
|
|
}
|
|
if (ferror(fp))
|
|
StdioException();
|
|
return buf.length;
|
|
}
|
|
|
|
auto sz = std.gc.capacity(buf.ptr);
|
|
//auto sz = buf.length;
|
|
buf = buf.ptr[0 .. sz];
|
|
if (fp._flag & _IONBF)
|
|
{
|
|
/* Use this for unbuffered I/O, when running
|
|
* across buffer boundaries, or for any but the common
|
|
* cases.
|
|
*/
|
|
L1:
|
|
char *p;
|
|
|
|
if (sz)
|
|
{
|
|
p = buf.ptr;
|
|
}
|
|
else
|
|
{
|
|
sz = 64;
|
|
p = cast(char*) std.gc.malloc(sz);
|
|
std.gc.hasNoPointers(p);
|
|
buf = p[0 .. sz];
|
|
}
|
|
size_t i = 0;
|
|
for (int c; (c = FGETC(fp)) != -1; )
|
|
{
|
|
if ((p[i] = c) != terminator)
|
|
{
|
|
i++;
|
|
if (i < sz)
|
|
continue;
|
|
buf = p[0 .. i] ~ readln(fp);
|
|
return buf.length;
|
|
}
|
|
else
|
|
{
|
|
buf = p[0 .. i + 1];
|
|
return i + 1;
|
|
}
|
|
}
|
|
if (ferror(fp))
|
|
StdioException();
|
|
buf = p[0 .. i];
|
|
return i;
|
|
}
|
|
else
|
|
{
|
|
int u = fp._cnt;
|
|
char* p = fp._ptr;
|
|
int i;
|
|
if (fp._flag & _IOTRAN)
|
|
{ /* Translated mode ignores \r and treats ^Z as end-of-file
|
|
*/
|
|
char c;
|
|
while (1)
|
|
{
|
|
if (i == u) // if end of buffer
|
|
goto L1; // give up
|
|
c = p[i];
|
|
i++;
|
|
if (c != '\r')
|
|
{
|
|
if (c == terminator)
|
|
break;
|
|
if (c != 0x1A)
|
|
continue;
|
|
goto L1;
|
|
}
|
|
else
|
|
{ if (i != u && p[i] == terminator)
|
|
break;
|
|
goto L1;
|
|
}
|
|
}
|
|
if (i > sz)
|
|
{
|
|
buf = cast(char[])std.gc.malloc(i);
|
|
std.gc.hasNoPointers(buf.ptr);
|
|
}
|
|
if (i - 1)
|
|
memcpy(buf.ptr, p, i - 1);
|
|
buf[i - 1] = terminator;
|
|
buf = buf[0 .. i];
|
|
if (terminator == '\n' && c == '\r')
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
while (1)
|
|
{
|
|
if (i == u) // if end of buffer
|
|
goto L1; // give up
|
|
auto c = p[i];
|
|
i++;
|
|
if (c == terminator)
|
|
break;
|
|
}
|
|
if (i > sz)
|
|
{
|
|
buf = cast(char[])std.gc.malloc(i);
|
|
std.gc.hasNoPointers(buf.ptr);
|
|
}
|
|
memcpy(buf.ptr, p, i);
|
|
buf = buf[0 .. i];
|
|
}
|
|
fp._cnt -= i;
|
|
fp._ptr += i;
|
|
return i;
|
|
}
|
|
}
|
|
else version (GCC_IO)
|
|
{
|
|
if (fwide(fp, 0) > 0)
|
|
{ /* Stream is in wide characters.
|
|
* Read them and convert to chars.
|
|
*/
|
|
FLOCK(fp);
|
|
scope(exit) FUNLOCK(fp);
|
|
version (Windows)
|
|
{
|
|
buf.length = 0;
|
|
int c2;
|
|
for (int c; (c = FGETWC(fp)) != -1; )
|
|
{
|
|
if ((c & ~0x7F) == 0)
|
|
{ buf ~= c;
|
|
if (c == terminator)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (c >= 0xD800 && c <= 0xDBFF)
|
|
{
|
|
if ((c2 = FGETWC(fp)) != -1 ||
|
|
c2 < 0xDC00 && c2 > 0xDFFF)
|
|
{
|
|
StdioException("unpaired UTF-16 surrogate");
|
|
}
|
|
c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00);
|
|
}
|
|
std.utf.encode(buf, c);
|
|
}
|
|
}
|
|
if (ferror(fp))
|
|
StdioException();
|
|
return buf.length;
|
|
}
|
|
else version (linux)
|
|
{
|
|
buf.length = 0;
|
|
for (int c; (c = FGETWC(fp)) != -1; )
|
|
{
|
|
if ((c & ~0x7F) == 0)
|
|
buf ~= c;
|
|
else
|
|
std.utf.encode(buf, cast(dchar)c);
|
|
if (c == terminator)
|
|
break;
|
|
}
|
|
if (ferror(fp))
|
|
StdioException();
|
|
return buf.length;
|
|
}
|
|
else
|
|
{
|
|
static assert(0);
|
|
}
|
|
}
|
|
|
|
char *lineptr = null;
|
|
size_t n = 0;
|
|
auto s = getdelim(&lineptr, &n, terminator, fp);
|
|
scope(exit) free(lineptr);
|
|
if (s < 0)
|
|
{
|
|
if (ferror(fp))
|
|
StdioException();
|
|
buf.length = 0; // end of file
|
|
return 0;
|
|
}
|
|
buf = buf.ptr[0 .. std.gc.capacity(buf.ptr)];
|
|
if (s <= buf.length)
|
|
{
|
|
buf.length = s;
|
|
buf[] = lineptr[0 .. s];
|
|
}
|
|
else
|
|
{
|
|
buf = lineptr[0 .. s].dup;
|
|
}
|
|
return s;
|
|
}
|
|
else
|
|
{
|
|
static assert(0);
|
|
}
|
|
}
|
|
|
|
/** ditto */
|
|
size_t readln(inout char[] buf, dchar terminator = '\n')
|
|
{
|
|
return readln(stdin, buf, terminator);
|
|
}
|
|
|
|
/** ditto */
|
|
// TODO: optimize this
|
|
size_t readln(FILE* f, inout wchar[] buf, dchar terminator = '\n')
|
|
{
|
|
string s = readln(f, terminator);
|
|
if (!s.length) return 0;
|
|
buf.length = 0;
|
|
foreach (wchar c; s)
|
|
{
|
|
buf ~= c;
|
|
}
|
|
return buf.length;
|
|
}
|
|
|
|
/** ditto */
|
|
// TODO: fold this together with wchar
|
|
size_t readln(FILE* f, inout dchar[] buf, dchar terminator = '\n')
|
|
{
|
|
string s = readln(f, terminator);
|
|
if (!s.length) return 0;
|
|
buf.length = 0;
|
|
foreach (dchar c; s)
|
|
{
|
|
buf ~= c;
|
|
}
|
|
return buf.length;
|
|
}
|
|
|
|
/***********************************
|
|
* Convenience function that forwards to $(D_PARAM std.c.stdio.fopen)
|
|
* with appropriately-constructed C-style strings.
|
|
*/
|
|
FILE* fopen(string name, string mode = "r")
|
|
{
|
|
return std.c.stdio.fopen(toStringz(name), toStringz(mode));
|
|
}
|
|
|
|
extern(C) FILE* popen(const char*, const char*);
|
|
|
|
/***********************************
|
|
* Convenience function that forwards to $(D_PARAM std.c.stdio.popen)
|
|
* with appropriately-constructed C-style strings.
|
|
*/
|
|
FILE* popen(string name, string mode)
|
|
{
|
|
return popen(toStringz(name), toStringz(mode));
|
|
}
|
|
|
|
/*
|
|
* Implements the static Writer interface for a FILE*. Instantiate it
|
|
* with the character type, e.g. PrivateFileWriter!(char),
|
|
* PrivateFileWriter!(wchar), or PrivateFileWriter!(dchar). Regardless of
|
|
* instantiation, PrivateFileWriter supports all character widths; it only is
|
|
* the most efficient at accepting the character type it was
|
|
* instantiated with.
|
|
*
|
|
* */
|
|
private struct PrivateFileWriter(Char)
|
|
{
|
|
alias Char NativeChar;
|
|
FILE* backend;
|
|
int orientation;
|
|
void write(C)(in C[] s)
|
|
{
|
|
if (!orientation) orientation = fwide(backend, 0);
|
|
if (orientation <= 0 && C.sizeof == 1)
|
|
{
|
|
// lucky case: narrow chars on narrow stream
|
|
if (std.c.stdio.fwrite(s.ptr, C.sizeof, s.length, backend)
|
|
!= s.length)
|
|
{
|
|
StdioException();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// put each character in turn
|
|
foreach (C c; s)
|
|
{
|
|
putchar(c);
|
|
}
|
|
}
|
|
}
|
|
void putchar(C)(in C c)
|
|
{
|
|
if (!orientation) orientation = fwide(backend, 0);
|
|
static if (c.sizeof == 1)
|
|
{
|
|
// simple char
|
|
if (orientation <= 0) FPUTC(c, backend);
|
|
else FPUTWC(c, backend);
|
|
}
|
|
else static if (c.sizeof == 2)
|
|
{
|
|
if (orientation <= 0)
|
|
{
|
|
if (c <= 0x7F)
|
|
{
|
|
FPUTC(c, backend);
|
|
}
|
|
else
|
|
{
|
|
char[4] buf;
|
|
auto b = std.utf.toUTF8(buf, c);
|
|
foreach (i ; 0 .. b.length)
|
|
FPUTC(b[i], backend);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FPUTWC(c, backend);
|
|
}
|
|
}
|
|
else // 32-bit characters
|
|
{
|
|
if (orientation <= 0)
|
|
{
|
|
if (c <= 0x7F)
|
|
{
|
|
FPUTC(c, backend);
|
|
}
|
|
else
|
|
{
|
|
char[4] buf;
|
|
auto b = std.utf.toUTF8(buf, c);
|
|
foreach (i ; 0 .. b.length)
|
|
FPUTC(b[i], backend);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
version (Windows)
|
|
{
|
|
assert(isValidDchar(c));
|
|
if (c <= 0xFFFF)
|
|
{
|
|
FPUTWC(c, backend);
|
|
}
|
|
else
|
|
{
|
|
FPUTWC(cast(wchar) ((((c - 0x10000) >> 10) & 0x3FF)
|
|
+ 0xD800), backend);
|
|
FPUTWC(cast(wchar) (((c - 0x10000) & 0x3FF) + 0xDC00),
|
|
backend);
|
|
}
|
|
}
|
|
else version (linux)
|
|
{
|
|
FPUTWC(c, backend);
|
|
}
|
|
else
|
|
{
|
|
static assert(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Iterates through the lines of a file by using $(D_PARAM foreach).
|
|
*
|
|
* Example:
|
|
*
|
|
---------
|
|
void main()
|
|
{
|
|
foreach (string line; lines(stdin))
|
|
{
|
|
... use line ...
|
|
}
|
|
}
|
|
---------
|
|
The line terminator ('\n' by default) is part of the string read (it
|
|
could be missing in the last line of the file). Several types are
|
|
supported for $(D_PARAM line), and the behavior of $(D_PARAM lines)
|
|
changes accordingly:
|
|
|
|
$(OL $(LI If $(D_PARAM line) has type $(D_PARAM string), $(D_PARAM
|
|
wstring), or $(D_PARAM dstring), a new string of the respective type
|
|
is allocated every read.) $(LI If $(D_PARAM line) has type $(D_PARAM
|
|
char[]), $(D_PARAM wchar[]), $(D_PARAM dchar[]), the line's content
|
|
will be reused (overwritten) across reads.) $(LI If $(D_PARAM line)
|
|
has type $(D_PARAM invariant(ubyte)[]), the behavior is similar to
|
|
case (1), except that no UTF checking is attempted upon input.) $(LI
|
|
If $(D_PARAM line) has type $(D_PARAM ubyte[]), the behavior is
|
|
similar to case (2), except that no UTF checking is attempted upon
|
|
input.))
|
|
|
|
In all cases, a two-symbols versions is also accepted, in which case
|
|
the first symbol (of integral type, e.g. $(D_PARAM ulong) or $(D_PARAM
|
|
uint)) tracks the zero-based number of the current line.
|
|
|
|
Example:
|
|
----
|
|
foreach (ulong i, string line; lines(stdin))
|
|
{
|
|
... use line ...
|
|
}
|
|
----
|
|
|
|
In case of an I/O error, an $(D_PARAM StdioException) is thrown.
|
|
*/
|
|
|
|
struct lines
|
|
{
|
|
private FILE * f;
|
|
private dchar terminator = '\n';
|
|
private string fileName;
|
|
|
|
static lines opCall(FILE* f, dchar terminator = '\n')
|
|
{
|
|
lines result;
|
|
result.f = f;
|
|
result.terminator = terminator;
|
|
return result;
|
|
}
|
|
|
|
// Keep these commented lines for later, when Walter fixes the
|
|
// exception model.
|
|
|
|
// static lines opCall(string fName, dchar terminator = '\n')
|
|
// {
|
|
// auto f = enforce(fopen(fName),
|
|
// new StdioException("Cannot open file `"~fName~"' for reading"));
|
|
// auto result = lines(f, terminator);
|
|
// result.fileName = fName;
|
|
// return result;
|
|
// }
|
|
|
|
int opApply(D)(D dg)
|
|
{
|
|
// scope(exit) {
|
|
// if (fileName.length && fclose(f))
|
|
// StdioException("Could not close file `"~fileName~"'");
|
|
// }
|
|
alias ParameterTypeTuple!(dg) Parms;
|
|
static if (isSomeString!(Parms[$ - 1]))
|
|
{
|
|
static const bool duplicate = is(Parms[$ - 1] == string)
|
|
|| is(Parms[$ - 1] == wstring) || is(Parms[$ - 1] == dstring);
|
|
int result = 0;
|
|
static if (is(Parms[$ - 1] : const(char)[]))
|
|
alias char C;
|
|
else static if (is(Parms[$ - 1] : const(wchar)[]))
|
|
alias wchar C;
|
|
else static if (is(Parms[$ - 1] : const(dchar)[]))
|
|
alias dchar C;
|
|
C[] line;
|
|
static if (Parms.length == 2)
|
|
Parms[0] i = 0;
|
|
for (;;)
|
|
{
|
|
if (!readln(f, line, terminator)) break;
|
|
auto copy = to!(Parms[$ - 1])(line);
|
|
static if (Parms.length == 2)
|
|
{
|
|
result = dg(i, copy);
|
|
++i;
|
|
}
|
|
else
|
|
{
|
|
result = dg(copy);
|
|
}
|
|
if (result != 0) break;
|
|
}
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
// raw read
|
|
return opApplyRaw(dg);
|
|
}
|
|
}
|
|
// no UTF checking
|
|
int opApplyRaw(D)(D dg)
|
|
{
|
|
alias ParameterTypeTuple!(dg) Parms;
|
|
static const duplicate = is(typeof(Parms[$ - 1]) : invariant(ubyte)[]);
|
|
int result = 1;
|
|
int c = void;
|
|
FLOCK(f);
|
|
scope(exit) FUNLOCK(f);
|
|
ubyte[] buffer;
|
|
static if (Parms.length == 2)
|
|
Parms[0] line = 0;
|
|
while ((c = FGETC(f)) != -1)
|
|
{
|
|
buffer ~= to!(ubyte)(c);
|
|
if (c == terminator)
|
|
{
|
|
static if (duplicate)
|
|
auto arg = assumeUnique(buffer);
|
|
else
|
|
alias buffer arg;
|
|
// unlock the file while calling the delegate
|
|
FUNLOCK(f);
|
|
scope(exit) FLOCK(f);
|
|
static if (Parms.length == 1)
|
|
{
|
|
result = dg(arg);
|
|
}
|
|
else
|
|
{
|
|
result = dg(line, arg);
|
|
++line;
|
|
}
|
|
if (result) break;
|
|
static if (!duplicate)
|
|
buffer.length = 0;
|
|
}
|
|
}
|
|
// can only reach when FGETC returned -1
|
|
if (!feof(f)) throw new StdioException(ferror(f)); // error occured
|
|
return result;
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
string file = "dmd-build-test.deleteme.txt";
|
|
scope(exit) { std.file.remove(file); }
|
|
alias TypeTuple!(string, wstring, dstring,
|
|
char[], wchar[], dchar[])
|
|
TestedWith;
|
|
foreach (T; TestedWith) {
|
|
// test looping with an empty file
|
|
std.file.write(file, "");
|
|
auto f = fopen(file, "r");
|
|
foreach (T line; lines(f))
|
|
{
|
|
assert(false);
|
|
}
|
|
fclose(f) == 0 || assert(false);
|
|
|
|
// test looping with a file with three lines
|
|
std.file.write(file, "Line one\nline two\nline three\n");
|
|
f = fopen(file, "r");
|
|
uint i = 0;
|
|
foreach (T line; lines(f))
|
|
{
|
|
if (i == 0) assert(line == "Line one\n");
|
|
else if (i == 1) assert(line == "line two\n");
|
|
else if (i == 2) assert(line == "line three\n");
|
|
else assert(false);
|
|
++i;
|
|
}
|
|
fclose(f) == 0 || assert(false);
|
|
|
|
// test looping with a file with three lines, last without a newline
|
|
std.file.write(file, "Line one\nline two\nline three");
|
|
f = fopen(file, "r");
|
|
i = 0;
|
|
foreach (T line; lines(f))
|
|
{
|
|
if (i == 0) assert(line == "Line one\n");
|
|
else if (i == 1) assert(line == "line two\n");
|
|
else if (i == 2) assert(line == "line three");
|
|
else assert(false);
|
|
++i;
|
|
}
|
|
fclose(f) == 0 || assert(false);
|
|
}
|
|
|
|
// test with ubyte[] inputs
|
|
alias TypeTuple!(invariant(ubyte)[], ubyte[])
|
|
TestedWith2;
|
|
foreach (T; TestedWith2) {
|
|
// test looping with an empty file
|
|
std.file.write(file, "");
|
|
auto f = fopen(file, "r");
|
|
foreach (T line; lines(f))
|
|
{
|
|
assert(false);
|
|
}
|
|
fclose(f) == 0 || assert(false);
|
|
|
|
// test looping with a file with three lines
|
|
std.file.write(file, "Line one\nline two\nline three\n");
|
|
f = fopen(file, "r");
|
|
uint i = 0;
|
|
foreach (T line; lines(f))
|
|
{
|
|
if (i == 0) assert(cast(char[]) line == "Line one\n");
|
|
else if (i == 1) assert(cast(char[]) line == "line two\n",
|
|
T.stringof ~ " " ~ cast(char[]) line);
|
|
else if (i == 2) assert(cast(char[]) line == "line three\n");
|
|
else assert(false);
|
|
++i;
|
|
}
|
|
fclose(f) == 0 || assert(false);
|
|
|
|
// test looping with a file with three lines, last without a newline
|
|
std.file.write(file, "Line one\nline two\nline three");
|
|
f = fopen(file, "r");
|
|
i = 0;
|
|
foreach (T line; lines(f))
|
|
{
|
|
if (i == 0) assert(cast(char[]) line == "Line one\n");
|
|
else if (i == 1) assert(cast(char[]) line == "line two\n");
|
|
else if (i == 2) assert(cast(char[]) line == "line three");
|
|
else assert(false);
|
|
++i;
|
|
}
|
|
fclose(f) == 0 || assert(false);
|
|
|
|
}
|
|
|
|
foreach (T; TypeTuple!(ubyte[]))
|
|
{
|
|
// test looping with a file with three lines, last without a newline
|
|
// using a counter too this time
|
|
std.file.write(file, "Line one\nline two\nline three");
|
|
auto f = fopen(file, "r");
|
|
uint i = 0;
|
|
foreach (ulong j, T line; lines(f))
|
|
{
|
|
if (i == 0) assert(cast(char[]) line == "Line one\n");
|
|
else if (i == 1) assert(cast(char[]) line == "line two\n");
|
|
else if (i == 2) assert(cast(char[]) line == "line three");
|
|
else assert(false);
|
|
++i;
|
|
}
|
|
fclose(f) == 0 || assert(false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Iterates through a file a chunk at a time by using $(D_PARAM
|
|
foreach).
|
|
|
|
Example:
|
|
|
|
---------
|
|
void main()
|
|
{
|
|
foreach (ubyte[] buffer; chunks(stdin, 4096))
|
|
{
|
|
... use buffer ...
|
|
}
|
|
}
|
|
---------
|
|
|
|
The content of $(D_PARAM buffer) is reused across calls. In the
|
|
example above, $(D_PARAM buffer.length) is 4096 for all iterations,
|
|
except for the last one, in which case $(D_PARAM buffer.length) may
|
|
be less than 4096 (but always greater than zero).
|
|
|
|
In case of an I/O error, an $(D_PARAM StdioException) is thrown.
|
|
*/
|
|
|
|
struct chunks
|
|
{
|
|
private FILE* f;
|
|
private size_t size;
|
|
private string fileName;
|
|
|
|
static chunks opCall(FILE* f, size_t size)
|
|
{
|
|
assert(f && size);
|
|
chunks result;
|
|
result.f = f;
|
|
result.size = size;
|
|
return result;
|
|
}
|
|
|
|
// static chunks opCall(string fName, size_t size)
|
|
// {
|
|
// auto f = enforce(fopen(fName),
|
|
// new StdioException("Cannot open file `"~fName~"' for reading"));
|
|
// auto result = chunks(f, size);
|
|
// result.fileName = fName;
|
|
// return result;
|
|
// }
|
|
|
|
int opApply(int delegate(ref ubyte[]) dg)
|
|
{
|
|
// scope(exit) {
|
|
// if (fileName.length && fclose(f))
|
|
// StdioException("Could not close file `"~fileName~"'");
|
|
// }
|
|
const maxStackSize = 1024 * 16;
|
|
ubyte[] buffer = void;
|
|
if (size < maxStackSize)
|
|
buffer = (cast(ubyte*) alloca(size))[0 .. size];
|
|
else
|
|
buffer = new ubyte[size];
|
|
size_t r = void;
|
|
int result = 1;
|
|
while ((r = std.c.stdio.fread(buffer.ptr,
|
|
buffer[0].sizeof, size, f)) > 0)
|
|
{
|
|
assert(r <= size);
|
|
if (r != size)
|
|
{
|
|
// error occured
|
|
if (!feof(f)) throw new StdioException(ferror(f));
|
|
buffer.length = r;
|
|
}
|
|
if ((result = dg(buffer)) != 0) break;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
string file = "dmd-build-test.deleteme.txt";
|
|
scope(exit) { std.file.remove(file); }
|
|
// test looping with an empty file
|
|
std.file.write(file, "");
|
|
auto f = fopen(file, "r");
|
|
foreach (ubyte[] line; chunks(f, 4))
|
|
{
|
|
assert(false);
|
|
}
|
|
fclose(f) == 0 || assert(false);
|
|
|
|
// test looping with a file with three lines
|
|
std.file.write(file, "Line one\nline two\nline three\n");
|
|
f = fopen(file, "r");
|
|
uint i = 0;
|
|
foreach (ubyte[] line; chunks(f, 3))
|
|
{
|
|
if (i == 0) assert(cast(char[]) line == "Lin");
|
|
else if (i == 1) assert(cast(char[]) line == "e o");
|
|
else if (i == 2) assert(cast(char[]) line == "ne\n");
|
|
else break;
|
|
++i;
|
|
}
|
|
fclose(f) == 0 || assert(false);
|
|
}
|