1 // Written in the D programming language.
2 
3 /**
4 This is a submodule of $(MREF std, format).
5 
6 It provides two functions for reading formatted input: $(LREF
7 unformatValue) and $(LREF formattedRead). The former reads a single
8 value. The latter reads several values at once and matches the
9 characters found between format specifiers.
10 
11 Parameters are ignored, except for the ones consisting of a single
12 $(B '*'). See $(LREF formattedRead) for more information.
13 
14 A space outside of a format specifier has a special meaning: it
15 matches any sequence of whitespace characters, not just a single
16 space.
17 
18 The following combinations of format characters and types are
19 available:
20 
21 $(BOOKTABLE ,
22 $(TR $(TH) $(TH s) $(TH c) $(TH d, u, b, o, x, X) $(TH e, E, f, g, G) $(TH r) $(TH compound))
23 $(TR $(TD `bool`) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)))
24 $(TR $(TD `null`) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)))
25 $(TR $(TD $(I integer)) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH)))
26 $(TR $(TD $(I floating point)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes) $(TD $(MDASH)))
27 $(TR $(TD $(I character)) $(TD yes) $(TD yes) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)))
28 $(TR $(TD $(I string)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes))
29 $(TR $(TD $(I array)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes))
30 $(TR $(TD $(I associative array)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes))
31 )
32 
33 Below are highlighted examples on how these combinations are used
34 with $(LREF unformatValue), however, they apply for $(LREF
35 formattedRead) also
36 
37 Copyright: Copyright The D Language Foundation 2000-2013.
38 
39 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
40 
41 Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com,
42 Andrei Alexandrescu), and Kenji Hara
43 
44 Source: $(PHOBOSSRC std/format/read.d)
45  */
46 module std.format.read;
47 
48 /// Booleans
49 @safe pure unittest
50 {
51     import std.format.spec : singleSpec;
52 
53     auto str = "false";
54     auto spec = singleSpec("%s");
55     assert(str.unformatValue!bool(spec) == false);
56 
57     str = "1";
58     spec = singleSpec("%d");
59     assert(str.unformatValue!bool(spec) == true);
60 }
61 
62 /// Null values
63 @safe pure unittest
64 {
65     import std.format.spec : singleSpec;
66 
67     auto str = "null";
68     auto spec = singleSpec("%s");
69     assert(str.unformatValue!(typeof(null))(spec) == null);
70 }
71 
72 /// Integrals
73 @safe pure unittest
74 {
75     import std.format.spec : singleSpec;
76 
77     // signed decimal values
78     auto str = "123";
79     auto spec = singleSpec("%s");
80     assert(str.unformatValue!int(spec) == 123);
81 
82     // hexadecimal values
83     str = "ABC";
84     spec = singleSpec("%X");
85     assert(str.unformatValue!int(spec) == 2748);
86 
87     // octal values
88     str = "11610";
89     spec = singleSpec("%o");
90     assert(str.unformatValue!int(spec) == 5000);
91 
92     // raw read, depends on endianess
93     str = "\x75\x01";
94     spec = singleSpec("%r");
95     auto result = str.unformatValue!short(spec);
96     assert(result == 373 /* little endian */ || result == 29953 /* big endian */ );
97 }
98 
99 /// Floating point numbers
100 @safe pure unittest
101 {
102     import std.format.spec : singleSpec;
103     import std.math.operations : isClose;
104 
105     // natural notation
106     auto str = "123.456";
107     auto spec = singleSpec("%s");
108     assert(str.unformatValue!double(spec).isClose(123.456));
109 
110     // scientific notation
111     str = "1e17";
112     spec = singleSpec("%e");
113     assert(str.unformatValue!double(spec).isClose(1e17));
114 
115     // raw read, depends on endianess
116     str = "\x40\x00\x00\xBF";
117     spec = singleSpec("%r");
118     auto result = str.unformatValue!float(spec);
119     assert(isClose(result, -0.5) /* little endian */ || isClose(result, 2.0) /* big endian */ );
120 }
121 
122 /// Characters
123 @safe pure unittest
124 {
125     import std.format.spec : singleSpec;
126 
127     // only the first character is read
128     auto str = "abc";
129     auto spec = singleSpec("%s");
130     assert(str.unformatValue!char(spec) == 'a');
131 
132     // using a numerical format character treats the read number as unicode code point
133     str = "65";
134     spec = singleSpec("%d");
135     assert(str.unformatValue!char(spec) == 'A');
136 
137     str = "41";
138     spec = singleSpec("%x");
139     assert(str.unformatValue!char(spec) == 'A');
140 
141     str = "10003";
142     spec = singleSpec("%d");
143     assert(str.unformatValue!dchar(spec) == '✓');
144 }
145 
146 /// Arrays
147 @safe pure unittest
148 {
149     import std.format.spec : singleSpec;
150 
151     // string value
152     string str = "aaa";
153     auto spec = singleSpec("%s");
154     assert(str.unformatValue!(dchar[])(spec) == "aaa"d);
155 
156     // fixed size array with characters
157     str = "aaa";
158     spec = singleSpec("%s");
159     dchar[3] ret = ['a', 'a', 'a'];
160     assert(str.unformatValue!(dchar[3])(spec) == ret);
161 
162     // dynamic array
163     str = "[1, 2, 3, 4]";
164     spec = singleSpec("%s");
165     assert(str.unformatValue!(int[])(spec) == [1, 2, 3, 4]);
166 
167     // fixed size array with integers
168     str = "[1, 2, 3, 4]";
169     spec = singleSpec("%s");
170     int[4] ret2 = [1, 2, 3, 4];
171     assert(str.unformatValue!(int[4])(spec) == ret2);
172 
173     // compound specifiers can be used for more control
174     str = "1,2,3";
175     spec = singleSpec("%(%s,%)");
176     assert(str.unformatValue!(int[])(spec) == [1, 2, 3]);
177 
178     str = "cool";
179     spec = singleSpec("%(%c%)");
180     assert(str.unformatValue!(char[])(spec) == ['c', 'o', 'o', 'l']);
181 }
182 
183 /// Associative arrays
184 @safe pure unittest
185 {
186     import std.format.spec : singleSpec;
187 
188     // as single value
189     auto str = `["one": 1, "two": 2]`;
190     auto spec = singleSpec("%s");
191     assert(str.unformatValue!(int[string])(spec) == ["one": 1, "two": 2]);
192 
193     // with compound specifier for more control
194     str = "1/1, 2/4, 3/9";
195     spec = singleSpec("%(%d/%d%|, %)");
196     assert(str.unformatValue!(int[int])(spec) == [1: 1, 2: 4, 3: 9]);
197 }
198 
199 import std.format.spec : FormatSpec;
200 import std.format.internal.read;
201 import std.meta : allSatisfy;
202 import std.traits : isSomeString, isType;
203 
204 /**
205 Reads an input range according to a format string and stores the read
206 values into its arguments.
207 
208 Format specifiers with format character $(B 'd'), $(B 'u') and $(B
209 'c') can take a $(B '*') parameter for skipping values.
210 
211 The second version of `formattedRead` takes the format string as
212 template argument. In this case, it is checked for consistency at
213 compile-time.
214 
215 Params:
216     r = an $(REF_ALTTEXT input range, isInputRange, std, range, primitives),
217         where the formatted input is read from
218     fmt = a $(MREF_ALTTEXT format string, std,format)
219     args = a variadic list of arguments where the read values are stored
220     Range = the type of the input range `r`
221     Char = the character type used for `fmt`
222     Args = a variadic list of types of the arguments
223 
224 Returns:
225     The number of variables filled. If the input range `r` ends early,
226     this number will be less than the number of variables provided.
227 
228 Throws:
229     A $(REF_ALTTEXT FormatException, FormatException, std, format)
230     if reading did not succeed.
231 
232 Note:
233     For backward compatibility the arguments `args` can be given as pointers
234     to that variable, but it is not recommended to do so, because this
235     option might be removed in the future.
236  */
237 uint formattedRead(Range, Char, Args...)(auto ref Range r, const(Char)[] fmt, auto ref Args args)
238 {
239     import std.format : enforceFmt;
240     import std.range.primitives : empty;
241     import std.traits : isPointer;
242     import std.typecons : isTuple;
243 
244     auto spec = FormatSpec!Char(fmt);
245     static if (!Args.length)
246     {
247         spec.readUpToNextSpec(r);
248         enforceFmt(spec.trailing.empty, "Trailing characters in formattedRead format string");
249         return 0;
250     }
251     else
252     {
253         enum hasPointer = isPointer!(typeof(args[0]));
254 
255         // The function below accounts for '*' == fields meant to be
256         // read and skipped
257         void skipUnstoredFields()
258         {
259             for (;;)
260             {
261                 spec.readUpToNextSpec(r);
262                 if (spec.width != spec.DYNAMIC) break;
263                 // must skip this field
264                 skipData(r, spec);
265             }
266         }
267 
268         skipUnstoredFields();
269         if (r.empty)
270         {
271             // Input is empty, nothing to read
272             return 0;
273         }
274 
275         static if (hasPointer)
276             alias A = typeof(*args[0]);
277         else
278             alias A = typeof(args[0]);
279 
280         static if (isTuple!A)
281         {
282             foreach (i, T; A.Types)
283             {
284                 static if (hasPointer)
285                     (*args[0])[i] = unformatValue!(T)(r, spec);
286                 else
287                     args[0][i] = unformatValue!(T)(r, spec);
288                 skipUnstoredFields();
289             }
290         }
291         else
292         {
293             static if (hasPointer)
294                 *args[0] = unformatValue!(A)(r, spec);
295             else
296                 args[0] = unformatValue!(A)(r, spec);
297         }
298         return 1 + formattedRead(r, spec.trailing, args[1 .. $]);
299     }
300 }
301 
302 /// ditto
303 uint formattedRead(alias fmt, Range, Args...)(auto ref Range r, auto ref Args args)
304 if (!isType!fmt && isSomeString!(typeof(fmt)))
305 {
306     import std.format : checkFormatException;
307     import std.meta : staticMap;
308     import std.typecons : Tuple;
309 
310 
311     // formattedRead supports std.typecons.Tuple
312     // however, checkFormatException does not
313     // this means that all std.typecons.Tuple's types in Args must be unwrapped
314     // and passed to checkFormatException
315     template Flatten(T)
316     {
317         static if (is(T : Tuple!Args, Args...))
318             alias Flatten = Args;
319         else
320             alias Flatten = T;
321     }
322 
323     alias e = checkFormatException!(fmt, staticMap!(Flatten, Args));
324     static assert(!e, e);
325     return .formattedRead(r, fmt, args);
326 }
327 
328 ///
329 @safe pure unittest
330 {
331     string object;
332     char cmp;
333     int value;
334 
335     assert(formattedRead("angle < 36", "%s %c %d", object, cmp, value) == 3);
336     assert(object == "angle");
337     assert(cmp == '<');
338     assert(value == 36);
339 
340     // reading may end early:
341     assert(formattedRead("length >", "%s %c %d", object, cmp, value) == 2);
342     assert(object == "length");
343     assert(cmp == '>');
344     // value is not changed:
345     assert(value == 36);
346 }
347 
348 /// The format string can be checked at compile-time:
349 @safe pure unittest
350 {
351     string a;
352     int b;
353     double c;
354 
355     assert("hello!124:34.5".formattedRead!"%s!%s:%s"(a, b, c) == 3);
356     assert(a == "hello");
357     assert(b == 124);
358     assert(c == 34.5);
359 }
360 
361 /// Skipping values
362 @safe pure unittest
363 {
364     string item;
365     double amount;
366 
367     assert("orange: (12%) 15.25".formattedRead("%s: (%*d%%) %f", item, amount) == 2);
368     assert(item == "orange");
369     assert(amount == 15.25);
370 
371     // can also be used with tuples
372     import std.typecons : Tuple;
373 
374     Tuple!(int, float) t;
375     char[] line = "1 7643 2.125".dup;
376     formattedRead(line, "%s %*u %s", t);
377     assert(t[0] == 1 && t[1] == 2.125);
378 }
379 
380 // https://issues.dlang.org/show_bug.cgi?id=23600
381 @safe pure unittest
382 {
383     import std.typecons : Tuple, tuple;
384 
385     string h, w;
386     Tuple!(int, float) t;
387 
388     assert("hello 1 2.34 world".formattedRead!"%s %d %f %s"(h, t, w) == 3);
389     assert(h == "hello");
390     assert(t == tuple(1, 2.34f));
391     assert(w == "world");
392 }
393 
394 @safe unittest
395 {
396     import std.math.operations : isClose;
397     import std.math.traits : isNaN;
398     import std.range.primitives : empty;
399 
400     string s = " 1.2 3.4 ";
401     double x, y, z;
402     assert(formattedRead(s, " %s %s %s ", x, y, z) == 2);
403     assert(s.empty);
404     assert(isClose(x, 1.2));
405     assert(isClose(y, 3.4));
406     assert(isNaN(z));
407 }
408 
409 // for backwards compatibility
410 @safe pure unittest
411 {
412     string s = "hello!124:34.5";
413     string a;
414     int b;
415     double c;
416     formattedRead(s, "%s!%s:%s", &a, &b, &c);
417     assert(a == "hello" && b == 124 && c == 34.5);
418 
419     // mix pointers and auto-ref
420     s = "world!200:42.25";
421     formattedRead(s, "%s!%s:%s", a, &b, &c);
422     assert(a == "world" && b == 200 && c == 42.25);
423 
424     s = "world1!201:42.5";
425     formattedRead(s, "%s!%s:%s", &a, &b, c);
426     assert(a == "world1" && b == 201 && c == 42.5);
427 
428     s = "world2!202:42.75";
429     formattedRead(s, "%s!%s:%s", a, b, &c);
430     assert(a == "world2" && b == 202 && c == 42.75);
431 }
432 
433 // for backwards compatibility
434 @safe pure unittest
435 {
436     import std.math.operations : isClose;
437     import std.math.traits : isNaN;
438     import std.range.primitives : empty;
439 
440     string s = " 1.2 3.4 ";
441     double x, y, z;
442     assert(formattedRead(s, " %s %s %s ", &x, &y, &z) == 2);
443     assert(s.empty);
444     assert(isClose(x, 1.2));
445     assert(isClose(y, 3.4));
446     assert(isNaN(z));
447 }
448 
449 @safe unittest
450 {
451     string s = "hello!124:34.5";
452     string a;
453     int b;
454     double c;
455     formattedRead(s, "%s!%s:%s", &a, &b, &c);
456     assert(a == "hello" && b == 124 && c == 34.5);
457 }
458 
459 @safe pure unittest
460 {
461     string line;
462 
463     bool f1;
464 
465     line = "true";
466     formattedRead(line, "%s", &f1);
467     assert(f1);
468 
469     line = "TrUE";
470     formattedRead(line, "%s", &f1);
471     assert(f1);
472 
473     line = "false";
474     formattedRead(line, "%s", &f1);
475     assert(!f1);
476 
477     line = "fALsE";
478     formattedRead(line, "%s", &f1);
479     assert(!f1);
480 
481     line = "1";
482     formattedRead(line, "%d", &f1);
483     assert(f1);
484 
485     line = "-1";
486     formattedRead(line, "%d", &f1);
487     assert(f1);
488 
489     line = "0";
490     formattedRead(line, "%d", &f1);
491     assert(!f1);
492 
493     line = "-0";
494     formattedRead(line, "%d", &f1);
495     assert(!f1);
496 }
497 
498 @safe pure unittest
499 {
500     union B
501     {
502         char[int.sizeof] untyped;
503         int typed;
504     }
505 
506     B b;
507     b.typed = 5;
508     char[] input = b.untyped[];
509     int witness;
510     formattedRead(input, "%r", &witness);
511     assert(witness == b.typed);
512 }
513 
514 @safe pure unittest
515 {
516     union A
517     {
518         char[float.sizeof] untyped;
519         float typed;
520     }
521 
522     A a;
523     a.typed = 5.5;
524     char[] input = a.untyped[];
525     float witness;
526     formattedRead(input, "%r", &witness);
527     assert(witness == a.typed);
528 }
529 
530 @safe pure unittest
531 {
532     import std.typecons : Tuple;
533 
534     char[] line = "1 2".dup;
535     int a, b;
536     formattedRead(line, "%s %s", &a, &b);
537     assert(a == 1 && b == 2);
538 
539     line = "10 2 3".dup;
540     formattedRead(line, "%d ", &a);
541     assert(a == 10);
542     assert(line == "2 3");
543 
544     Tuple!(int, float) t;
545     line = "1 2.125".dup;
546     formattedRead(line, "%d %g", &t);
547     assert(t[0] == 1 && t[1] == 2.125);
548 
549     line = "1 7643 2.125".dup;
550     formattedRead(line, "%s %*u %s", &t);
551     assert(t[0] == 1 && t[1] == 2.125);
552 }
553 
554 @safe pure unittest
555 {
556     string line;
557 
558     char c1, c2;
559 
560     line = "abc";
561     formattedRead(line, "%s%c", &c1, &c2);
562     assert(c1 == 'a' && c2 == 'b');
563     assert(line == "c");
564 }
565 
566 @safe pure unittest
567 {
568     string line;
569 
570     line = "[1,2,3]";
571     int[] s1;
572     formattedRead(line, "%s", &s1);
573     assert(s1 == [1,2,3]);
574 }
575 
576 @safe pure unittest
577 {
578     string line;
579 
580     line = "[1,2,3]";
581     int[] s1;
582     formattedRead(line, "[%(%s,%)]", &s1);
583     assert(s1 == [1,2,3]);
584 
585     line = `["hello", "world"]`;
586     string[] s2;
587     formattedRead(line, "[%(%s, %)]", &s2);
588     assert(s2 == ["hello", "world"]);
589 
590     line = "123 456";
591     int[] s3;
592     formattedRead(line, "%(%s %)", &s3);
593     assert(s3 == [123, 456]);
594 
595     line = "h,e,l,l,o; w,o,r,l,d";
596     string[] s4;
597     formattedRead(line, "%(%(%c,%); %)", &s4);
598     assert(s4 == ["hello", "world"]);
599 }
600 
601 @safe pure unittest
602 {
603     import std.exception : assertThrown;
604 
605     string line;
606 
607     int[4] sa1;
608     line = `[1,2,3,4]`;
609     formattedRead(line, "%s", &sa1);
610     assert(sa1 == [1,2,3,4]);
611 
612     int[4] sa2;
613     line = `[1,2,3]`;
614     assertThrown(formattedRead(line, "%s", &sa2));
615 
616     int[4] sa3;
617     line = `[1,2,3,4,5]`;
618     assertThrown(formattedRead(line, "%s", &sa3));
619 }
620 
621 @safe pure unittest
622 {
623     import std.exception : assertThrown;
624     import std.format : FormatException;
625 
626     string input;
627 
628     int[4] sa1;
629     input = `[1,2,3,4]`;
630     formattedRead(input, "[%(%s,%)]", &sa1);
631     assert(sa1 == [1,2,3,4]);
632 
633     int[4] sa2;
634     input = `[1,2,3]`;
635     assertThrown!FormatException(formattedRead(input, "[%(%s,%)]", &sa2));
636 }
637 
638 @safe pure unittest
639 {
640     string line;
641 
642     string s1, s2;
643 
644     line = "hello, world";
645     formattedRead(line, "%s", &s1);
646     assert(s1 == "hello, world", s1);
647 
648     line = "hello, world;yah";
649     formattedRead(line, "%s;%s", &s1, &s2);
650     assert(s1 == "hello, world", s1);
651     assert(s2 == "yah", s2);
652 
653     line = `['h','e','l','l','o']`;
654     string s3;
655     formattedRead(line, "[%(%s,%)]", &s3);
656     assert(s3 == "hello");
657 
658     line = `"hello"`;
659     string s4;
660     formattedRead(line, "\"%(%c%)\"", &s4);
661     assert(s4 == "hello");
662 }
663 
664 @safe pure unittest
665 {
666     string line;
667 
668     string[int] aa1;
669     line = `[1:"hello", 2:"world"]`;
670     formattedRead(line, "%s", &aa1);
671     assert(aa1 == [1:"hello", 2:"world"]);
672 
673     int[string] aa2;
674     line = `{"hello"=1; "world"=2}`;
675     formattedRead(line, "{%(%s=%s; %)}", &aa2);
676     assert(aa2 == ["hello":1, "world":2]);
677 
678     int[string] aa3;
679     line = `{[hello=1]; [world=2]}`;
680     formattedRead(line, "{%([%(%c%)=%s]%|; %)}", &aa3);
681     assert(aa3 == ["hello":1, "world":2]);
682 }
683 
684 // test rvalue using
685 @safe pure unittest
686 {
687     string[int] aa1;
688     formattedRead!("%s")(`[1:"hello", 2:"world"]`, aa1);
689     assert(aa1 == [1:"hello", 2:"world"]);
690 
691     int[string] aa2;
692     formattedRead(`{"hello"=1; "world"=2}`, "{%(%s=%s; %)}", aa2);
693     assert(aa2 == ["hello":1, "world":2]);
694 }
695 
696 /**
697 Reads an input range according to a format string and returns a tuple of Args
698 with the read values.
699 
700 Format specifiers with format character $(B 'd'), $(B 'u') and $(B
701 'c') can take a $(B '*') parameter for skipping values.
702 
703 The second version of `formattedRead` takes the format string as
704 template argument. In this case, it is checked for consistency at
705 compile-time.
706 
707 Params:
708     Args = a variadic list of types of the arguments
709  */
710 template formattedRead(Args...)
711 if (Args.length && allSatisfy!(isType, Args))
712 {
713     import std.typecons : Tuple;
714 
715     /**
716     Params:
717         r = an $(REF_ALTTEXT input range, isInputRange, std, range, primitives),
718             where the formatted input is read from
719         fmt = a $(MREF_ALTTEXT format string, std,format)
720         Range = the type of the input range `r`
721         Char = the character type used for `fmt`
722 
723     Returns:
724         A Tuple!Args with the elements filled.
725 
726     Throws:
727         A $(REF_ALTTEXT FormatException, FormatException, std, format)
728         if reading did not succeed.
729     */
730     Tuple!Args formattedRead(Range, Char)(auto ref Range r, const(Char)[] fmt)
731     {
732         import core.lifetime : forward;
733         import std.format : enforceFmt;
734 
735         Tuple!Args args;
736         const numArgsFilled = .formattedRead(forward!r, fmt, args.expand);
737         enforceFmt(numArgsFilled == Args.length, "Failed reading into all format arguments");
738         return args;
739     }
740 }
741 
742 ///
743 @safe pure unittest
744 {
745     import std.exception : assertThrown;
746     import std.format : FormatException;
747     import std.typecons : tuple;
748 
749     auto complete = "hello!34.5:124".formattedRead!(string, double, int)("%s!%s:%s");
750     assert(complete == tuple("hello", 34.5, 124));
751 
752     // reading ends early
753     assertThrown!FormatException("hello!34.5:".formattedRead!(string, double, int)("%s!%s:%s"));
754 }
755 
756 /// Skipping values
757 @safe pure unittest
758 {
759     import std.format : FormatException;
760     import std.typecons : tuple;
761 
762     auto result = "orange: (12%) 15.25".formattedRead!(string, double)("%s: (%*d%%) %f");
763     assert(result == tuple("orange", 15.25));
764 }
765 
766 /// ditto
767 template formattedRead(alias fmt, Args...)
768 if (!isType!fmt && isSomeString!(typeof(fmt)) && Args.length && allSatisfy!(isType, Args))
769 {
770     import std.typecons : Flag, Tuple, Yes;
771     Tuple!Args formattedRead(Range)(auto ref Range r)
772     {
773         import core.lifetime : forward;
774         import std.format : enforceFmt;
775 
776         Tuple!Args args;
777         const numArgsFilled = .formattedRead!fmt(forward!r, args.expand);
778         enforceFmt(numArgsFilled == Args.length, "Failed reading into all format arguments");
779         return args;
780     }
781 }
782 
783 /// The format string can be checked at compile-time
784 @safe pure unittest
785 {
786     import std.exception : assertThrown;
787     import std.format : FormatException;
788     import std.typecons : tuple;
789 
790     auto expected = tuple("hello", 124, 34.5);
791     auto result = "hello!124:34.5".formattedRead!("%s!%s:%s", string, int, double);
792     assert(result == expected);
793 
794     assertThrown!FormatException("hello!34.5:".formattedRead!("%s!%s:%s", string, double, int));
795 }
796 
797 /// Compile-time consistency check
798 @safe pure unittest
799 {
800     import std.format : FormatException;
801     import std.typecons : tuple;
802 
803     static assert(!__traits(compiles, "orange: (12%) 15.25".formattedRead!("%s: (%*d%%) %f", string, double)));
804 }
805 
806 /**
807 Reads a value from the given _input range and converts it according to a
808 format specifier.
809 
810 Params:
811     input = the $(REF_ALTTEXT input range, isInputRange, std, range, primitives),
812             to read from
813     spec = a $(MREF_ALTTEXT format string, std,format)
814     T = type to return
815     Range = the type of the input range `input`
816     Char = the character type used for `spec`
817 
818 Returns:
819     A value from `input` of type `T`.
820 
821 Throws:
822     A $(REF_ALTTEXT FormatException, FormatException, std, format)
823     if reading did not succeed.
824 
825 See_Also:
826     $(REF parse, std, conv) and $(REF to, std, conv)
827  */
828 T unformatValue(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec)
829 {
830     return unformatValueImpl!T(input, spec);
831 }
832 
833 ///
834 @safe pure unittest
835 {
836     import std.format.spec : singleSpec;
837 
838     string s = "42";
839     auto spec = singleSpec("%s");
840     assert(unformatValue!int(s, spec) == 42);
841 }
842 
843 // https://issues.dlang.org/show_bug.cgi?id=7241
844 @safe pure unittest
845 {
846     string input = "a";
847     auto spec = FormatSpec!char("%s");
848     spec.readUpToNextSpec(input);
849     auto result = unformatValue!(dchar[1])(input, spec);
850     assert(result[0] == 'a');
851 }
852 
853 // https://issues.dlang.org/show_bug.cgi?id=20393
854 @safe pure unittest
855 {
856     import std.exception : assertThrown;
857     string str = "foo 12a-buzz";
858     string a, c;
859     int b;
860     assertThrown(formattedRead(str, "%s %d-%s", &a, &b, &c));
861 }
862 
863 // https://issues.dlang.org/show_bug.cgi?id=18051
864 @safe pure unittest
865 {
866     import std.format : format;
867 
868     enum Op { lt, gt, eq }
869 
870     auto s = format!"%s"(Op.lt);
871     Op op;
872     assert(formattedRead!"%s"(s, op) == 1);
873     assert(op == Op.lt);
874 }