phobos/std/format/read.d
2021-03-16 14:57:38 +01:00

556 lines
12 KiB
D

// Written in the D programming language.
/**
This is a submodule of $(MREF std, format).
It provides some helpful tools.
Copyright: Copyright The D Language Foundation 2000-2013.
License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com,
Andrei Alexandrescu), and Kenji Hara
Source: $(PHOBOSSRC std/format/read.d)
*/
module std.format.read;
import std.format.spec : FormatSpec;
import std.format.internal.read;
import std.traits : isSomeString;
/**
Reads characters from $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
`r`, converts them according to `fmt`, and writes them to `args`.
Params:
r = The range to read from.
fmt = The format of the data to read.
args = The drain of the data read.
Returns:
On success, the function returns the number of variables filled. This count
can match the expected number of readings or fewer, even zero, if a
matching failure happens.
Throws:
A `FormatException` if `S.length == 0` and `fmt` has format specifiers.
*/
uint formattedRead(alias fmt, R, S...)(auto ref R r, auto ref S args)
if (isSomeString!(typeof(fmt)))
{
import std.format : checkFormatException;
alias e = checkFormatException!(fmt, S);
static assert(!e, e.msg);
return .formattedRead(r, fmt, args);
}
/// ditto
uint formattedRead(R, Char, S...)(auto ref R r, const(Char)[] fmt, auto ref S args)
{
import std.format : enforceFmt;
import std.range.primitives : empty;
import std.traits : isPointer;
import std.typecons : isTuple;
auto spec = FormatSpec!Char(fmt);
static if (!S.length)
{
spec.readUpToNextSpec(r);
enforceFmt(spec.trailing.empty, "Trailing characters in formattedRead format string");
return 0;
}
else
{
enum hasPointer = isPointer!(typeof(args[0]));
// The function below accounts for '*' == fields meant to be
// read and skipped
void skipUnstoredFields()
{
for (;;)
{
spec.readUpToNextSpec(r);
if (spec.width != spec.DYNAMIC) break;
// must skip this field
skipData(r, spec);
}
}
skipUnstoredFields();
if (r.empty)
{
// Input is empty, nothing to read
return 0;
}
static if (hasPointer)
alias A = typeof(*args[0]);
else
alias A = typeof(args[0]);
static if (isTuple!A)
{
foreach (i, T; A.Types)
{
static if (hasPointer)
(*args[0])[i] = unformatValue!(T)(r, spec);
else
args[0][i] = unformatValue!(T)(r, spec);
skipUnstoredFields();
}
}
else
{
static if (hasPointer)
*args[0] = unformatValue!(A)(r, spec);
else
args[0] = unformatValue!(A)(r, spec);
}
return 1 + formattedRead(r, spec.trailing, args[1 .. $]);
}
}
/// The format string can be checked at compile-time (see $(REF format, std, format) for details):
@safe pure unittest
{
string s = "hello!124:34.5";
string a;
int b;
double c;
s.formattedRead!"%s!%s:%s"(a, b, c);
assert(a == "hello" && b == 124 && c == 34.5);
}
@safe unittest
{
import std.math : isClose, isNaN;
import std.range.primitives : empty;
string s = " 1.2 3.4 ";
double x, y, z;
assert(formattedRead(s, " %s %s %s ", x, y, z) == 2);
assert(s.empty);
assert(isClose(x, 1.2));
assert(isClose(y, 3.4));
assert(isNaN(z));
}
// for backwards compatibility
@system pure unittest
{
string s = "hello!124:34.5";
string a;
int b;
double c;
formattedRead(s, "%s!%s:%s", &a, &b, &c);
assert(a == "hello" && b == 124 && c == 34.5);
// mix pointers and auto-ref
s = "world!200:42.25";
formattedRead(s, "%s!%s:%s", a, &b, &c);
assert(a == "world" && b == 200 && c == 42.25);
s = "world1!201:42.5";
formattedRead(s, "%s!%s:%s", &a, &b, c);
assert(a == "world1" && b == 201 && c == 42.5);
s = "world2!202:42.75";
formattedRead(s, "%s!%s:%s", a, b, &c);
assert(a == "world2" && b == 202 && c == 42.75);
}
// for backwards compatibility
@system pure unittest
{
import std.math : isClose, isNaN;
import std.range.primitives : empty;
string s = " 1.2 3.4 ";
double x, y, z;
assert(formattedRead(s, " %s %s %s ", &x, &y, &z) == 2);
assert(s.empty);
assert(isClose(x, 1.2));
assert(isClose(y, 3.4));
assert(isNaN(z));
}
@system pure unittest
{
string line;
bool f1;
line = "true";
formattedRead(line, "%s", &f1);
assert(f1);
line = "TrUE";
formattedRead(line, "%s", &f1);
assert(f1);
line = "false";
formattedRead(line, "%s", &f1);
assert(!f1);
line = "fALsE";
formattedRead(line, "%s", &f1);
assert(!f1);
line = "1";
formattedRead(line, "%d", &f1);
assert(f1);
line = "-1";
formattedRead(line, "%d", &f1);
assert(f1);
line = "0";
formattedRead(line, "%d", &f1);
assert(!f1);
line = "-0";
formattedRead(line, "%d", &f1);
assert(!f1);
}
@system pure unittest
{
union B
{
char[int.sizeof] untyped;
int typed;
}
B b;
b.typed = 5;
char[] input = b.untyped[];
int witness;
formattedRead(input, "%r", &witness);
assert(witness == b.typed);
}
@system pure unittest
{
union A
{
char[float.sizeof] untyped;
float typed;
}
A a;
a.typed = 5.5;
char[] input = a.untyped[];
float witness;
formattedRead(input, "%r", &witness);
assert(witness == a.typed);
}
@system pure unittest
{
import std.typecons : Tuple;
char[] line = "1 2".dup;
int a, b;
formattedRead(line, "%s %s", &a, &b);
assert(a == 1 && b == 2);
line = "10 2 3".dup;
formattedRead(line, "%d ", &a);
assert(a == 10);
assert(line == "2 3");
Tuple!(int, float) t;
line = "1 2.125".dup;
formattedRead(line, "%d %g", &t);
assert(t[0] == 1 && t[1] == 2.125);
line = "1 7643 2.125".dup;
formattedRead(line, "%s %*u %s", &t);
assert(t[0] == 1 && t[1] == 2.125);
}
@system pure unittest
{
string line;
char c1, c2;
line = "abc";
formattedRead(line, "%s%c", &c1, &c2);
assert(c1 == 'a' && c2 == 'b');
assert(line == "c");
}
@system pure unittest
{
string line;
line = "[1,2,3]";
int[] s1;
formattedRead(line, "%s", &s1);
assert(s1 == [1,2,3]);
}
@system pure unittest
{
string line;
line = "[1,2,3]";
int[] s1;
formattedRead(line, "[%(%s,%)]", &s1);
assert(s1 == [1,2,3]);
line = `["hello", "world"]`;
string[] s2;
formattedRead(line, "[%(%s, %)]", &s2);
assert(s2 == ["hello", "world"]);
line = "123 456";
int[] s3;
formattedRead(line, "%(%s %)", &s3);
assert(s3 == [123, 456]);
line = "h,e,l,l,o; w,o,r,l,d";
string[] s4;
formattedRead(line, "%(%(%c,%); %)", &s4);
assert(s4 == ["hello", "world"]);
}
@system pure unittest
{
import std.exception : assertThrown;
string line;
int[4] sa1;
line = `[1,2,3,4]`;
formattedRead(line, "%s", &sa1);
assert(sa1 == [1,2,3,4]);
int[4] sa2;
line = `[1,2,3]`;
assertThrown(formattedRead(line, "%s", &sa2));
int[4] sa3;
line = `[1,2,3,4,5]`;
assertThrown(formattedRead(line, "%s", &sa3));
}
@system pure unittest
{
import std.exception : assertThrown;
import std.format : FormatException;
string input;
int[4] sa1;
input = `[1,2,3,4]`;
formattedRead(input, "[%(%s,%)]", &sa1);
assert(sa1 == [1,2,3,4]);
int[4] sa2;
input = `[1,2,3]`;
assertThrown!FormatException(formattedRead(input, "[%(%s,%)]", &sa2));
}
@system pure unittest
{
string line;
string s1, s2;
line = "hello, world";
formattedRead(line, "%s", &s1);
assert(s1 == "hello, world", s1);
line = "hello, world;yah";
formattedRead(line, "%s;%s", &s1, &s2);
assert(s1 == "hello, world", s1);
assert(s2 == "yah", s2);
line = `['h','e','l','l','o']`;
string s3;
formattedRead(line, "[%(%s,%)]", &s3);
assert(s3 == "hello");
line = `"hello"`;
string s4;
formattedRead(line, "\"%(%c%)\"", &s4);
assert(s4 == "hello");
}
@system pure unittest
{
string line;
string[int] aa1;
line = `[1:"hello", 2:"world"]`;
formattedRead(line, "%s", &aa1);
assert(aa1 == [1:"hello", 2:"world"]);
int[string] aa2;
line = `{"hello"=1; "world"=2}`;
formattedRead(line, "{%(%s=%s; %)}", &aa2);
assert(aa2 == ["hello":1, "world":2]);
int[string] aa3;
line = `{[hello=1]; [world=2]}`;
formattedRead(line, "{%([%(%c%)=%s]%|; %)}", &aa3);
assert(aa3 == ["hello":1, "world":2]);
}
// test rvalue using
@system pure unittest
{
string[int] aa1;
formattedRead!("%s")(`[1:"hello", 2:"world"]`, aa1);
assert(aa1 == [1:"hello", 2:"world"]);
int[string] aa2;
formattedRead(`{"hello"=1; "world"=2}`, "{%(%s=%s; %)}", aa2);
assert(aa2 == ["hello":1, "world":2]);
}
/**
* Reads a value from the given _input range according to spec
* and returns it as type `T`.
*
* Params:
* T = the type to return
* input = the _input range to read from
* spec = the `FormatSpec` to use when reading from `input`
* Returns:
* A value from `input` of type `T`
* Throws:
* A `FormatException` if `spec` cannot read a type `T`
* See_Also:
* $(REF parse, std, conv) and $(REF to, std, conv)
*/
T unformatValue(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec)
{
return unformatValueImpl!T(input, spec);
}
/// Booleans
@safe pure unittest
{
import std.format.spec : singleSpec;
auto str = "false";
auto spec = singleSpec("%s");
assert(unformatValue!bool(str, spec) == false);
str = "1";
spec = singleSpec("%d");
assert(unformatValue!bool(str, spec));
}
/// Null values
@safe pure unittest
{
import std.format.spec : singleSpec;
auto str = "null";
auto spec = singleSpec("%s");
assert(str.unformatValue!(typeof(null))(spec) == null);
}
/// Integrals
@safe pure unittest
{
import std.format.spec : singleSpec;
auto str = "123";
auto spec = singleSpec("%s");
assert(str.unformatValue!int(spec) == 123);
str = "ABC";
spec = singleSpec("%X");
assert(str.unformatValue!int(spec) == 2748);
str = "11610";
spec = singleSpec("%o");
assert(str.unformatValue!int(spec) == 5000);
}
/// Floating point numbers
@safe pure unittest
{
import std.format.spec : singleSpec;
import std.math : isClose;
auto str = "123.456";
auto spec = singleSpec("%s");
assert(str.unformatValue!double(spec).isClose(123.456));
}
/// Character input ranges
@safe pure unittest
{
import std.format.spec : singleSpec;
auto str = "aaa";
auto spec = singleSpec("%s");
assert(str.unformatValue!char(spec) == 'a');
// Using a numerical format spec reads a Unicode value from a string
str = "65";
spec = singleSpec("%d");
assert(str.unformatValue!char(spec) == 'A');
str = "41";
spec = singleSpec("%x");
assert(str.unformatValue!char(spec) == 'A');
str = "10003";
spec = singleSpec("%d");
assert(str.unformatValue!dchar(spec) == '✓');
}
/// Arrays and static arrays
@safe pure unittest
{
import std.format.spec : singleSpec;
string str = "aaa";
auto spec = singleSpec("%s");
assert(str.unformatValue!(dchar[])(spec) == "aaa"d);
str = "aaa";
spec = singleSpec("%s");
dchar[3] ret = ['a', 'a', 'a'];
assert(str.unformatValue!(dchar[3])(spec) == ret);
str = "[1, 2, 3, 4]";
spec = singleSpec("%s");
assert(str.unformatValue!(int[])(spec) == [1, 2, 3, 4]);
str = "[1, 2, 3, 4]";
spec = singleSpec("%s");
int[4] ret2 = [1, 2, 3, 4];
assert(str.unformatValue!(int[4])(spec) == ret2);
}
/// Associative arrays
@safe pure unittest
{
import std.format.spec : singleSpec;
auto str = `["one": 1, "two": 2]`;
auto spec = singleSpec("%s");
assert(str.unformatValue!(int[string])(spec) == ["one": 1, "two": 2]);
}
// https://issues.dlang.org/show_bug.cgi?id=7241
@safe pure unittest
{
string input = "a";
auto spec = FormatSpec!char("%s");
spec.readUpToNextSpec(input);
auto result = unformatValue!(dchar[1])(input, spec);
assert(result[0] == 'a');
}