// 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); 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)); } // https://issues.dlang.org/show_bug.cgi?id=18051 @safe pure unittest { import std.format : format; enum Op { lt, gt, eq } auto s = format!"%s"(Op.lt); Op op; assert(formattedRead!"%s"(s, op) == 1); assert(op == Op.lt); }