Skip to content

Commit

Permalink
Issue 13568: Add CT-checked format string overloads to std.stdio
Browse files Browse the repository at this point in the history
* Add overloads for readf, writef, writefln.
* Separate fmt argument from args for writef[ln] to improve docs.
  • Loading branch information
ntrel committed Mar 20, 2017
1 parent 4a1733e commit 3aa8011
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 24 deletions.
15 changes: 7 additions & 8 deletions changelog/std-format-formattedWrite.dd
Expand Up @@ -6,14 +6,13 @@ specifiers to be matched against the argument types passed. Any mismatch or
orphaned specifiers/arguments will cause a compile-time error:

-------
import std.format;
import std.format, std.stdio;

void main() {
auto s = format!"%s is %s"("Pi", 3.14);
assert(s == "Pi is 3.14");
auto s = format!"%s is %s"("Pi", 3.14);
assert(s == "Pi is 3.14");
writefln!"%c is %s"('e', 1.61);

static assert(!__traits(compiles, {s = format!"%l"();})); // missing arg
static assert(!__traits(compiles, {s = format!""(404);})); // surplus arg
static assert(!__traits(compiles, {s = format!"%d"(4.03);})); // incompatible arg
}
static assert(!__traits(compiles, {s = format!"%l"();})); // missing arg
static assert(!__traits(compiles, {s = format!""(404);})); // surplus arg
static assert(!__traits(compiles, {s = format!"%d"(4.03);})); // incompatible arg
-------
111 changes: 95 additions & 16 deletions std/stdio.d
Expand Up @@ -1448,11 +1448,22 @@ Throws: $(D Exception) if the file is not opened.

/**
Writes its arguments in text format to the file, according to the
format in the first argument.
format string fmt.
Throws: $(D Exception) if the file is not opened.
$(D ErrnoException) on an error writing to the file.
*/
void writef(alias fmt, A...)(A args)
if (isSomeString!(typeof(fmt)))
{
import std.format : checkFormatException;

alias e = checkFormatException!(fmt, A);
static assert(!e, e.msg);
return this.writef(fmt, args);
}

