stdio: add readfln and File.readfln

Compared to readf, these functions provide a less error-prone way to
read a single line of formatted input.

Fixes #10370
This commit is contained in:
Paul Backus 2025-03-02 10:36:32 -05:00 committed by The Dlang Bot
parent 0d724aa0c8
commit 4a91ed7033
2 changed files with 150 additions and 0 deletions

6
changelog/readfln.dd Normal file
View file

@ -0,0 +1,6 @@
Added `readfln` and `File.readfln` to `std.stdio`
These functions read a single line of input and parse it using a format string.
Unlike `readf`, they will not accidentally read multiple lines if the user
forgets to include a line terminator in the format string—a common mistake for
beginners.

View file

@ -18,6 +18,7 @@ $(TR $(TD Reading) $(TD
$(MYREF chunks) $(MYREF chunks)
$(MYREF lines) $(MYREF lines)
$(MYREF readf) $(MYREF readf)
$(MYREF readfln)
$(MYREF readln) $(MYREF readln)
)) ))
$(TR $(TD Writing) $(TD $(TR $(TD Writing) $(TD
@ -2094,6 +2095,85 @@ $(CONSOLE
"Unexpected '\\n' when converting from type LockingTextReader to type int"); "Unexpected '\\n' when converting from type LockingTextReader to type int");
} }
/**
Reads a line from the file and parses it using $(REF formattedRead, std,format,read).
Params:
format = The $(MREF_ALTTEXT format string, std,format). When passed as a
compile-time argument, the string will be statically checked against the
argument types passed.
data = Items to be read.
Returns: Same as `formattedRead`: the number of variables filled. If the
input ends early, this number will be less that the number of variables
provided.
Example:
---
// sum_rows.d
void main()
{
import std.stdio;
auto f = File("input");
int a, b, c;
while (f.readfln("%d %d %d", a, b, c) == 3)
{
writeln(a + b + c);
}
}
---
$(CONSOLE
% cat << EOF > input
1 2 3
4 5 6
7 8 9
EOF
% rdmd sum_rows.d
6
15
24
)
*/
uint readfln(alias format, Data...)(auto ref Data data)
if (isSomeString!(typeof(format)))
{
import std.format : checkFormatException;
alias e = checkFormatException!(format, Data);
static assert(!e, e);
return this.readfln(format, data);
}
/// ditto
uint readfln(Data...)(scope const(char)[] format, auto ref Data data)
{
import std.format.read : formattedRead;
import std.string : stripRight;
string line = this.readln.stripRight("\r\n");
return formattedRead(line, format, data);
}
@system unittest
{
static import std.file;
auto deleteme = testFilename();
std.file.write(deleteme, "hello\nworld\ntrue\nfalse\n");
scope(exit) std.file.remove(deleteme);
string s;
auto f = File(deleteme);
f.readfln!"%s"(s);
assert(s == "hello", "["~s~"]");
f.readfln("%s", s);
assert(s == "world", "["~s~"]");
bool b1, b2;
f.readfln("%s", b1);
f.readfln("%s", b2);
assert(b1 == true && b2 == false);
}
/** /**
Returns a temporary file by calling $(CSTDIO tmpfile). Returns a temporary file by calling $(CSTDIO tmpfile).
Note that the created file has no $(LREF name).*/ Note that the created file has no $(LREF name).*/
@ -4489,6 +4569,70 @@ if (isSomeChar!C && is(Unqual!C == C) && !is(C == enum) &&
} }
} }
/**
Reads a line from `stdin` and parses it using $(REF formattedRead, std,format,read).
Params:
format = The $(MREF_ALTTEXT format string, std,format). When passed as a
compile-time argument, the string will be statically checked against the
argument types passed.
data = Items to be read.
Returns: Same as `formattedRead`: the number of variables filled. If the
input ends early, this number will be less that the number of variables
provided.
Example:
---
// sum_rows.d
void main()
{
import std.stdio;
int a, b, c;
while (readfln("%d %d %d", a, b, c) == 3)
{
writeln(a + b + c);
}
}
---
$(CONSOLE
% cat << EOF > input
1 2 3
4 5 6
7 8 9
EOF
% rdmd sum_rows.d < input
6
15
24
)
*/
uint readfln(alias format, Data...)(auto ref Data data)
{
import std.format : checkFormatException;
alias e = checkFormatException!(format, Data);
static assert(!e, e);
return .readfln(format, data);
}
/// ditto
uint readfln(Data...)(scope const(char)[] format, auto ref Data data)
{
return stdin.readfln(format, data);
}
@system unittest
{
float f;
string s;
char c;
int n;
if (false) readfln("%f %s %c %d", f, s, c, n);
if (false) readfln!"%f %s %c %d"(f, s, c, n);
}
/* /*
* Convenience function that forwards to `core.sys.posix.stdio.fopen` * Convenience function that forwards to `core.sys.posix.stdio.fopen`
* (to `_wfopen` on Windows) * (to `_wfopen` on Windows)