diff options
Diffstat (limited to 'libphobos/src/std/format/read.d')
-rw-r--r-- | libphobos/src/std/format/read.d | 721 |
1 files changed, 721 insertions, 0 deletions
diff --git a/libphobos/src/std/format/read.d b/libphobos/src/std/format/read.d new file mode 100644 index 00000000000..0de88183fb2 --- /dev/null +++ b/libphobos/src/std/format/read.d @@ -0,0 +1,721 @@ +// Written in the D programming language. + +/** +This is a submodule of $(MREF std, format). + +It provides two functions for reading formatted input: $(LREF +unformatValue) and $(LREF formattedRead). The former reads a single +value. The latter reads several values at once and matches the +characters found between format specifiers. + +Parameters are ignored, except for the ones consisting of a single +$(B '*'). See $(LREF formattedRead) for more information. + +A space outside of a format specifier has a special meaning: it +matches any sequence of whitespace characters, not just a single +space. + +The following combinations of format characters and types are +available: + +$(BOOKTABLE , +$(TR $(TH) $(TH s) $(TH c) $(TH d, u, b, o, x, X) $(TH e, E, f, g, G) $(TH r) $(TH compound)) +$(TR $(TD `bool`) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH))) +$(TR $(TD `null`) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH))) +$(TR $(TD $(I integer)) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH))) +$(TR $(TD $(I floating point)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes) $(TD $(MDASH))) +$(TR $(TD $(I character)) $(TD yes) $(TD yes) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH))) +$(TR $(TD $(I string)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes)) +$(TR $(TD $(I array)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes)) +$(TR $(TD $(I associative array)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes)) +) + +Below are highlighted examples on how these combinations are used +with $(LREF unformatValue), however, they apply for $(LREF +formattedRead) also + +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; + +/// Booleans +@safe pure unittest +{ + import std.format.spec : singleSpec; + + auto str = "false"; + auto spec = singleSpec("%s"); + assert(str.unformatValue!bool(spec) == false); + + str = "1"; + spec = singleSpec("%d"); + assert(str.unformatValue!bool(spec) == true); +} + +/// 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; + + // signed decimal values + auto str = "123"; + auto spec = singleSpec("%s"); + assert(str.unformatValue!int(spec) == 123); + + // hexadecimal values + str = "ABC"; + spec = singleSpec("%X"); + assert(str.unformatValue!int(spec) == 2748); + + // octal values + str = "11610"; + spec = singleSpec("%o"); + assert(str.unformatValue!int(spec) == 5000); + + // raw read, depends on endianess + str = "\x75\x01"; + spec = singleSpec("%r"); + auto result = str.unformatValue!short(spec); + assert(result == 373 /* little endian */ || result == 29953 /* big endian */ ); +} + +/// Floating point numbers +@safe pure unittest +{ + import std.format.spec : singleSpec; + import std.math.operations : isClose; + + // natural notation + auto str = "123.456"; + auto spec = singleSpec("%s"); + assert(str.unformatValue!double(spec).isClose(123.456)); + + // scientific notation + str = "1e17"; + spec = singleSpec("%e"); + assert(str.unformatValue!double(spec).isClose(1e17)); + + // raw read, depends on endianess + str = "\x40\x00\x00\xBF"; + spec = singleSpec("%r"); + auto result = str.unformatValue!float(spec); + assert(isClose(result, -0.5) /* little endian */ || isClose(result, 2.0) /* big endian */ ); +} + +/// Characters +@safe pure unittest +{ + import std.format.spec : singleSpec; + + // only the first character is read + auto str = "abc"; + auto spec = singleSpec("%s"); + assert(str.unformatValue!char(spec) == 'a'); + + // using a numerical format character treats the read number as unicode code point + 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 +@safe pure unittest +{ + import std.format.spec : singleSpec; + + // string value + string str = "aaa"; + auto spec = singleSpec("%s"); + assert(str.unformatValue!(dchar[])(spec) == "aaa"d); + + // fixed size array with characters + str = "aaa"; + spec = singleSpec("%s"); + dchar[3] ret = ['a', 'a', 'a']; + assert(str.unformatValue!(dchar[3])(spec) == ret); + + // dynamic array + str = "[1, 2, 3, 4]"; + spec = singleSpec("%s"); + assert(str.unformatValue!(int[])(spec) == [1, 2, 3, 4]); + + // fixed size array with integers + str = "[1, 2, 3, 4]"; + spec = singleSpec("%s"); + int[4] ret2 = [1, 2, 3, 4]; + assert(str.unformatValue!(int[4])(spec) == ret2); + + // compound specifiers can be used for more control + str = "1,2,3"; + spec = singleSpec("%(%s,%)"); + assert(str.unformatValue!(int[])(spec) == [1, 2, 3]); + + str = "cool"; + spec = singleSpec("%(%c%)"); + assert(str.unformatValue!(char[])(spec) == ['c', 'o', 'o', 'l']); +} + +/// Associative arrays +@safe pure unittest +{ + import std.format.spec : singleSpec; + + // as single value + auto str = `["one": 1, "two": 2]`; + auto spec = singleSpec("%s"); + assert(str.unformatValue!(int[string])(spec) == ["one": 1, "two": 2]); + + // with compound specifier for more control + str = "1/1, 2/4, 3/9"; + spec = singleSpec("%(%d/%d%|, %)"); + assert(str.unformatValue!(int[int])(spec) == [1: 1, 2: 4, 3: 9]); +} + +import std.format.spec : FormatSpec; +import std.format.internal.read; +import std.traits : isSomeString; + +/** +Reads an input range according to a format string and stores the read +values into its arguments. + +Format specifiers with format character $(B 'd'), $(B 'u') and $(B +'c') can take a $(B '*') parameter for skipping values. + +The second version of `formattedRead` takes the format string as +template argument. In this case, it is checked for consistency at +compile-time. + +Params: + r = an $(REF_ALTTEXT input range, isInputRange, std, range, primitives), + where the formatted input is read from + fmt = a $(MREF_ALTTEXT format string, std,format) + args = a variadic list of arguments where the read values are stored + Range = the type of the input range `r` + Char = the character type used for `fmt` + Args = a variadic list of types of the arguments + +Returns: + The number of variables filled. If the input range `r` ends early, + this number will be less than the number of variables provided. + +Throws: + A $(REF_ALTTEXT FormatException, FormatException, std, format) + if reading did not succeed. + +Note: + For backward compatibility the arguments `args` can be given as pointers + to that variable, but it is not recommended to do so, because this + option might be removed in the future. + */ +uint formattedRead(Range, Char, Args...)(auto ref Range r, const(Char)[] fmt, auto ref Args 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 (!Args.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 .. $]); + } +} + +/// ditto +uint formattedRead(alias fmt, Range, Args...)(auto ref Range r, auto ref Args args) +if (isSomeString!(typeof(fmt))) +{ + import std.format : checkFormatException; + + alias e = checkFormatException!(fmt, Args); + static assert(!e, e.msg); + return .formattedRead(r, fmt, args); +} + +/// +@safe pure unittest +{ + string object; + char cmp; + int value; + + assert(formattedRead("angle < 36", "%s %c %d", object, cmp, value) == 3); + assert(object == "angle"); + assert(cmp == '<'); + assert(value == 36); + + // reading may end early: + assert(formattedRead("length >", "%s %c %d", object, cmp, value) == 2); + assert(object == "length"); + assert(cmp == '>'); + // value is not changed: + assert(value == 36); +} + +/// The format string can be checked at compile-time: +@safe pure unittest +{ + string a; + int b; + double c; + + assert("hello!124:34.5".formattedRead!"%s!%s:%s"(a, b, c) == 3); + assert(a == "hello"); + assert(b == 124); + assert(c == 34.5); +} + +/// Skipping values +@safe pure unittest +{ + string item; + double amount; + + assert("orange: (12%) 15.25".formattedRead("%s: (%*d%%) %f", item, amount) == 2); + assert(item == "orange"); + assert(amount == 15.25); + + // can also be used with tuples + import std.typecons : Tuple; + + Tuple!(int, float) t; + char[] line = "1 7643 2.125".dup; + formattedRead(line, "%s %*u %s", t); + assert(t[0] == 1 && t[1] == 2.125); +} + +@safe unittest +{ + import std.math.operations : isClose; + import std.math.traits : 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 +@safe 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 +@safe pure unittest +{ + import std.math.operations : isClose; + import std.math.traits : 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)); +} + +@safe 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); +} + +@safe 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); +} + +@safe 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); +} + +@safe 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); +} + +@safe 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); +} + +@safe pure unittest +{ + string line; + + char c1, c2; + + line = "abc"; + formattedRead(line, "%s%c", &c1, &c2); + assert(c1 == 'a' && c2 == 'b'); + assert(line == "c"); +} + +@safe pure unittest +{ + string line; + + line = "[1,2,3]"; + int[] s1; + formattedRead(line, "%s", &s1); + assert(s1 == [1,2,3]); +} + +@safe 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"]); +} + +@safe 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)); +} + +@safe 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)); +} + +@safe 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"); +} + +@safe 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 +@safe 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 and converts it according to a +format specifier. + +Params: + input = the $(REF_ALTTEXT input range, isInputRange, std, range, primitives), + to read from + spec = a $(MREF_ALTTEXT format string, std,format) + T = type to return + Range = the type of the input range `input` + Char = the character type used for `spec` + +Returns: + A value from `input` of type `T`. + +Throws: + A $(REF_ALTTEXT FormatException, FormatException, std, format) + if reading did not succeed. + +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); +} + +/// +@safe pure unittest +{ + import std.format.spec : singleSpec; + + string s = "42"; + auto spec = singleSpec("%s"); + assert(unformatValue!int(s, spec) == 42); +} + +// 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'); +} + +// https://issues.dlang.org/show_bug.cgi?id=20393 +@safe pure unittest +{ + import std.exception : assertThrown; + string str = "foo 12a-buzz"; + string a, c; + int b; + assertThrown(formattedRead(str, "%s %d-%s", &a, &b, &c)); +} |