/// ditto
void writef(Char, A...)(in Char[] fmt, A args)
{
import std.format : formattedWrite;
Expand All @@ -1462,11 +1473,22 @@ Throws: $(D Exception) if the file is not opened.

/**
Writes its arguments in text format to the file, according to the
format in the first argument, followed by a newline.
format string fmt, followed by a newline.
Throws: $(D Exception) if the file is not opened.
$(D ErrnoException) on an error writing to the file.
*/
void writefln(alias fmt, A...)(A args)
if (isSomeString!(typeof(fmt)))
{
import std.format : checkFormatException;

alias e = checkFormatException!(fmt, A);
static assert(!e, e.msg);
return this.writefln(fmt, args);
}

/// ditto
void writefln(Char, A...)(in Char[] fmt, A args)
{
import std.format : formattedWrite;
Expand Down Expand Up @@ -1764,7 +1786,7 @@ is recommended if you want to process a complete file.
}

/**
* Read data from the file according to the specified
* Reads data from the file according to the specified
* $(LINK2 std_format.html#_format-string, _format specifier) using
* $(REF formattedRead, std,_format).
* Example:
Expand All @@ -1777,7 +1799,7 @@ void main()
foreach (_; 0 .. 3)
{
int a;
f.readf(" %d", a);
f.readf!" %d"(a);
writeln(++a);
}
}
Expand All @@ -1790,6 +1812,17 @@ $(CONSOLE
4
)
*/
uint readf(alias format, Data...)(auto ref Data data)
if (isSomeString!(typeof(format)))
{
import std.format : checkFormatException;

alias e = checkFormatException!(format, Data);
static assert(!e, e.msg);
return this.readf(format, data);
}

/// ditto
uint readf(Data...)(in char[] format, auto ref Data data)
{
import std.format : formattedRead;
Expand All @@ -1809,7 +1842,7 @@ $(CONSOLE
scope(exit) std.file.remove(deleteme);
string s;
auto f = File(deleteme);
f.readf("%s\n", s);
f.readf!"%s\n"(s);
assert(s == "hello", "["~s~"]");
f.readf("%s\n", s);
assert(s == "world", "["~s~"]");
Expand Down Expand Up @@ -3668,11 +3701,12 @@ void writeln(T...)(T args)
Writes formatted data to standard output (without a trailing newline).
Params:
args = The first argument $(D args[0]) should be the format string, specifying
fmt = The format string, specifying
how to format the rest of the arguments. For a full description of the syntax
of the format string and how it controls the formatting of the rest of the
arguments, please refer to the documentation for $(REF formattedWrite,
std,format).
args = Items to write.
Note: In older versions of Phobos, it used to be possible to write:
Expand All @@ -3688,10 +3722,20 @@ stderr.writef("%s", "message");
------
*/
void writef(alias fmt, A...)(A args)
if (isSomeString!(typeof(fmt)))
{
import std.format : checkFormatException;

alias e = checkFormatException!(fmt, A);
static assert(!e, e.msg);
return .writef(fmt, args);
}

void writef(T...)(T args)
/// ditto
void writef(Char, A...)(in Char[] fmt, A args)
{
trustedStdout.writef(args);
trustedStdout.writef(fmt, args);
}

@system unittest
Expand All @@ -3704,24 +3748,35 @@ void writef(T...)(T args)
auto deleteme = testFilename();
auto f = File(deleteme, "w");
scope(exit) { std.file.remove(deleteme); }
f.writef("Hello, %s world number %s!", "nice", 42);
f.writef!"Hello, %s world number %s!"("nice", 42);
f.close();
assert(cast(char[]) std.file.read(deleteme) == "Hello, nice world number 42!");
// test write on stdout
auto saveStdout = stdout;
scope(exit) stdout = saveStdout;
stdout.open(deleteme, "w");
writef("Hello, %s world number %s!", "nice", 42);
writef!"Hello, %s world number %s!"("nice", 42);
stdout.close();
assert(cast(char[]) std.file.read(deleteme) == "Hello, nice world number 42!");
}

/***********************************
* Equivalent to $(D writef(args, '\n')).
* Equivalent to $(D writef(fmt, args, '\n')).
*/
void writefln(T...)(T args)
void writefln(alias fmt, A...)(A args)
if (isSomeString!(typeof(fmt)))
{
import std.format : checkFormatException;

alias e = checkFormatException!(fmt, A);
static assert(!e, e.msg);
return .writefln(fmt, args);
}

/// ditto
void writefln(Char, A...)(in Char[] fmt, A args)
{
trustedStdout.writefln(args);
trustedStdout.writefln(fmt, args);
}

@system unittest
Expand All @@ -3730,11 +3785,11 @@ void writefln(T...)(T args)

scope(failure) printf("Failed test at line %d\n", __LINE__);

// test writefln
// test File.writefln
auto deleteme = testFilename();
auto f = File(deleteme, "w");
scope(exit) { std.file.remove(deleteme); }
f.writefln("Hello, %s world number %s!", "nice", 42);
f.writefln!"Hello, %s world number %s!"("nice", 42);
f.close();
version (Windows)
assert(cast(char[]) std.file.read(deleteme) ==
Expand All @@ -3743,6 +3798,19 @@ void writefln(T...)(T args)
assert(cast(char[]) std.file.read(deleteme) ==
"Hello, nice world number 42!\n",
cast(char[]) std.file.read(deleteme));

// test writefln
auto saveStdout = stdout;
scope(exit) stdout = saveStdout;
stdout.open(deleteme, "w");
writefln!"Hello, %s world number %s!"("nice", 42);
stdout.close();
version (Windows)
assert(cast(char[]) std.file.read(deleteme) ==
"Hello, nice world number 42!\r\n");
else
assert(cast(char[]) std.file.read(deleteme) ==
"Hello, nice world number 42!\n");
}

/**
Expand All @@ -3758,7 +3826,7 @@ void main()
foreach (_; 0 .. 3)
{
int a;
readf(" %d", a);
readf!" %d"(a);
writeln(++a);
}
}
Expand All @@ -3770,6 +3838,17 @@ $(CONSOLE
4
)
*/
uint readf(alias format, A...)(auto ref A args)
if (isSomeString!(typeof(format)))
{
import std.format : checkFormatException;

alias e = checkFormatException!(format, A);
static assert(!e, e.msg);
return .readf(format, args);
}

/// ditto
uint readf(A...)(in char[] format, auto ref A args)
{
return stdin.readf(format, args);
Expand Down

0 comments on commit 3aa8011

Please sign in to comment.