1 // Written in the D programming language.
2
3 /**
4 This module implements the formatting functionality for strings and
5 I/O. It's comparable to C99's $(D vsprintf()) and uses a similar
6 _format encoding scheme.
7
8 For an introductory look at $(B std._format)'s capabilities and how to use
9 this module see the dedicated
10 $(LINK2 http://wiki.dlang.org/Defining_custom_print_format_specifiers, DWiki article).
11
12 This module centers around two functions:
13
14 $(BOOKTABLE ,
15 $(TR $(TH Function Name) $(TH Description)
16 )
17 $(TR $(TD $(LREF formattedRead))
18 $(TD Reads values according to the _format string from an InputRange.
19 ))
20 $(TR $(TD $(LREF formattedWrite))
21 $(TD Formats its arguments according to the _format string and puts them
22 to an OutputRange.
23 ))
24 )
25
26 Please see the documentation of function $(LREF formattedWrite) for a
27 description of the _format string.
28
29 Two functions have been added for convenience:
30
31 $(BOOKTABLE ,
32 $(TR $(TH Function Name) $(TH Description)
33 )
34 $(TR $(TD $(LREF _format))
35 $(TD Returns a GC-allocated string with the formatting result.
36 ))
37 $(TR $(TD $(LREF sformat))
38 $(TD Puts the formatting result into a preallocated array.
39 ))
40 )
41
42 These two functions are publicly imported by $(MREF std, string)
43 to be easily available.
44
45 The functions $(LREF formatValue) and $(LREF unformatValue) are
46 used for the plumbing.
47 Copyright: Copyright Digital Mars 2000-2013.
48
49 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
50
51 Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com,
52 Andrei Alexandrescu), and Kenji Hara
53
54 Source: $(PHOBOSSRC std/_format.d)
55 */
56 module std.format;
57
58 //debug=format; // uncomment to turn on debugging printf's
59
60 import core.vararg;
61 import std.exception;
62 import std.meta;
63 import std.range.primitives;
64 import std.traits;
65
66
67 /**********************************************************************
68 * Signals a mismatch between a format and its corresponding argument.
69 */
70 class FormatException : Exception
71 {
72 @safe pure nothrow
this()73 this()
74 {
75 super("format error");
76 }
77
78 @safe pure nothrow
79 this(string msg, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null)
80 {
81 super(msg, fn, ln, next);
82 }
83 }
84
85 private alias enforceFmt = enforceEx!FormatException;
86
87
88 /**********************************************************************
89 Interprets variadic argument list $(D args), formats them according
90 to $(D fmt), and sends the resulting characters to $(D w). The
91 encoding of the output is the same as $(D Char). The type $(D Writer)
92 must satisfy $(D $(REF isOutputRange, std,range,primitives)!(Writer, Char)).
93
94 The variadic arguments are normally consumed in order. POSIX-style
95 $(HTTP opengroup.org/onlinepubs/009695399/functions/printf.html,
96 positional parameter syntax) is also supported. Each argument is
97 formatted into a sequence of chars according to the format
98 specification, and the characters are passed to $(D w). As many
99 arguments as specified in the format string are consumed and
100 formatted. If there are fewer arguments than format specifiers, a
101 $(D FormatException) is thrown. If there are more remaining arguments
102 than needed by the format specification, they are ignored but only
103 if at least one argument was formatted.
104
105 The format string supports the formatting of array and nested array elements
106 via the grouping format specifiers $(B %() and $(B %)). Each
107 matching pair of $(B %() and $(B %)) corresponds with a single array
108 argument. The enclosed sub-format string is applied to individual array
109 elements. The trailing portion of the sub-format string following the
110 conversion specifier for the array element is interpreted as the array
111 delimiter, and is therefore omitted following the last array element. The
112 $(B %|) specifier may be used to explicitly indicate the start of the
113 delimiter, so that the preceding portion of the string will be included
114 following the last array element. (See below for explicit examples.)
115
116 Params:
117
118 w = Output is sent to this writer. Typical output writers include
119 $(REF Appender!string, std,array) and $(REF LockingTextWriter, std,stdio).
120
121 fmt = Format string.
122
123 args = Variadic argument list.
124
125 Returns: Formatted number of arguments.
126
127 Throws: Mismatched arguments and formats result in a $(D
128 FormatException) being thrown.
129
130 Format_String: <a name="format-string">$(I Format strings)</a>
131 consist of characters interspersed with $(I format
132 specifications). Characters are simply copied to the output (such
133 as putc) after any necessary conversion to the corresponding UTF-8
134 sequence.
135
136 The format string has the following grammar:
137
138 $(PRE
139 $(I FormatString):
140 $(I FormatStringItem)*
141 $(I FormatStringItem):
142 $(B '%%')
143 $(B '%') $(I Position) $(I Flags) $(I Width) $(I Separator) $(I Precision) $(I FormatChar)
144 $(B '%$(LPAREN)') $(I FormatString) $(B '%$(RPAREN)')
145 $(I OtherCharacterExceptPercent)
146 $(I Position):
147 $(I empty)
148 $(I Integer) $(B '$')
149 $(I Flags):
150 $(I empty)
151 $(B '-') $(I Flags)
152 $(B '+') $(I Flags)
153 $(B '#') $(I Flags)
154 $(B '0') $(I Flags)
155 $(B ' ') $(I Flags)
156 $(I Width):
157 $(I empty)
158 $(I Integer)
159 $(B '*')
160 $(I Separator):
161 $(I empty)
162 $(B ',')
163 $(B ',') $(B '?')
164 $(B ',') $(B '*') $(B '?')
165 $(B ',') $(I Integer) $(B '?')
166 $(B ',') $(B '*')
167 $(B ',') $(I Integer)
168 $(I Precision):
169 $(I empty)
170 $(B '.')
171 $(B '.') $(I Integer)
172 $(B '.*')
173 $(I Integer):
174 $(I Digit)
175 $(I Digit) $(I Integer)
176 $(I Digit):
177 $(B '0')|$(B '1')|$(B '2')|$(B '3')|$(B '4')|$(B '5')|$(B '6')|$(B '7')|$(B '8')|$(B '9')
178 $(I FormatChar):
179 $(B 's')|$(B 'c')|$(B 'b')|$(B 'd')|$(B 'o')|$(B 'x')|$(B 'X')|$(B 'e')|$(B 'E')|$(B 'f')|$(B 'F')|$(B 'g')|$(B 'G')|$(B 'a')|$(B 'A')|$(B '|')
180 )
181
182 $(BOOKTABLE Flags affect formatting depending on the specifier as
183 follows., $(TR $(TH Flag) $(TH Types affected) $(TH Semantics))
184
185 $(TR $(TD $(B '-')) $(TD numeric) $(TD Left justify the result in
186 the field. It overrides any $(B 0) flag.))
187
188 $(TR $(TD $(B '+')) $(TD numeric) $(TD Prefix positive numbers in
189 a signed conversion with a $(B +). It overrides any $(I space)
190 flag.))
191
192 $(TR $(TD $(B '#')) $(TD integral ($(B 'o'))) $(TD Add to
193 precision as necessary so that the first digit of the octal
194 formatting is a '0', even if both the argument and the $(I
195 Precision) are zero.))
196
197 $(TR $(TD $(B '#')) $(TD integral ($(B 'x'), $(B 'X'))) $(TD If
198 non-zero, prefix result with $(B 0x) ($(B 0X)).))
199
200 $(TR $(TD $(B '#')) $(TD floating) $(TD Always insert the decimal
201 point and print trailing zeros.))
202
203 $(TR $(TD $(B '0')) $(TD numeric) $(TD Use leading
204 zeros to pad rather than spaces (except for the floating point
205 values $(D nan) and $(D infinity)). Ignore if there's a $(I
206 Precision).))
207
208 $(TR $(TD $(B ' ')) $(TD numeric) $(TD Prefix positive
209 numbers in a signed conversion with a space.)))
210
211 $(DL
212 $(DT $(I Width))
213 $(DD
214 Specifies the minimum field width.
215 If the width is a $(B *), an additional argument of type $(B int),
216 preceding the actual argument, is taken as the width.
217 If the width is negative, it is as if the $(B -) was given
218 as a $(I Flags) character.)
219
220 $(DT $(I Precision))
221 $(DD Gives the precision for numeric conversions.
222 If the precision is a $(B *), an additional argument of type $(B int),
223 preceding the actual argument, is taken as the precision.
224 If it is negative, it is as if there was no $(I Precision) specifier.)
225
226 $(DT $(I Separator))
227 $(DD Inserts the separator symbols ',' every $(I X) digits, from right
228 to left, into numeric values to increase readability.
229 The fractional part of floating point values inserts the separator
230 from left to right.
231 Entering an integer after the ',' allows to specify $(I X).
232 If a '*' is placed after the ',' then $(I X) is specified by an
233 additional parameter to the format function.
234 Adding a '?' after the ',' or $(I X) specifier allows to specify
235 the separator character as an additional parameter.
236 )
237
238 $(DT $(I FormatChar))
239 $(DD
240 $(DL
241 $(DT $(B 's'))
242 $(DD The corresponding argument is formatted in a manner consistent
243 with its type:
244 $(DL
245 $(DT $(B bool))
246 $(DD The result is $(D "true") or $(D "false").)
247 $(DT integral types)
248 $(DD The $(B %d) format is used.)
249 $(DT floating point types)
250 $(DD The $(B %g) format is used.)
251 $(DT string types)
252 $(DD The result is the string converted to UTF-8.
253 A $(I Precision) specifies the maximum number of characters
254 to use in the result.)
255 $(DT structs)
256 $(DD If the struct defines a $(B toString()) method the result is
257 the string returned from this function. Otherwise the result is
258 StructName(field<sub>0</sub>, field<sub>1</sub>, ...) where
259 field<sub>n</sub> is the nth element formatted with the default
260 format.)
261 $(DT classes derived from $(B Object))
262 $(DD The result is the string returned from the class instance's
263 $(B .toString()) method.
264 A $(I Precision) specifies the maximum number of characters
265 to use in the result.)
266 $(DT unions)
267 $(DD If the union defines a $(B toString()) method the result is
268 the string returned from this function. Otherwise the result is
269 the name of the union, without its contents.)
270 $(DT non-string static and dynamic arrays)
271 $(DD The result is [s<sub>0</sub>, s<sub>1</sub>, ...]
272 where s<sub>n</sub> is the nth element
273 formatted with the default format.)
274 $(DT associative arrays)
275 $(DD The result is the equivalent of what the initializer
276 would look like for the contents of the associative array,
277 e.g.: ["red" : 10, "blue" : 20].)
278 ))
279
280 $(DT $(B 'c'))
281 $(DD The corresponding argument must be a character type.)
282
283 $(DT $(B 'b','d','o','x','X'))
284 $(DD The corresponding argument must be an integral type
285 and is formatted as an integer. If the argument is a signed type
286 and the $(I FormatChar) is $(B d) it is converted to
287 a signed string of characters, otherwise it is treated as
288 unsigned. An argument of type $(B bool) is formatted as '1'
289 or '0'. The base used is binary for $(B b), octal for $(B o),
290 decimal
291 for $(B d), and hexadecimal for $(B x) or $(B X).
292 $(B x) formats using lower case letters, $(B X) uppercase.
293 If there are fewer resulting digits than the $(I Precision),
294 leading zeros are used as necessary.
295 If the $(I Precision) is 0 and the number is 0, no digits
296 result.)
297
298 $(DT $(B 'e','E'))
299 $(DD A floating point number is formatted as one digit before
300 the decimal point, $(I Precision) digits after, the $(I FormatChar),
301 ±, followed by at least a two digit exponent:
302 $(I d.dddddd)e$(I ±dd).
303 If there is no $(I Precision), six
304 digits are generated after the decimal point.
305 If the $(I Precision) is 0, no decimal point is generated.)
306
307 $(DT $(B 'f','F'))
308 $(DD A floating point number is formatted in decimal notation.
309 The $(I Precision) specifies the number of digits generated
310 after the decimal point. It defaults to six. At least one digit
311 is generated before the decimal point. If the $(I Precision)
312 is zero, no decimal point is generated.)
313
314 $(DT $(B 'g','G'))
315 $(DD A floating point number is formatted in either $(B e) or
316 $(B f) format for $(B g); $(B E) or $(B F) format for
317 $(B G).
318 The $(B f) format is used if the exponent for an $(B e) format
319 is greater than -5 and less than the $(I Precision).
320 The $(I Precision) specifies the number of significant
321 digits, and defaults to six.
322 Trailing zeros are elided after the decimal point, if the fractional
323 part is zero then no decimal point is generated.)
324
325 $(DT $(B 'a','A'))
326 $(DD A floating point number is formatted in hexadecimal
327 exponential notation 0x$(I h.hhhhhh)p$(I ±d).
328 There is one hexadecimal digit before the decimal point, and as
329 many after as specified by the $(I Precision).
330 If the $(I Precision) is zero, no decimal point is generated.
331 If there is no $(I Precision), as many hexadecimal digits as
332 necessary to exactly represent the mantissa are generated.
333 The exponent is written in as few digits as possible,
334 but at least one, is in decimal, and represents a power of 2 as in
335 $(I h.hhhhhh)*2<sup>$(I ±d)</sup>.
336 The exponent for zero is zero.
337 The hexadecimal digits, x and p are in upper case if the
338 $(I FormatChar) is upper case.)
339 ))
340 )
341
342 Floating point NaN's are formatted as $(B nan) if the
343 $(I FormatChar) is lower case, or $(B NAN) if upper.
344 Floating point infinities are formatted as $(B inf) or
345 $(B infinity) if the
346 $(I FormatChar) is lower case, or $(B INF) or $(B INFINITY) if upper.
347
348 The positional and non-positional styles can be mixed in the same
349 format string. (POSIX leaves this behavior undefined.) The internal
350 counter for non-positional parameters tracks the next parameter after
351 the largest positional parameter already used.
352
353 Example using array and nested array formatting:
354 -------------------------
355 import std.stdio;
356
357 void main()
358 {
359 writefln("My items are %(%s %).", [1,2,3]);
360 writefln("My items are %(%s, %).", [1,2,3]);
361 }
362 -------------------------
363 The output is:
364 $(CONSOLE
365 My items are 1 2 3.
366 My items are 1, 2, 3.
367 )
368
369 The trailing end of the sub-format string following the specifier for each
370 item is interpreted as the array delimiter, and is therefore omitted
371 following the last array item. The $(B %|) delimiter specifier may be used
372 to indicate where the delimiter begins, so that the portion of the format
373 string prior to it will be retained in the last array element:
374 -------------------------
375 import std.stdio;
376
377 void main()
378 {
379 writefln("My items are %(-%s-%|, %).", [1,2,3]);
380 }
381 -------------------------
382 which gives the output:
383 $(CONSOLE
384 My items are -1-, -2-, -3-.
385 )
386
387 These compound format specifiers may be nested in the case of a nested
388 array argument:
389 -------------------------
390 import std.stdio;
391 void main() {
392 auto mat = [[1, 2, 3],
393 [4, 5, 6],
394 [7, 8, 9]];
395
396 writefln("%(%(%d %)\n%)", mat);
397 writeln();
398
399 writefln("[%(%(%d %)\n %)]", mat);
400 writeln();
401
402 writefln("[%([%(%d %)]%|\n %)]", mat);
403 writeln();
404 }
405 -------------------------
406 The output is:
407 $(CONSOLE
408 1 2 3
409 4 5 6
410 7 8 9
411
412 [1 2 3
413 4 5 6
414 7 8 9]
415
416 [[1 2 3]
417 [4 5 6]
418 [7 8 9]]
419 )
420
421 Inside a compound format specifier, strings and characters are escaped
422 automatically. To avoid this behavior, add $(B '-') flag to
423 $(D "%$(LPAREN)").
424 -------------------------
425 import std.stdio;
426
427 void main()
428 {
429 writefln("My friends are %s.", ["John", "Nancy"]);
430 writefln("My friends are %(%s, %).", ["John", "Nancy"]);
431 writefln("My friends are %-(%s, %).", ["John", "Nancy"]);
432 }
433 -------------------------
434 which gives the output:
435 $(CONSOLE
436 My friends are ["John", "Nancy"].
437 My friends are "John", "Nancy".
438 My friends are John, Nancy.
439 )
440 */
441 uint formattedWrite(alias fmt, Writer, A...)(auto ref Writer w, A args)
442 if (isSomeString!(typeof(fmt)))
443 {
444 alias e = checkFormatException!(fmt, A);
445 static assert(!e, e.msg);
446 return .formattedWrite(w, fmt, args);
447 }
448
449 /// The format string can be checked at compile-time (see $(LREF format) for details):
450 @safe pure unittest
451 {
452 import std.array : appender;
453 import std.format : formattedWrite;
454
455 auto writer = appender!string();
456 writer.formattedWrite!"%s is the ultimate %s."(42, "answer");
457 assert(writer.data == "42 is the ultimate answer.");
458
459 // Clear the writer
460 writer = appender!string();
461 formattedWrite(writer, "Date: %2$s %1$s", "October", 5);
462 assert(writer.data == "Date: 5 October");
463 }
464
465 /// ditto
formattedWrite(Writer,Char,A...)466 uint formattedWrite(Writer, Char, A...)(auto ref Writer w, in Char[] fmt, A args)
467 {
468 import std.conv : text;
469
470 auto spec = FormatSpec!Char(fmt);
471
472 // Are we already done with formats? Then just dump each parameter in turn
473 uint currentArg = 0;
474 while (spec.writeUpToNextSpec(w))
475 {
476 if (currentArg == A.length && !spec.indexStart)
477 {
478 // leftover spec?
479 enforceFmt(fmt.length == 0,
480 text("Orphan format specifier: %", spec.spec));
481 break;
482 }
483
484 if (spec.width == spec.DYNAMIC)
485 {
486 auto width = getNthInt!"integer width"(currentArg, args);
487 if (width < 0)
488 {
489 spec.flDash = true;
490 width = -width;
491 }
492 spec.width = width;
493 ++currentArg;
494 }
495 else if (spec.width < 0)
496 {
497 // means: get width as a positional parameter
498 auto index = cast(uint) -spec.width;
499 assert(index > 0);
500 auto width = getNthInt!"integer width"(index - 1, args);
501 if (currentArg < index) currentArg = index;
502 if (width < 0)
503 {
504 spec.flDash = true;
505 width = -width;
506 }
507 spec.width = width;
508 }
509
510 if (spec.precision == spec.DYNAMIC)
511 {
512 auto precision = getNthInt!"integer precision"(currentArg, args);
513 if (precision >= 0) spec.precision = precision;
514 // else negative precision is same as no precision
515 else spec.precision = spec.UNSPECIFIED;
516 ++currentArg;
517 }
518 else if (spec.precision < 0)
519 {
520 // means: get precision as a positional parameter
521 auto index = cast(uint) -spec.precision;
522 assert(index > 0);
523 auto precision = getNthInt!"integer precision"(index- 1, args);
524 if (currentArg < index) currentArg = index;
525 if (precision >= 0) spec.precision = precision;
526 // else negative precision is same as no precision
527 else spec.precision = spec.UNSPECIFIED;
528 }
529
530 if (spec.separators == spec.DYNAMIC)
531 {
532 auto separators = getNthInt!"separator digit width"(currentArg, args);
533 spec.separators = separators;
534 ++currentArg;
535 }
536
537 if (spec.separatorCharPos == spec.DYNAMIC)
538 {
539 auto separatorChar =
540 getNth!("separator character", isSomeChar, dchar)(currentArg, args);
541 spec.separatorChar = separatorChar;
542 ++currentArg;
543 }
544
545 if (currentArg == A.length && !spec.indexStart)
546 {
547 // leftover spec?
548 enforceFmt(fmt.length == 0,
549 text("Orphan format specifier: %", spec.spec));
550 break;
551 }
552
553 // Format an argument
554 // This switch uses a static foreach to generate a jump table.
555 // Currently `spec.indexStart` use the special value '0' to signal
556 // we should use the current argument. An enhancement would be to
557 // always store the index.
558 size_t index = currentArg;
559 if (spec.indexStart != 0)
560 index = spec.indexStart - 1;
561 else
562 ++currentArg;
563 SWITCH: switch (index)
564 {
565 foreach (i, Tunused; A)
566 {
567 case i:
568 formatValue(w, args[i], spec);
569 if (currentArg < spec.indexEnd)
570 currentArg = spec.indexEnd;
571 // A little know feature of format is to format a range
572 // of arguments, e.g. `%1:3$` will format the first 3
573 // arguments. Since they have to be consecutive we can
574 // just use explicit fallthrough to cover that case.
575 if (i + 1 < spec.indexEnd)
576 {
577 // You cannot goto case if the next case is the default
578 static if (i + 1 < A.length)
579 goto case;
580 else
581 goto default;
582 }
583 else
584 break SWITCH;
585 }
586 default:
587 throw new FormatException(
588 text("Positional specifier %", spec.indexStart, '$', spec.spec,
589 " index exceeds ", A.length));
590 }
591 }
592 return currentArg;
593 }
594
595 ///
596 @safe unittest
597 {
598 assert(format("%,d", 1000) == "1,000");
599 assert(format("%,f", 1234567.891011) == "1,234,567.891,011");
600 assert(format("%,?d", '?', 1000) == "1?000");
601 assert(format("%,1d", 1000) == "1,0,0,0", format("%,1d", 1000));
602 assert(format("%,*d", 4, -12345) == "-1,2345");
603 assert(format("%,*?d", 4, '_', -12345) == "-1_2345");
604 assert(format("%,6?d", '_', -12345678) == "-12_345678");
605 assert(format("%12,3.3f", 1234.5678) == " 1,234.568", "'" ~
606 format("%12,3.3f", 1234.5678) ~ "'");
607 }
608
609 @safe pure unittest
610 {
611 import std.array;
612 auto w = appender!string();
613 formattedWrite(w, "%s %d", "@safe/pure", 42);
614 assert(w.data == "@safe/pure 42");
615 }
616
617 /**
618 Reads characters from input range $(D r), converts them according
619 to $(D fmt), and writes them to $(D args).
620
621 Params:
622 r = The range to read from.
623 fmt = The format of the data to read.
624 args = The drain of the data read.
625
626 Returns:
627
628 On success, the function returns the number of variables filled. This count
629 can match the expected number of readings or fewer, even zero, if a
630 matching failure happens.
631
632 Throws:
633 An `Exception` if `S.length == 0` and `fmt` has format specifiers.
634 */
635 uint formattedRead(alias fmt, R, S...)(ref R r, auto ref S args)
636 if (isSomeString!(typeof(fmt)))
637 {
638 alias e = checkFormatException!(fmt, S);
639 static assert(!e, e.msg);
640 return .formattedRead(r, fmt, args);
641 }
642
643 /// ditto
formattedRead(R,Char,S...)644 uint formattedRead(R, Char, S...)(ref R r, const(Char)[] fmt, auto ref S args)
645 {
646 import std.typecons : isTuple;
647
648 auto spec = FormatSpec!Char(fmt);
649 static if (!S.length)
650 {
651 spec.readUpToNextSpec(r);
652 enforce(spec.trailing.empty, "Trailing characters in formattedRead format string");
653 return 0;
654 }
655 else
656 {
657 enum hasPointer = isPointer!(typeof(args[0]));
658
659 // The function below accounts for '*' == fields meant to be
660 // read and skipped
661 void skipUnstoredFields()
662 {
663 for (;;)
664 {
665 spec.readUpToNextSpec(r);
666 if (spec.width != spec.DYNAMIC) break;
667 // must skip this field
668 skipData(r, spec);
669 }
670 }
671
672 skipUnstoredFields();
673 if (r.empty)
674 {
675 // Input is empty, nothing to read
676 return 0;
677 }
678 static if (hasPointer)
679 alias A = typeof(*args[0]);
680 else
681 alias A = typeof(args[0]);
682
683 static if (isTuple!A)
684 {
685 foreach (i, T; A.Types)
686 {
687 static if (hasPointer)
688 (*args[0])[i] = unformatValue!(T)(r, spec);
689 else
690 args[0][i] = unformatValue!(T)(r, spec);
691 skipUnstoredFields();
692 }
693 }
694 else
695 {
696 static if (hasPointer)
697 *args[0] = unformatValue!(A)(r, spec);
698 else
699 args[0] = unformatValue!(A)(r, spec);
700 }
701 return 1 + formattedRead(r, spec.trailing, args[1 .. $]);
702 }
703 }
704
705 /// The format string can be checked at compile-time (see $(LREF format) for details):
706 @safe pure unittest
707 {
708 string s = "hello!124:34.5";
709 string a;
710 int b;
711 double c;
712 s.formattedRead!"%s!%s:%s"(a, b, c);
713 assert(a == "hello" && b == 124 && c == 34.5);
714 }
715
716 @safe unittest
717 {
718 import std.math;
719 string s = " 1.2 3.4 ";
720 double x, y, z;
721 assert(formattedRead(s, " %s %s %s ", x, y, z) == 2);
722 assert(s.empty);
723 assert(approxEqual(x, 1.2));
724 assert(approxEqual(y, 3.4));
725 assert(isNaN(z));
726 }
727
728 // for backwards compatibility
729 @system pure unittest
730 {
731 string s = "hello!124:34.5";
732 string a;
733 int b;
734 double c;
735 formattedRead(s, "%s!%s:%s", &a, &b, &c);
736 assert(a == "hello" && b == 124 && c == 34.5);
737
738 // mix pointers and auto-ref
739 s = "world!200:42.25";
740 formattedRead(s, "%s!%s:%s", a, &b, &c);
741 assert(a == "world" && b == 200 && c == 42.25);
742
743 s = "world1!201:42.5";
744 formattedRead(s, "%s!%s:%s", &a, &b, c);
745 assert(a == "world1" && b == 201 && c == 42.5);
746
747 s = "world2!202:42.75";
748 formattedRead(s, "%s!%s:%s", a, b, &c);
749 assert(a == "world2" && b == 202 && c == 42.75);
750 }
751
752 // for backwards compatibility
753 @system pure unittest
754 {
755 import std.math;
756 string s = " 1.2 3.4 ";
757 double x, y, z;
758 assert(formattedRead(s, " %s %s %s ", &x, &y, &z) == 2);
759 assert(s.empty);
760 assert(approxEqual(x, 1.2));
761 assert(approxEqual(y, 3.4));
762 assert(isNaN(z));
763 }
764
765 @system pure unittest
766 {
767 string line;
768
769 bool f1;
770
771 line = "true";
772 formattedRead(line, "%s", &f1);
773 assert(f1);
774
775 line = "TrUE";
776 formattedRead(line, "%s", &f1);
777 assert(f1);
778
779 line = "false";
780 formattedRead(line, "%s", &f1);
781 assert(!f1);
782
783 line = "fALsE";
784 formattedRead(line, "%s", &f1);
785 assert(!f1);
786
787 line = "1";
788 formattedRead(line, "%d", &f1);
789 assert(f1);
790
791 line = "-1";
792 formattedRead(line, "%d", &f1);
793 assert(f1);
794
795 line = "0";
796 formattedRead(line, "%d", &f1);
797 assert(!f1);
798
799 line = "-0";
800 formattedRead(line, "%d", &f1);
801 assert(!f1);
802 }
803
804 @system pure unittest
805 {
806 union B
807 {
808 char[int.sizeof] untyped;
809 int typed;
810 }
811 B b;
812 b.typed = 5;
813 char[] input = b.untyped[];
814 int witness;
815 formattedRead(input, "%r", &witness);
816 assert(witness == b.typed);
817 }
818
819 @system pure unittest
820 {
821 union A
822 {
823 char[float.sizeof] untyped;
824 float typed;
825 }
826 A a;
827 a.typed = 5.5;
828 char[] input = a.untyped[];
829 float witness;
830 formattedRead(input, "%r", &witness);
831 assert(witness == a.typed);
832 }
833
834 @system pure unittest
835 {
836 import std.typecons;
837 char[] line = "1 2".dup;
838 int a, b;
839 formattedRead(line, "%s %s", &a, &b);
840 assert(a == 1 && b == 2);
841
842 line = "10 2 3".dup;
843 formattedRead(line, "%d ", &a);
844 assert(a == 10);
845 assert(line == "2 3");
846
847 Tuple!(int, float) t;
848 line = "1 2.125".dup;
849 formattedRead(line, "%d %g", &t);
850 assert(t[0] == 1 && t[1] == 2.125);
851
852 line = "1 7643 2.125".dup;
853 formattedRead(line, "%s %*u %s", &t);
854 assert(t[0] == 1 && t[1] == 2.125);
855 }
856
857 @system pure unittest
858 {
859 string line;
860
861 char c1, c2;
862
863 line = "abc";
864 formattedRead(line, "%s%c", &c1, &c2);
865 assert(c1 == 'a' && c2 == 'b');
866 assert(line == "c");
867 }
868
869 @system pure unittest
870 {
871 string line;
872
873 line = "[1,2,3]";
874 int[] s1;
875 formattedRead(line, "%s", &s1);
876 assert(s1 == [1,2,3]);
877 }
878
879 @system pure unittest
880 {
881 string line;
882
883 line = "[1,2,3]";
884 int[] s1;
885 formattedRead(line, "[%(%s,%)]", &s1);
886 assert(s1 == [1,2,3]);
887
888 line = `["hello", "world"]`;
889 string[] s2;
890 formattedRead(line, "[%(%s, %)]", &s2);
891 assert(s2 == ["hello", "world"]);
892
893 line = "123 456";
894 int[] s3;
895 formattedRead(line, "%(%s %)", &s3);
896 assert(s3 == [123, 456]);
897
898 line = "h,e,l,l,o; w,o,r,l,d";
899 string[] s4;
900 formattedRead(line, "%(%(%c,%); %)", &s4);
901 assert(s4 == ["hello", "world"]);
902 }
903
904 @system pure unittest
905 {
906 string line;
907
908 int[4] sa1;
909 line = `[1,2,3,4]`;
910 formattedRead(line, "%s", &sa1);
911 assert(sa1 == [1,2,3,4]);
912
913 int[4] sa2;
914 line = `[1,2,3]`;
915 assertThrown(formattedRead(line, "%s", &sa2));
916
917 int[4] sa3;
918 line = `[1,2,3,4,5]`;
919 assertThrown(formattedRead(line, "%s", &sa3));
920 }
921
922 @system pure unittest
923 {
924 string input;
925
926 int[4] sa1;
927 input = `[1,2,3,4]`;
928 formattedRead(input, "[%(%s,%)]", &sa1);
929 assert(sa1 == [1,2,3,4]);
930
931 int[4] sa2;
932 input = `[1,2,3]`;
933 assertThrown(formattedRead(input, "[%(%s,%)]", &sa2));
934 }
935
936 @system pure unittest
937 {
938 string line;
939
940 string s1, s2;
941
942 line = "hello, world";
943 formattedRead(line, "%s", &s1);
944 assert(s1 == "hello, world", s1);
945
946 line = "hello, world;yah";
947 formattedRead(line, "%s;%s", &s1, &s2);
948 assert(s1 == "hello, world", s1);
949 assert(s2 == "yah", s2);
950
951 line = `['h','e','l','l','o']`;
952 string s3;
953 formattedRead(line, "[%(%s,%)]", &s3);
954 assert(s3 == "hello");
955
956 line = `"hello"`;
957 string s4;
958 formattedRead(line, "\"%(%c%)\"", &s4);
959 assert(s4 == "hello");
960 }
961
962 @system pure unittest
963 {
964 string line;
965
966 string[int] aa1;
967 line = `[1:"hello", 2:"world"]`;
968 formattedRead(line, "%s", &aa1);
969 assert(aa1 == [1:"hello", 2:"world"]);
970
971 int[string] aa2;
972 line = `{"hello"=1; "world"=2}`;
973 formattedRead(line, "{%(%s=%s; %)}", &aa2);
974 assert(aa2 == ["hello":1, "world":2]);
975
976 int[string] aa3;
977 line = `{[hello=1]; [world=2]}`;
978 formattedRead(line, "{%([%(%c%)=%s]%|; %)}", &aa3);
979 assert(aa3 == ["hello":1, "world":2]);
980 }
981
982 template FormatSpec(Char)
983 if (!is(Unqual!Char == Char))
984 {
985 alias FormatSpec = FormatSpec!(Unqual!Char);
986 }
987
988 /**
989 * A General handler for $(D printf) style format specifiers. Used for building more
990 * specific formatting functions.
991 */
992 struct FormatSpec(Char)
993 if (is(Unqual!Char == Char))
994 {
995 import std.algorithm.searching : startsWith;
996 import std.ascii : isDigit, isPunctuation, isAlpha;
997 import std.conv : parse, text, to;
998
999 /**
1000 Minimum _width, default $(D 0).
1001 */
1002 int width = 0;
1003
1004 /**
1005 Precision. Its semantics depends on the argument type. For
1006 floating point numbers, _precision dictates the number of
1007 decimals printed.
1008 */
1009 int precision = UNSPECIFIED;
1010
1011 /**
1012 Number of digits printed between _separators.
1013 */
1014 int separators = UNSPECIFIED;
1015
1016 /**
1017 Set to `DYNAMIC` when the separator character is supplied at runtime.
1018 */
1019 int separatorCharPos = UNSPECIFIED;
1020
1021 /**
1022 Character to insert between digits.
1023 */
1024 dchar separatorChar = ',';
1025
1026 /**
1027 Special value for width and precision. $(D DYNAMIC) width or
1028 precision means that they were specified with $(D '*') in the
1029 format string and are passed at runtime through the varargs.
1030 */
1031 enum int DYNAMIC = int.max;
1032
1033 /**
1034 Special value for precision, meaning the format specifier
1035 contained no explicit precision.
1036 */
1037 enum int UNSPECIFIED = DYNAMIC - 1;
1038
1039 /**
1040 The actual format specifier, $(D 's') by default.
1041 */
1042 char spec = 's';
1043
1044 /**
1045 Index of the argument for positional parameters, from $(D 1) to
1046 $(D ubyte.max). ($(D 0) means not used).
1047 */
1048 ubyte indexStart;
1049
1050 /**
1051 Index of the last argument for positional parameter range, from
1052 $(D 1) to $(D ubyte.max). ($(D 0) means not used).
1053 */
1054 ubyte indexEnd;
1055
version(StdDdoc)1056 version (StdDdoc)
1057 {
1058 /**
1059 The format specifier contained a $(D '-') ($(D printf)
1060 compatibility).
1061 */
1062 bool flDash;
1063
1064 /**
1065 The format specifier contained a $(D '0') ($(D printf)
1066 compatibility).
1067 */
1068 bool flZero;
1069
1070 /**
1071 The format specifier contained a $(D ' ') ($(D printf)
1072 compatibility).
1073 */
1074 bool flSpace;
1075
1076 /**
1077 The format specifier contained a $(D '+') ($(D printf)
1078 compatibility).
1079 */
1080 bool flPlus;
1081
1082 /**
1083 The format specifier contained a $(D '#') ($(D printf)
1084 compatibility).
1085 */
1086 bool flHash;
1087
1088 /**
1089 The format specifier contained a $(D ',')
1090 */
1091 bool flSeparator;
1092
1093 // Fake field to allow compilation
1094 ubyte allFlags;
1095 }
1096 else
1097 {
1098 union
1099 {
1100 import std.bitmanip : bitfields;
1101 mixin(bitfields!(
1102 bool, "flDash", 1,
1103 bool, "flZero", 1,
1104 bool, "flSpace", 1,
1105 bool, "flPlus", 1,
1106 bool, "flHash", 1,
1107 bool, "flSeparator", 1,
1108 ubyte, "", 2));
1109 ubyte allFlags;
1110 }
1111 }
1112
1113 /**
1114 In case of a compound format specifier starting with $(D
1115 "%$(LPAREN)") and ending with $(D "%$(RPAREN)"), $(D _nested)
1116 contains the string contained within the two separators.
1117 */
1118 const(Char)[] nested;
1119
1120 /**
1121 In case of a compound format specifier, $(D _sep) contains the
1122 string positioning after $(D "%|").
1123 `sep is null` means no separator else `sep.empty` means 0 length
1124 separator.
1125 */
1126 const(Char)[] sep;
1127
1128 /**
1129 $(D _trailing) contains the rest of the format string.
1130 */
1131 const(Char)[] trailing;
1132
1133 /*
1134 This string is inserted before each sequence (e.g. array)
1135 formatted (by default $(D "[")).
1136 */
1137 enum immutable(Char)[] seqBefore = "[";
1138
1139 /*
1140 This string is inserted after each sequence formatted (by
1141 default $(D "]")).
1142 */
1143 enum immutable(Char)[] seqAfter = "]";
1144
1145 /*
1146 This string is inserted after each element keys of a sequence (by
1147 default $(D ":")).
1148 */
1149 enum immutable(Char)[] keySeparator = ":";
1150
1151 /*
1152 This string is inserted in between elements of a sequence (by
1153 default $(D ", ")).
1154 */
1155 enum immutable(Char)[] seqSeparator = ", ";
1156
1157 /**
1158 Construct a new $(D FormatSpec) using the format string $(D fmt), no
1159 processing is done until needed.
1160 */
1161 this(in Char[] fmt) @safe pure
1162 {
1163 trailing = fmt;
1164 }
1165
1166 bool writeUpToNextSpec(OutputRange)(ref OutputRange writer)
1167 {
1168 if (trailing.empty)
1169 return false;
1170 for (size_t i = 0; i < trailing.length; ++i)
1171 {
1172 if (trailing[i] != '%') continue;
1173 put(writer, trailing[0 .. i]);
1174 trailing = trailing[i .. $];
1175 enforceFmt(trailing.length >= 2, `Unterminated format specifier: "%"`);
1176 trailing = trailing[1 .. $];
1177
1178 if (trailing[0] != '%')
1179 {
1180 // Spec found. Fill up the spec, and bailout
1181 fillUp();
1182 return true;
1183 }
1184 // Doubled! Reset and Keep going
1185 i = 0;
1186 }
1187 // no format spec found
1188 put(writer, trailing);
1189 trailing = null;
1190 return false;
1191 }
1192
1193 @safe unittest
1194 {
1195 import std.array;
1196 auto w = appender!(char[])();
1197 auto f = FormatSpec("abc%sdef%sghi");
1198 f.writeUpToNextSpec(w);
1199 assert(w.data == "abc", w.data);
1200 assert(f.trailing == "def%sghi", text(f.trailing));
1201 f.writeUpToNextSpec(w);
1202 assert(w.data == "abcdef", w.data);
1203 assert(f.trailing == "ghi");
1204 // test with embedded %%s
1205 f = FormatSpec("ab%%cd%%ef%sg%%h%sij");
1206 w.clear();
1207 f.writeUpToNextSpec(w);
1208 assert(w.data == "ab%cd%ef" && f.trailing == "g%%h%sij", w.data);
1209 f.writeUpToNextSpec(w);
1210 assert(w.data == "ab%cd%efg%h" && f.trailing == "ij");
1211 // bug4775
1212 f = FormatSpec("%%%s");
1213 w.clear();
1214 f.writeUpToNextSpec(w);
1215 assert(w.data == "%" && f.trailing == "");
1216 f = FormatSpec("%%%%%s%%");
1217 w.clear();
1218 while (f.writeUpToNextSpec(w)) continue;
1219 assert(w.data == "%%%");
1220
1221 f = FormatSpec("a%%b%%c%");
1222 w.clear();
1223 assertThrown!FormatException(f.writeUpToNextSpec(w));
1224 assert(w.data == "a%b%c" && f.trailing == "%");
1225 }
1226
1227 private void fillUp()
1228 {
1229 // Reset content
1230 if (__ctfe)
1231 {
1232 flDash = false;
1233 flZero = false;
1234 flSpace = false;
1235 flPlus = false;
1236 flHash = false;
1237 flSeparator = false;
1238 }
1239 else
1240 {
1241 allFlags = 0;
1242 }
1243
1244 width = 0;
1245 precision = UNSPECIFIED;
1246 nested = null;
1247 // Parse the spec (we assume we're past '%' already)
1248 for (size_t i = 0; i < trailing.length; )
1249 {
1250 switch (trailing[i])
1251 {
1252 case '(':
1253 // Embedded format specifier.
1254 auto j = i + 1;
1255 // Get the matching balanced paren
1256 for (uint innerParens;;)
1257 {
1258 enforceFmt(j + 1 < trailing.length,
1259 text("Incorrect format specifier: %", trailing[i .. $]));
1260 if (trailing[j++] != '%')
1261 {
1262 // skip, we're waiting for %( and %)
1263 continue;
1264 }
1265 if (trailing[j] == '-') // for %-(
1266 {
1267 ++j; // skip
1268 enforceFmt(j < trailing.length,
1269 text("Incorrect format specifier: %", trailing[i .. $]));
1270 }
1271 if (trailing[j] == ')')
1272 {
1273 if (innerParens-- == 0) break;
1274 }
1275 else if (trailing[j] == '|')
1276 {
1277 if (innerParens == 0) break;
1278 }
1279 else if (trailing[j] == '(')
1280 {
1281 ++innerParens;
1282 }
1283 }
1284 if (trailing[j] == '|')
1285 {
1286 auto k = j;
1287 for (++j;;)
1288 {
1289 if (trailing[j++] != '%')
1290 continue;
1291 if (trailing[j] == '%')
1292 ++j;
1293 else if (trailing[j] == ')')
1294 break;
1295 else
1296 throw new Exception(
1297 text("Incorrect format specifier: %",
1298 trailing[j .. $]));
1299 }
1300 nested = trailing[i + 1 .. k - 1];
1301 sep = trailing[k + 1 .. j - 1];
1302 }
1303 else
1304 {
1305 nested = trailing[i + 1 .. j - 1];
1306 sep = null; // no separator
1307 }
1308 //this = FormatSpec(innerTrailingSpec);
1309 spec = '(';
1310 // We practically found the format specifier
1311 trailing = trailing[j + 1 .. $];
1312 return;
1313 case '-': flDash = true; ++i; break;
1314 case '+': flPlus = true; ++i; break;
1315 case '#': flHash = true; ++i; break;
1316 case '0': flZero = true; ++i; break;
1317 case ' ': flSpace = true; ++i; break;
1318 case '*':
1319 if (isDigit(trailing[++i]))
1320 {
1321 // a '*' followed by digits and '$' is a
1322 // positional format
1323 trailing = trailing[1 .. $];
1324 width = -parse!(typeof(width))(trailing);
1325 i = 0;
1326 enforceFmt(trailing[i++] == '$',
1327 "$ expected");
1328 }
1329 else
1330 {
1331 // read result
1332 width = DYNAMIC;
1333 }
1334 break;
1335 case '1': .. case '9':
1336 auto tmp = trailing[i .. $];
1337 const widthOrArgIndex = parse!uint(tmp);
1338 enforceFmt(tmp.length,
1339 text("Incorrect format specifier %", trailing[i .. $]));
1340 i = arrayPtrDiff(tmp, trailing);
1341 if (tmp.startsWith('$'))
1342 {
1343 // index of the form %n$
1344 indexEnd = indexStart = to!ubyte(widthOrArgIndex);
1345 ++i;
1346 }
1347 else if (tmp.startsWith(':'))
1348 {
1349 // two indexes of the form %m:n$, or one index of the form %m:$
1350 indexStart = to!ubyte(widthOrArgIndex);
1351 tmp = tmp[1 .. $];
1352 if (tmp.startsWith('$'))
1353 {
1354 indexEnd = indexEnd.max;
1355 }
1356 else
1357 {
1358 indexEnd = parse!(typeof(indexEnd))(tmp);
1359 }
1360 i = arrayPtrDiff(tmp, trailing);
1361 enforceFmt(trailing[i++] == '$',
1362 "$ expected");
1363 }
1364 else
1365 {
1366 // width
1367 width = to!int(widthOrArgIndex);
1368 }
1369 break;
1370 case ',':
1371 // Precision
1372 ++i;
1373 flSeparator = true;
1374
1375 if (trailing[i] == '*')
1376 {
1377 ++i;
1378 // read result
1379 separators = DYNAMIC;
1380 }
1381 else if (isDigit(trailing[i]))
1382 {
1383 auto tmp = trailing[i .. $];
1384 separators = parse!int(tmp);
1385 i = arrayPtrDiff(tmp, trailing);
1386 }
1387 else
1388 {
1389 // "," was specified, but nothing after it
1390 separators = 3;
1391 }
1392
1393 if (trailing[i] == '?')
1394 {
1395 separatorCharPos = DYNAMIC;
1396 ++i;
1397 }
1398
1399 break;
1400 case '.':
1401 // Precision
1402 if (trailing[++i] == '*')
1403 {
1404 if (isDigit(trailing[++i]))
1405 {
1406 // a '.*' followed by digits and '$' is a
1407 // positional precision
1408 trailing = trailing[i .. $];
1409 i = 0;
1410 precision = -parse!int(trailing);
1411 enforceFmt(trailing[i++] == '$',
1412 "$ expected");
1413 }
1414 else
1415 {
1416 // read result
1417 precision = DYNAMIC;
1418 }
1419 }
1420 else if (trailing[i] == '-')
1421 {
1422 // negative precision, as good as 0
1423 precision = 0;
1424 auto tmp = trailing[i .. $];
1425 parse!int(tmp); // skip digits
1426 i = arrayPtrDiff(tmp, trailing);
1427 }
1428 else if (isDigit(trailing[i]))
1429 {
1430 auto tmp = trailing[i .. $];
1431 precision = parse!int(tmp);
1432 i = arrayPtrDiff(tmp, trailing);
1433 }
1434 else
1435 {
1436 // "." was specified, but nothing after it
1437 precision = 0;
1438 }
1439 break;
1440 default:
1441 // this is the format char
1442 spec = cast(char) trailing[i++];
1443 trailing = trailing[i .. $];
1444 return;
1445 } // end switch
1446 } // end for
1447 throw new Exception(text("Incorrect format specifier: ", trailing));
1448 }
1449
1450 //--------------------------------------------------------------------------
1451 private bool readUpToNextSpec(R)(ref R r)
1452 {
1453 import std.ascii : isLower, isWhite;
1454 import std.utf : stride;
1455
1456 // Reset content
1457 if (__ctfe)
1458 {
1459 flDash = false;
1460 flZero = false;
1461 flSpace = false;
1462 flPlus = false;
1463 flHash = false;
1464 flSeparator = false;
1465 }
1466 else
1467 {
1468 allFlags = 0;
1469 }
1470 width = 0;
1471 precision = UNSPECIFIED;
1472 nested = null;
1473 // Parse the spec
1474 while (trailing.length)
1475 {
1476 const c = trailing[0];
1477 if (c == '%' && trailing.length > 1)
1478 {
1479 const c2 = trailing[1];
1480 if (c2 == '%')
1481 {
1482 assert(!r.empty);
1483 // Require a '%'
1484 if (r.front != '%') break;
1485 trailing = trailing[2 .. $];
1486 r.popFront();
1487 }
1488 else
1489 {
1490 enforce(isLower(c2) || c2 == '*' ||
1491 c2 == '(',
1492 text("'%", c2,
1493 "' not supported with formatted read"));
1494 trailing = trailing[1 .. $];
1495 fillUp();
1496 return true;
1497 }
1498 }
1499 else
1500 {
1501 if (c == ' ')
1502 {
1503 while (!r.empty && isWhite(r.front)) r.popFront();
1504 //r = std.algorithm.find!(not!(isWhite))(r);
1505 }
1506 else
1507 {
1508 enforce(!r.empty,
1509 text("parseToFormatSpec: Cannot find character '",
1510 c, "' in the input string."));
1511 if (r.front != trailing.front) break;
1512 r.popFront();
1513 }
1514 trailing = trailing[stride(trailing, 0) .. $];
1515 }
1516 }
1517 return false;
1518 }
1519
1520 private string getCurFmtStr() const
1521 {
1522 import std.array : appender;
1523 auto w = appender!string();
1524 auto f = FormatSpec!Char("%s"); // for stringnize
1525
1526 put(w, '%');
1527 if (indexStart != 0)
1528 {
1529 formatValue(w, indexStart, f);
1530 put(w, '$');
1531 }
1532 if (flDash) put(w, '-');
1533 if (flZero) put(w, '0');
1534 if (flSpace) put(w, ' ');
1535 if (flPlus) put(w, '+');
1536 if (flHash) put(w, '#');
1537 if (flSeparator) put(w, ',');
1538 if (width != 0)
1539 formatValue(w, width, f);
1540 if (precision != FormatSpec!Char.UNSPECIFIED)
1541 {
1542 put(w, '.');
1543 formatValue(w, precision, f);
1544 }
1545 put(w, spec);
1546 return w.data;
1547 }
1548
1549 @safe unittest
1550 {
1551 // issue 5237
1552 import std.array;
1553 auto w = appender!string();
1554 auto f = FormatSpec!char("%.16f");
1555 f.writeUpToNextSpec(w); // dummy eating
1556 assert(f.spec == 'f');
1557 auto fmt = f.getCurFmtStr();
1558 assert(fmt == "%.16f");
1559 }
1560
1561 private const(Char)[] headUpToNextSpec()
1562 {
1563 import std.array : appender;
1564 auto w = appender!(typeof(return))();
1565 auto tr = trailing;
1566
1567 while (tr.length)
1568 {
1569 if (tr[0] == '%')
1570 {
1571 if (tr.length > 1 && tr[1] == '%')
1572 {
1573 tr = tr[2 .. $];
1574 w.put('%');
1575 }
1576 else
1577 break;
1578 }
1579 else
1580 {
1581 w.put(tr.front);
1582 tr.popFront();
1583 }
1584 }
1585 return w.data;
1586 }
1587
1588 string toString()
1589 {
1590 return text("address = ", cast(void*) &this,
1591 "\nwidth = ", width,
1592 "\nprecision = ", precision,
1593 "\nspec = ", spec,
1594 "\nindexStart = ", indexStart,
1595 "\nindexEnd = ", indexEnd,
1596 "\nflDash = ", flDash,
1597 "\nflZero = ", flZero,
1598 "\nflSpace = ", flSpace,
1599 "\nflPlus = ", flPlus,
1600 "\nflHash = ", flHash,
1601 "\nflSeparator = ", flSeparator,
1602 "\nnested = ", nested,
1603 "\ntrailing = ", trailing, "\n");
1604 }
1605 }
1606
1607 ///
1608 @safe pure unittest
1609 {
1610 import std.array;
1611 auto a = appender!(string)();
1612 auto fmt = "Number: %2.4e\nString: %s";
1613 auto f = FormatSpec!char(fmt);
1614
1615 f.writeUpToNextSpec(a);
1616
1617 assert(a.data == "Number: ");
1618 assert(f.trailing == "\nString: %s");
1619 assert(f.spec == 'e');
1620 assert(f.width == 2);
1621 assert(f.precision == 4);
1622
1623 f.writeUpToNextSpec(a);
1624
1625 assert(a.data == "Number: \nString: ");
1626 assert(f.trailing == "");
1627 assert(f.spec == 's');
1628 }
1629
1630 // Issue 14059
1631 @safe unittest
1632 {
1633 import std.array : appender;
1634 auto a = appender!(string)();
1635
1636 auto f = FormatSpec!char("%-(%s%"); // %)")
1637 assertThrown(f.writeUpToNextSpec(a));
1638
1639 f = FormatSpec!char("%(%-"); // %)")
1640 assertThrown(f.writeUpToNextSpec(a));
1641 }
1642
1643 @safe unittest
1644 {
1645 import std.array : appender;
1646 auto a = appender!(string)();
1647
1648 auto f = FormatSpec!char("%,d");
1649 f.writeUpToNextSpec(a);
1650
1651 assert(f.spec == 'd', format("%s", f.spec));
1652 assert(f.precision == FormatSpec!char.UNSPECIFIED);
1653 assert(f.separators == 3);
1654
1655 f = FormatSpec!char("%5,10f");
1656 f.writeUpToNextSpec(a);
1657 assert(f.spec == 'f', format("%s", f.spec));
1658 assert(f.separators == 10);
1659 assert(f.width == 5);
1660
1661 f = FormatSpec!char("%5,10.4f");
1662 f.writeUpToNextSpec(a);
1663 assert(f.spec == 'f', format("%s", f.spec));
1664 assert(f.separators == 10);
1665 assert(f.width == 5);
1666 assert(f.precision == 4);
1667 }
1668
1669 /**
1670 Helper function that returns a $(D FormatSpec) for a single specifier given
1671 in $(D fmt).
1672
1673 Params:
1674 fmt = A format specifier.
1675
1676 Returns:
1677 A $(D FormatSpec) with the specifier parsed.
1678 Throws:
1679 An `Exception` when more than one specifier is given or the specifier
1680 is malformed.
1681 */
1682 FormatSpec!Char singleSpec(Char)(Char[] fmt)
1683 {
1684 import std.conv : text;
1685 enforce(fmt.length >= 2, "fmt must be at least 2 characters long");
1686 enforce(fmt.front == '%', "fmt must start with a '%' character");
1687
1688 static struct DummyOutputRange {
1689 void put(C)(C[] buf) {} // eat elements
1690 }
1691 auto a = DummyOutputRange();
1692 auto spec = FormatSpec!Char(fmt);
1693 //dummy write
1694 spec.writeUpToNextSpec(a);
1695
1696 enforce(spec.trailing.empty,
1697 text("Trailing characters in fmt string: '", spec.trailing));
1698
1699 return spec;
1700 }
1701
1702 ///
1703 @safe pure unittest
1704 {
1705 import std.exception : assertThrown;
1706 auto spec = singleSpec("%2.3e");
1707
1708 assert(spec.trailing == "");
1709 assert(spec.spec == 'e');
1710 assert(spec.width == 2);
1711 assert(spec.precision == 3);
1712
1713 assertThrown(singleSpec(""));
1714 assertThrown(singleSpec("2.3e"));
1715 assertThrown(singleSpec("%2.3eTest"));
1716 }
1717
1718 /**
1719 $(D bool)s are formatted as "true" or "false" with %s and as "1" or
1720 "0" with integral-specific format specs.
1721
1722 Params:
1723 w = The $(D OutputRange) to write to.
1724 obj = The value to write.
1725 f = The $(D FormatSpec) defining how to write the value.
1726 */
1727 void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f)
1728 if (is(BooleanTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
1729 {
1730 BooleanTypeOf!T val = obj;
1731
1732 if (f.spec == 's')
1733 {
1734 string s = val ? "true" : "false";
1735 if (!f.flDash)
1736 {
1737 // right align
1738 if (f.width > s.length)
1739 foreach (i ; 0 .. f.width - s.length) put(w, ' ');
1740 put(w, s);
1741 }
1742 else
1743 {
1744 // left align
1745 put(w, s);
1746 if (f.width > s.length)
1747 foreach (i ; 0 .. f.width - s.length) put(w, ' ');
1748 }
1749 }
1750 else
1751 formatValue(w, cast(int) val, f);
1752 }
1753
1754 ///
1755 @safe pure unittest
1756 {
1757 import std.array : appender;
1758 auto w = appender!string();
1759 auto spec = singleSpec("%s");
1760 formatValue(w, true, spec);
1761
1762 assert(w.data == "true");
1763 }
1764
1765 @safe pure unittest
1766 {
1767 assertCTFEable!(
1768 {
1769 formatTest( false, "false" );
1770 formatTest( true, "true" );
1771 });
1772 }
1773 @system unittest
1774 {
1775 class C1 { bool val; alias val this; this(bool v){ val = v; } }
1776 class C2 { bool val; alias val this; this(bool v){ val = v; }
1777 override string toString() const { return "C"; } }
1778 formatTest( new C1(false), "false" );
1779 formatTest( new C1(true), "true" );
1780 formatTest( new C2(false), "C" );
1781 formatTest( new C2(true), "C" );
1782
1783 struct S1 { bool val; alias val this; }
1784 struct S2 { bool val; alias val this;
1785 string toString() const { return "S"; } }
1786 formatTest( S1(false), "false" );
1787 formatTest( S1(true), "true" );
1788 formatTest( S2(false), "S" );
1789 formatTest( S2(true), "S" );
1790 }
1791
1792 @safe pure unittest
1793 {
1794 string t1 = format("[%6s] [%6s] [%-6s]", true, false, true);
1795 assert(t1 == "[ true] [ false] [true ]");
1796
1797 string t2 = format("[%3s] [%-2s]", true, false);
1798 assert(t2 == "[true] [false]");
1799 }
1800
1801 /**
1802 $(D null) literal is formatted as $(D "null").
1803
1804 Params:
1805 w = The $(D OutputRange) to write to.
1806 obj = The value to write.
1807 f = The $(D FormatSpec) defining how to write the value.
1808 */
1809 void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f)
1810 if (is(Unqual!T == typeof(null)) && !is(T == enum) && !hasToString!(T, Char))
1811 {
1812 enforceFmt(f.spec == 's',
1813 "null literal cannot match %" ~ f.spec);
1814
1815 put(w, "null");
1816 }
1817
1818 ///
1819 @safe pure unittest
1820 {
1821 import std.array : appender;
1822 auto w = appender!string();
1823 auto spec = singleSpec("%s");
1824 formatValue(w, null, spec);
1825
1826 assert(w.data == "null");
1827 }
1828
1829 @safe pure unittest
1830 {
1831 assert(collectExceptionMsg!FormatException(format("%p", null)).back == 'p');
1832
1833 assertCTFEable!(
1834 {
1835 formatTest( null, "null" );
1836 });
1837 }
1838
1839 /**
1840 Integrals are formatted like $(D printf) does.
1841
1842 Params:
1843 w = The $(D OutputRange) to write to.
1844 obj = The value to write.
1845 f = The $(D FormatSpec) defining how to write the value.
1846 */
1847 void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f)
1848 if (is(IntegralTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
1849 {
1850 alias U = IntegralTypeOf!T;
1851 U val = obj; // Extracting alias this may be impure/system/may-throw
1852
1853 if (f.spec == 'r')
1854 {
1855 // raw write, skip all else and write the thing
1856 auto raw = (ref val)@trusted{
1857 return (cast(const char*) &val)[0 .. val.sizeof];
1858 }(val);
1859 if (needToSwapEndianess(f))
1860 {
1861 foreach_reverse (c; raw)
1862 put(w, c);
1863 }
1864 else
1865 {
1866 foreach (c; raw)
1867 put(w, c);
1868 }
1869 return;
1870 }
1871
1872 immutable uint base =
1873 f.spec == 'x' || f.spec == 'X' ? 16 :
1874 f.spec == 'o' ? 8 :
1875 f.spec == 'b' ? 2 :
1876 f.spec == 's' || f.spec == 'd' || f.spec == 'u' ? 10 :
1877 0;
1878 enforceFmt(base > 0,
1879 "incompatible format character for integral argument: %" ~ f.spec);
1880
1881 // Forward on to formatIntegral to handle both U and const(U)
1882 // Saves duplication of code for both versions.
1883 static if (is(ucent) && (is(U == cent) || is(U == ucent)))
1884 alias C = U;
1885 else static if (isSigned!U)
1886 alias C = long;
1887 else
1888 alias C = ulong;
1889 formatIntegral(w, cast(C) val, f, base, Unsigned!U.max);
1890 }
1891
1892 ///
1893 @safe pure unittest
1894 {
1895 import std.array : appender;
1896 auto w = appender!string();
1897 auto spec = singleSpec("%d");
1898 formatValue(w, 1337, spec);
1899
1900 assert(w.data == "1337");
1901 }
1902
1903 private void formatIntegral(Writer, T, Char)(ref Writer w, const(T) val, const ref FormatSpec!Char fs,
1904 uint base, ulong mask)
1905 {
1906 T arg = val;
1907
1908 immutable negative = (base == 10 && arg < 0);
1909 if (negative)
1910 {
1911 arg = -arg;
1912 }
1913
1914 // All unsigned integral types should fit in ulong.
1915 static if (is(ucent) && is(typeof(arg) == ucent))
1916 formatUnsigned(w, (cast(ucent) arg) & mask, fs, base, negative);
1917 else
1918 formatUnsigned(w, (cast(ulong) arg) & mask, fs, base, negative);
1919 }
1920
1921 private void formatUnsigned(Writer, T, Char)
1922 (ref Writer w, T arg, const ref FormatSpec!Char fs, uint base, bool negative)
1923 {
1924 /* Write string:
1925 * leftpad prefix1 prefix2 zerofill digits rightpad
1926 */
1927
1928 /* Convert arg to digits[].
1929 * Note that 0 becomes an empty digits[]
1930 */
1931 char[64] buffer = void; // 64 bits in base 2 at most
1932 char[] digits;
1933 if (arg < base && base <= 10 && arg)
1934 {
1935 // Most numbers are a single digit - avoid expensive divide
1936 buffer[0] = cast(char)(arg + '0');
1937 digits = buffer[0 .. 1];
1938 }
1939 else
1940 {
1941 size_t i = buffer.length;
1942 while (arg)
1943 {
1944 --i;
1945 char c = cast(char) (arg % base);
1946 arg /= base;
1947 if (c < 10)
1948 buffer[i] = cast(char)(c + '0');
1949 else
1950 buffer[i] = cast(char)(c + (fs.spec == 'x' ? 'a' - 10 : 'A' - 10));
1951 }
1952 digits = buffer[i .. $]; // got the digits without the sign
1953 }
1954
1955
1956 immutable precision = (fs.precision == fs.UNSPECIFIED) ? 1 : fs.precision;
1957
1958 char padChar = 0;
1959 if (!fs.flDash)
1960 {
1961 padChar = (fs.flZero && fs.precision == fs.UNSPECIFIED) ? '0' : ' ';
1962 }
1963
1964 // Compute prefix1 and prefix2
1965 char prefix1 = 0;
1966 char prefix2 = 0;
1967 if (base == 10)
1968 {
1969 if (negative)
1970 prefix1 = '-';
1971 else if (fs.flPlus)
1972 prefix1 = '+';
1973 else if (fs.flSpace)
1974 prefix1 = ' ';
1975 }
1976 else if (base == 16 && fs.flHash && digits.length)
1977 {
1978 prefix1 = '0';
1979 prefix2 = fs.spec == 'x' ? 'x' : 'X';
1980 }
1981 // adjust precision to print a '0' for octal if alternate format is on
1982 else if (base == 8 && fs.flHash &&
1983 (precision <= 1 || precision <= digits.length) && // too low precision
1984 digits.length > 0)
1985 prefix1 = '0';
1986
1987 size_t zerofill = precision > digits.length ? precision - digits.length : 0;
1988 size_t leftpad = 0;
1989 size_t rightpad = 0;
1990
1991 immutable ptrdiff_t spacesToPrint =
1992 fs.width - (
1993 (prefix1 != 0)
1994 + (prefix2 != 0)
1995 + zerofill
1996 + digits.length
1997 + ((fs.flSeparator != 0) * (digits.length / fs.separators))
1998 );
1999 if (spacesToPrint > 0) // need to do some padding
2000 {
2001 if (padChar == '0')
2002 zerofill += spacesToPrint;
2003 else if (padChar)
2004 leftpad = spacesToPrint;
2005 else
2006 rightpad = spacesToPrint;
2007 }
2008
2009 // Print
2010 foreach (i ; 0 .. leftpad)
2011 put(w, ' ');
2012
2013 if (prefix1) put(w, prefix1);
2014 if (prefix2) put(w, prefix2);
2015
2016 foreach (i ; 0 .. zerofill)
2017 put(w, '0');
2018
2019 if (fs.flSeparator)
2020 {
2021 for (size_t j = 0; j < digits.length; ++j)
2022 {
2023 if (j != 0 && (digits.length - j) % fs.separators == 0)
2024 {
2025 put(w, fs.separatorChar);
2026 }
2027 put(w, digits[j]);
2028 }
2029 }
2030 else
2031 {
2032 put(w, digits);
2033 }
2034
2035 foreach (i ; 0 .. rightpad)
2036 put(w, ' ');
2037 }
2038
2039 @safe pure unittest
2040 {
2041 assert(collectExceptionMsg!FormatException(format("%c", 5)).back == 'c');
2042
2043 assertCTFEable!(
2044 {
2045 formatTest(9, "9");
2046 formatTest( 10, "10" );
2047 });
2048 }
2049
2050 @system unittest
2051 {
2052 class C1 { long val; alias val this; this(long v){ val = v; } }
2053 class C2 { long val; alias val this; this(long v){ val = v; }
2054 override string toString() const { return "C"; } }
2055 formatTest( new C1(10), "10" );
2056 formatTest( new C2(10), "C" );
2057
2058 struct S1 { long val; alias val this; }
2059 struct S2 { long val; alias val this;
2060 string toString() const { return "S"; } }
2061 formatTest( S1(10), "10" );
2062 formatTest( S2(10), "S" );
2063 }
2064
2065 // bugzilla 9117
2066 @safe unittest
2067 {
2068 static struct Frop {}
2069
2070 static struct Foo
2071 {
2072 int n = 0;
2073 alias n this;
2074 T opCast(T) () if (is(T == Frop))
2075 {
2076 return Frop();
2077 }
2078 string toString()
2079 {
2080 return "Foo";
2081 }
2082 }
2083
2084 static struct Bar
2085 {
2086 Foo foo;
2087 alias foo this;
2088 string toString()
2089 {
2090 return "Bar";
2091 }
2092 }
2093
2094 const(char)[] result;
2095 void put(const char[] s){ result ~= s; }
2096
2097 Foo foo;
2098 formattedWrite(&put, "%s", foo); // OK
2099 assert(result == "Foo");
2100
2101 result = null;
2102
2103 Bar bar;
2104 formattedWrite(&put, "%s", bar); // NG
2105 assert(result == "Bar");
2106
2107 result = null;
2108
2109 int i = 9;
2110 formattedWrite(&put, "%s", 9);
2111 assert(result == "9");
2112 }
2113
2114 private enum ctfpMessage = "Cannot format floating point types at compile-time";
2115
2116 /**
2117 Floating-point values are formatted like $(D printf) does.
2118
2119 Params:
2120 w = The $(D OutputRange) to write to.
2121 obj = The value to write.
2122 f = The $(D FormatSpec) defining how to write the value.
2123 */
2124 void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f)
2125 if (is(FloatingPointTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
2126 {
2127 import std.algorithm.comparison : min;
2128 import std.algorithm.searching : find;
2129 import std.string : indexOf, indexOfAny, indexOfNeither;
2130
2131 FormatSpec!Char fs = f; // fs is copy for change its values.
2132 FloatingPointTypeOf!T val = obj;
2133
2134 if (fs.spec == 'r')
2135 {
2136 // raw write, skip all else and write the thing
2137 auto raw = (ref val)@trusted{
2138 return (cast(const char*) &val)[0 .. val.sizeof];
2139 }(val);
2140 if (needToSwapEndianess(f))
2141 {
2142 foreach_reverse (c; raw)
2143 put(w, c);
2144 }
2145 else
2146 {
2147 foreach (c; raw)
2148 put(w, c);
2149 }
2150 return;
2151 }
2152 enforceFmt(find("fgFGaAeEs", fs.spec).length,
2153 "incompatible format character for floating point argument: %" ~ fs.spec);
2154 enforceFmt(!__ctfe, ctfpMessage);
2155
2156 version (CRuntime_Microsoft)
2157 {
2158 import std.math : isNaN, isInfinity;
2159 immutable double tval = val; // convert early to get "inf" in case of overflow
2160 string s;
2161 if (isNaN(tval))
2162 s = "nan"; // snprintf writes 1.#QNAN
2163 else if (isInfinity(tval))
2164 s = val >= 0 ? "inf" : "-inf"; // snprintf writes 1.#INF
2165
2166 if (s.length > 0)
2167 {
2168 version (none)
2169 {
2170 return formatValue(w, s, f);
2171 }
2172 else // FIXME:workaround
2173 {
2174 s = s[0 .. f.precision < $ ? f.precision : $];
2175 if (!f.flDash)
2176 {
2177 // right align
2178 if (f.width > s.length)
2179 foreach (j ; 0 .. f.width - s.length) put(w, ' ');
2180 put(w, s);
2181 }
2182 else
2183 {
2184 // left align
2185 put(w, s);
2186 if (f.width > s.length)
2187 foreach (j ; 0 .. f.width - s.length) put(w, ' ');
2188 }
2189 return;
2190 }
2191 }
2192 }
2193 else
2194 alias tval = val;
2195 if (fs.spec == 's') fs.spec = 'g';
2196 char[1 /*%*/ + 5 /*flags*/ + 3 /*width.prec*/ + 2 /*format*/
2197 + 1 /*\0*/] sprintfSpec = void;
2198 sprintfSpec[0] = '%';
2199 uint i = 1;
2200 if (fs.flDash) sprintfSpec[i++] = '-';
2201 if (fs.flPlus) sprintfSpec[i++] = '+';
2202 if (fs.flZero) sprintfSpec[i++] = '0';
2203 if (fs.flSpace) sprintfSpec[i++] = ' ';
2204 if (fs.flHash) sprintfSpec[i++] = '#';
2205 sprintfSpec[i .. i + 3] = "*.*";
2206 i += 3;
2207 if (is(Unqual!(typeof(val)) == real)) sprintfSpec[i++] = 'L';
2208 sprintfSpec[i++] = fs.spec;
2209 sprintfSpec[i] = 0;
2210 //printf("format: '%s'; geeba: %g\n", sprintfSpec.ptr, val);
2211 char[512] buf = void;
2212
2213 immutable n = ()@trusted{
2214 import core.stdc.stdio : snprintf;
2215 return snprintf(buf.ptr, buf.length,
2216 sprintfSpec.ptr,
2217 fs.width,
2218 // negative precision is same as no precision specified
2219 fs.precision == fs.UNSPECIFIED ? -1 : fs.precision,
2220 tval);
2221 }();
2222
2223 enforceFmt(n >= 0,
2224 "floating point formatting failure");
2225
2226 auto len = min(n, buf.length-1);
2227 ptrdiff_t dot = buf[0 .. len].indexOf('.');
2228 if (fs.flSeparator && dot != -1)
2229 {
2230 ptrdiff_t firstDigit = buf[0 .. len].indexOfAny("0123456789");
2231 ptrdiff_t ePos = buf[0 .. len].indexOf('e');
2232 size_t j;
2233
2234 ptrdiff_t firstLen = dot - firstDigit;
2235
2236 size_t separatorScoreCnt = firstLen / fs.separators;
2237
2238 size_t afterDotIdx;
2239 if (ePos != -1)
2240 {
2241 afterDotIdx = ePos;
2242 }
2243 else
2244 {
2245 afterDotIdx = len;
2246 }
2247
2248 if (dot != -1)
2249 {
2250 ptrdiff_t mantissaLen = afterDotIdx - (dot + 1);
2251 separatorScoreCnt += (mantissaLen > 0) ? (mantissaLen - 1) / fs.separators : 0;
2252 }
2253
2254 // plus, minus prefix
2255 ptrdiff_t digitsBegin = buf[0 .. separatorScoreCnt].indexOfNeither(" ");
2256 if (digitsBegin == -1)
2257 {
2258 digitsBegin = separatorScoreCnt;
2259 }
2260 put(w, buf[digitsBegin .. firstDigit]);
2261
2262 // digits until dot with separator
2263 for (j = 0; j < firstLen; ++j)
2264 {
2265 if (j > 0 && (firstLen - j) % fs.separators == 0)
2266 {
2267 put(w, fs.separatorChar);
2268 }
2269 put(w, buf[j + firstDigit]);
2270 }
2271 put(w, '.');
2272
2273 // digits after dot
2274 for (j = dot + 1; j < afterDotIdx; ++j)
2275 {
2276 auto realJ = (j - (dot + 1));
2277 if (realJ != 0 && realJ % fs.separators == 0)
2278 {
2279 put(w, fs.separatorChar);
2280 }
2281 put(w, buf[j]);
2282 }
2283
2284 // rest
2285 if (ePos != -1)
2286 {
2287 put(w, buf[afterDotIdx .. len]);
2288 }
2289 }
2290 else
2291 {
2292 put(w, buf[0 .. len]);
2293 }
2294 }
2295
2296 ///
2297 @safe unittest
2298 {
2299 import std.array : appender;
2300 auto w = appender!string();
2301 auto spec = singleSpec("%.1f");
2302 formatValue(w, 1337.7, spec);
2303
2304 assert(w.data == "1337.7");
2305 }
2306
2307 @safe /*pure*/ unittest // formatting floating point values is now impure
2308 {
2309 import std.conv : to;
2310
2311 assert(collectExceptionMsg!FormatException(format("%d", 5.1)).back == 'd');
2312
2313 foreach (T; AliasSeq!(float, double, real))
2314 {
2315 formatTest( to!( T)(5.5), "5.5" );
2316 formatTest( to!( const T)(5.5), "5.5" );
2317 formatTest( to!(immutable T)(5.5), "5.5" );
2318
2319 formatTest( T.nan, "nan" );
2320 }
2321 }
2322
2323 @system unittest
2324 {
2325 formatTest( 2.25, "2.25" );
2326
2327 class C1 { double val; alias val this; this(double v){ val = v; } }
2328 class C2 { double val; alias val this; this(double v){ val = v; }
2329 override string toString() const { return "C"; } }
2330 formatTest( new C1(2.25), "2.25" );
2331 formatTest( new C2(2.25), "C" );
2332
2333 struct S1 { double val; alias val this; }
2334 struct S2 { double val; alias val this;
2335 string toString() const { return "S"; } }
2336 formatTest( S1(2.25), "2.25" );
2337 formatTest( S2(2.25), "S" );
2338 }
2339
2340 /*
2341 Formatting a $(D creal) is deprecated but still kept around for a while.
2342
2343 Params:
2344 w = The $(D OutputRange) to write to.
2345 obj = The value to write.
2346 f = The $(D FormatSpec) defining how to write the value.
2347 */
2348 void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f)
2349 if (is(Unqual!T : creal) && !is(T == enum) && !hasToString!(T, Char))
2350 {
2351 immutable creal val = obj;
2352
2353 formatValue(w, val.re, f);
2354 if (val.im >= 0)
2355 {
2356 put(w, '+');
2357 }
2358 formatValue(w, val.im, f);
2359 put(w, 'i');
2360 }
2361
2362 @safe /*pure*/ unittest // formatting floating point values is now impure
2363 {
2364 import std.conv : to;
2365 foreach (T; AliasSeq!(cfloat, cdouble, creal))
2366 {
2367 formatTest( to!( T)(1 + 1i), "1+1i" );
2368 formatTest( to!( const T)(1 + 1i), "1+1i" );
2369 formatTest( to!(immutable T)(1 + 1i), "1+1i" );
2370 }
2371 foreach (T; AliasSeq!(cfloat, cdouble, creal))
2372 {
2373 formatTest( to!( T)(0 - 3i), "0-3i" );
2374 formatTest( to!( const T)(0 - 3i), "0-3i" );
2375 formatTest( to!(immutable T)(0 - 3i), "0-3i" );
2376 }
2377 }
2378
2379 @system unittest
2380 {
2381 formatTest( 3+2.25i, "3+2.25i" );
2382
2383 class C1 { cdouble val; alias val this; this(cdouble v){ val = v; } }
2384 class C2 { cdouble val; alias val this; this(cdouble v){ val = v; }
2385 override string toString() const { return "C"; } }
2386 formatTest( new C1(3+2.25i), "3+2.25i" );
2387 formatTest( new C2(3+2.25i), "C" );
2388
2389 struct S1 { cdouble val; alias val this; }
2390 struct S2 { cdouble val; alias val this;
2391 string toString() const { return "S"; } }
2392 formatTest( S1(3+2.25i), "3+2.25i" );
2393 formatTest( S2(3+2.25i), "S" );
2394 }
2395
2396 /*
2397 Formatting an $(D ireal) is deprecated but still kept around for a while.
2398
2399 Params:
2400 w = The $(D OutputRange) to write to.
2401 obj = The value to write.
2402 f = The $(D FormatSpec) defining how to write the value.
2403 */
2404 void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f)
2405 if (is(Unqual!T : ireal) && !is(T == enum) && !hasToString!(T, Char))
2406 {
2407 immutable ireal val = obj;
2408
2409 formatValue(w, val.im, f);
2410 put(w, 'i');
2411 }
2412
2413 @safe /*pure*/ unittest // formatting floating point values is now impure
2414 {
2415 import std.conv : to;
2416 foreach (T; AliasSeq!(ifloat, idouble, ireal))
2417 {
2418 formatTest( to!( T)(1i), "1i" );
2419 formatTest( to!( const T)(1i), "1i" );
2420 formatTest( to!(immutable T)(1i), "1i" );
2421 }
2422 }
2423
2424 @system unittest
2425 {
2426 formatTest( 2.25i, "2.25i" );
2427
2428 class C1 { idouble val; alias val this; this(idouble v){ val = v; } }
2429 class C2 { idouble val; alias val this; this(idouble v){ val = v; }
2430 override string toString() const { return "C"; } }
2431 formatTest( new C1(2.25i), "2.25i" );
2432 formatTest( new C2(2.25i), "C" );
2433
2434 struct S1 { idouble val; alias val this; }
2435 struct S2 { idouble val; alias val this;
2436 string toString() const { return "S"; } }
2437 formatTest( S1(2.25i), "2.25i" );
2438 formatTest( S2(2.25i), "S" );
2439 }
2440
2441 /**
2442 Individual characters ($(D char), $(D wchar), or $(D dchar)) are formatted as
2443 Unicode characters with %s and as integers with integral-specific format
2444 specs.
2445
2446 Params:
2447 w = The $(D OutputRange) to write to.
2448 obj = The value to write.
2449 f = The $(D FormatSpec) defining how to write the value.
2450 */
2451 void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f)
2452 if (is(CharTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
2453 {
2454 CharTypeOf!T val = obj;
2455
2456 if (f.spec == 's' || f.spec == 'c')
2457 {
2458 put(w, val);
2459 }
2460 else
2461 {
2462 alias U = AliasSeq!(ubyte, ushort, uint)[CharTypeOf!T.sizeof/2];
2463 formatValue(w, cast(U) val, f);
2464 }
2465 }
2466
2467 ///
2468 @safe pure unittest
2469 {
2470 import std.array : appender;
2471 auto w = appender!string();
2472 auto spec = singleSpec("%c");
2473 formatValue(w, 'a', spec);
2474
2475 assert(w.data == "a");
2476 }
2477
2478 @safe pure unittest
2479 {
2480 assertCTFEable!(
2481 {
2482 formatTest( 'c', "c" );
2483 });
2484 }
2485
2486 @system unittest
2487 {
2488 class C1 { char val; alias val this; this(char v){ val = v; } }
2489 class C2 { char val; alias val this; this(char v){ val = v; }
2490 override string toString() const { return "C"; } }
2491 formatTest( new C1('c'), "c" );
2492 formatTest( new C2('c'), "C" );
2493
2494 struct S1 { char val; alias val this; }
2495 struct S2 { char val; alias val this;
2496 string toString() const { return "S"; } }
2497 formatTest( S1('c'), "c" );
2498 formatTest( S2('c'), "S" );
2499 }
2500
2501 @safe unittest
2502 {
2503 //Little Endian
2504 formatTest( "%-r", cast( char)'c', ['c' ] );
2505 formatTest( "%-r", cast(wchar)'c', ['c', 0 ] );
2506 formatTest( "%-r", cast(dchar)'c', ['c', 0, 0, 0] );
2507 formatTest( "%-r", '本', ['\x2c', '\x67'] );
2508
2509 //Big Endian
2510 formatTest( "%+r", cast( char)'c', [ 'c'] );
2511 formatTest( "%+r", cast(wchar)'c', [0, 'c'] );
2512 formatTest( "%+r", cast(dchar)'c', [0, 0, 0, 'c'] );
2513 formatTest( "%+r", '本', ['\x67', '\x2c'] );
2514 }
2515
2516 /**
2517 Strings are formatted like $(D printf) does.
2518
2519 Params:
2520 w = The $(D OutputRange) to write to.
2521 obj = The value to write.
2522 f = The $(D FormatSpec) defining how to write the value.
2523 */
2524 void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f)
2525 if (is(StringTypeOf!T) && !is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
2526 {
2527 Unqual!(StringTypeOf!T) val = obj; // for `alias this`, see bug5371
2528 formatRange(w, val, f);
2529 }
2530
2531 ///
2532 @safe pure unittest
2533 {
2534 import std.array : appender;
2535 auto w = appender!string();
2536 auto spec = singleSpec("%s");
2537 formatValue(w, "hello", spec);
2538
2539 assert(w.data == "hello");
2540 }
2541
2542 @safe unittest
2543 {
2544 formatTest( "abc", "abc" );
2545 }
2546
2547 @system unittest
2548 {
2549 // Test for bug 5371 for classes
2550 class C1 { const string var; alias var this; this(string s){ var = s; } }
2551 class C2 { string var; alias var this; this(string s){ var = s; } }
2552 formatTest( new C1("c1"), "c1" );
2553 formatTest( new C2("c2"), "c2" );
2554
2555 // Test for bug 5371 for structs
2556 struct S1 { const string var; alias var this; }
2557 struct S2 { string var; alias var this; }
2558 formatTest( S1("s1"), "s1" );
2559 formatTest( S2("s2"), "s2" );
2560 }
2561
2562 @system unittest
2563 {
2564 class C3 { string val; alias val this; this(string s){ val = s; }
2565 override string toString() const { return "C"; } }
2566 formatTest( new C3("c3"), "C" );
2567
2568 struct S3 { string val; alias val this;
2569 string toString() const { return "S"; } }
2570 formatTest( S3("s3"), "S" );
2571 }
2572
2573 @safe pure unittest
2574 {
2575 //Little Endian
2576 formatTest( "%-r", "ab"c, ['a' , 'b' ] );
2577 formatTest( "%-r", "ab"w, ['a', 0 , 'b', 0 ] );
2578 formatTest( "%-r", "ab"d, ['a', 0, 0, 0, 'b', 0, 0, 0] );
2579 formatTest( "%-r", "日本語"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac', '\xe8', '\xaa', '\x9e'] );
2580 formatTest( "%-r", "日本語"w, ['\xe5', '\x65', '\x2c', '\x67', '\x9e', '\x8a']);
2581 formatTest( "%-r", "日本語"d, ['\xe5', '\x65', '\x00', '\x00', '\x2c', '\x67',
2582 '\x00', '\x00', '\x9e', '\x8a', '\x00', '\x00'] );
2583
2584 //Big Endian
2585 formatTest( "%+r", "ab"c, [ 'a', 'b'] );
2586 formatTest( "%+r", "ab"w, [ 0, 'a', 0, 'b'] );
2587 formatTest( "%+r", "ab"d, [0, 0, 0, 'a', 0, 0, 0, 'b'] );
2588 formatTest( "%+r", "日本語"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac', '\xe8', '\xaa', '\x9e'] );
2589 formatTest( "%+r", "日本語"w, ['\x65', '\xe5', '\x67', '\x2c', '\x8a', '\x9e'] );
2590 formatTest( "%+r", "日本語"d, ['\x00', '\x00', '\x65', '\xe5', '\x00', '\x00',
2591 '\x67', '\x2c', '\x00', '\x00', '\x8a', '\x9e'] );
2592 }
2593
2594 /**
2595 Static-size arrays are formatted as dynamic arrays.
2596
2597 Params:
2598 w = The $(D OutputRange) to write to.
2599 obj = The value to write.
2600 f = The $(D FormatSpec) defining how to write the value.
2601 */
2602 void formatValue(Writer, T, Char)(auto ref Writer w, auto ref T obj, const ref FormatSpec!Char f)
2603 if (is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
2604 {
2605 formatValue(w, obj[], f);
2606 }
2607
2608 ///
2609 @safe pure unittest
2610 {
2611 import std.array : appender;
2612 auto w = appender!string();
2613 auto spec = singleSpec("%s");
2614 char[2] two = ['a', 'b'];
2615 formatValue(w, two, spec);
2616
2617 assert(w.data == "ab");
2618 }
2619
2620 @safe unittest // Test for issue 8310
2621 {
2622 import std.array : appender;
2623 FormatSpec!char f;
2624 auto w = appender!string();
2625
2626 char[2] two = ['a', 'b'];
2627 formatValue(w, two, f);
2628
2629 char[2] getTwo(){ return two; }
2630 formatValue(w, getTwo(), f);
2631 }
2632
2633 /**
2634 Dynamic arrays are formatted as input ranges.
2635
2636 Specializations:
2637 $(UL $(LI $(D void[]) is formatted like $(D ubyte[]).)
2638 $(LI Const array is converted to input range by removing its qualifier.))
2639
2640 Params:
2641 w = The $(D OutputRange) to write to.
2642 obj = The value to write.
2643 f = The $(D FormatSpec) defining how to write the value.
2644 */
2645 void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f)
2646 if (is(DynamicArrayTypeOf!T) && !is(StringTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
2647 {
2648 static if (is(const(ArrayTypeOf!T) == const(void[])))
2649 {
2650 formatValue(w, cast(const ubyte[]) obj, f);
2651 }
2652 else static if (!isInputRange!T)
2653 {
2654 alias U = Unqual!(ArrayTypeOf!T);
2655 static assert(isInputRange!U);
2656 U val = obj;
2657 formatValue(w, val, f);
2658 }
2659 else
2660 {
2661 formatRange(w, obj, f);
2662 }
2663 }
2664
2665 ///
2666 @safe pure unittest
2667 {
2668 import std.array : appender;
2669 auto w = appender!string();
2670 auto spec = singleSpec("%s");
2671 auto two = [1, 2];
2672 formatValue(w, two, spec);
2673
2674 assert(w.data == "[1, 2]");
2675 }
2676
2677 // alias this, input range I/F, and toString()
2678 @system unittest
2679 {
2680 struct S(int flags)
2681 {
2682 int[] arr;
2683 static if (flags & 1)
2684 alias arr this;
2685
2686 static if (flags & 2)
2687 {
2688 @property bool empty() const { return arr.length == 0; }
2689 @property int front() const { return arr[0] * 2; }
2690 void popFront() { arr = arr[1..$]; }
2691 }
2692
2693 static if (flags & 4)
2694 string toString() const { return "S"; }
2695 }
2696 formatTest(S!0b000([0, 1, 2]), "S!0([0, 1, 2])");
2697 formatTest(S!0b001([0, 1, 2]), "[0, 1, 2]"); // Test for bug 7628
2698 formatTest(S!0b010([0, 1, 2]), "[0, 2, 4]");
2699 formatTest(S!0b011([0, 1, 2]), "[0, 2, 4]");
2700 formatTest(S!0b100([0, 1, 2]), "S");
2701 formatTest(S!0b101([0, 1, 2]), "S"); // Test for bug 7628
2702 formatTest(S!0b110([0, 1, 2]), "S");
2703 formatTest(S!0b111([0, 1, 2]), "S");
2704
2705 class C(uint flags)
2706 {
2707 int[] arr;
2708 static if (flags & 1)
2709 alias arr this;
2710
2711 this(int[] a) { arr = a; }
2712
2713 static if (flags & 2)
2714 {
2715 @property bool empty() const { return arr.length == 0; }
2716 @property int front() const { return arr[0] * 2; }
2717 void popFront() { arr = arr[1..$]; }
2718 }
2719
2720 static if (flags & 4)
2721 override string toString() const { return "C"; }
2722 }
2723 formatTest(new C!0b000([0, 1, 2]), (new C!0b000([])).toString());
2724 formatTest(new C!0b001([0, 1, 2]), "[0, 1, 2]"); // Test for bug 7628
2725 formatTest(new C!0b010([0, 1, 2]), "[0, 2, 4]");
2726 formatTest(new C!0b011([0, 1, 2]), "[0, 2, 4]");
2727 formatTest(new C!0b100([0, 1, 2]), "C");
2728 formatTest(new C!0b101([0, 1, 2]), "C"); // Test for bug 7628
2729 formatTest(new C!0b110([0, 1, 2]), "C");
2730 formatTest(new C!0b111([0, 1, 2]), "C");
2731 }
2732
2733 @system unittest
2734 {
2735 // void[]
2736 void[] val0;
2737 formatTest( val0, "[]" );
2738
2739 void[] val = cast(void[]) cast(ubyte[])[1, 2, 3];
2740 formatTest( val, "[1, 2, 3]" );
2741
2742 void[0] sval0 = [];
2743 formatTest( sval0, "[]");
2744
2745 void[3] sval = cast(void[3]) cast(ubyte[3])[1, 2, 3];
2746 formatTest( sval, "[1, 2, 3]" );
2747 }
2748
2749 @safe unittest
2750 {
2751 // const(T[]) -> const(T)[]
2752 const short[] a = [1, 2, 3];
2753 formatTest( a, "[1, 2, 3]" );
2754
2755 struct S { const(int[]) arr; alias arr this; }
2756 auto s = S([1,2,3]);
2757 formatTest( s, "[1, 2, 3]" );
2758 }
2759
2760 @safe unittest
2761 {
2762 // 6640
2763 struct Range
2764 {
2765 @safe:
2766 string value;
2767 @property bool empty() const { return !value.length; }
2768 @property dchar front() const { return value.front; }
2769 void popFront() { value.popFront(); }
2770
2771 @property size_t length() const { return value.length; }
2772 }
2773 immutable table =
2774 [
2775 ["[%s]", "[string]"],
2776 ["[%10s]", "[ string]"],
2777 ["[%-10s]", "[string ]"],
2778 ["[%(%02x %)]", "[73 74 72 69 6e 67]"],
2779 ["[%(%c %)]", "[s t r i n g]"],
2780 ];
2781 foreach (e; table)
2782 {
2783 formatTest(e[0], "string", e[1]);
2784 formatTest(e[0], Range("string"), e[1]);
2785 }
2786 }
2787
2788 @system unittest
2789 {
2790 // string literal from valid UTF sequence is encoding free.
2791 foreach (StrType; AliasSeq!(string, wstring, dstring))
2792 {
2793 // Valid and printable (ASCII)
2794 formatTest( [cast(StrType)"hello"],
2795 `["hello"]` );
2796
2797 // 1 character escape sequences (' is not escaped in strings)
2798 formatTest( [cast(StrType)"\"'\0\\\a\b\f\n\r\t\v"],
2799 `["\"'\0\\\a\b\f\n\r\t\v"]` );
2800
2801 // 1 character optional escape sequences
2802 formatTest( [cast(StrType)"\'\?"],
2803 `["'?"]` );
2804
2805 // Valid and non-printable code point (<= U+FF)
2806 formatTest( [cast(StrType)"\x10\x1F\x20test"],
2807 `["\x10\x1F test"]` );
2808
2809 // Valid and non-printable code point (<= U+FFFF)
2810 formatTest( [cast(StrType)"\u200B..\u200F"],
2811 `["\u200B..\u200F"]` );
2812
2813 // Valid and non-printable code point (<= U+10FFFF)
2814 formatTest( [cast(StrType)"\U000E0020..\U000E007F"],
2815 `["\U000E0020..\U000E007F"]` );
2816 }
2817
2818 // invalid UTF sequence needs hex-string literal postfix (c/w/d)
2819 {
2820 // U+FFFF with UTF-8 (Invalid code point for interchange)
2821 formatTest( [cast(string)[0xEF, 0xBF, 0xBF]],
2822 `[x"EF BF BF"c]` );
2823
2824 // U+FFFF with UTF-16 (Invalid code point for interchange)
2825 formatTest( [cast(wstring)[0xFFFF]],
2826 `[x"FFFF"w]` );
2827
2828 // U+FFFF with UTF-32 (Invalid code point for interchange)
2829 formatTest( [cast(dstring)[0xFFFF]],
2830 `[x"FFFF"d]` );
2831 }
2832 }
2833
2834 @safe unittest
2835 {
2836 // nested range formatting with array of string
2837 formatTest( "%({%(%02x %)}%| %)", ["test", "msg"],
2838 `{74 65 73 74} {6d 73 67}` );
2839 }
2840
2841 @safe unittest
2842 {
2843 // stop auto escaping inside range formatting
2844 auto arr = ["hello", "world"];
2845 formatTest( "%(%s, %)", arr, `"hello", "world"` );
2846 formatTest( "%-(%s, %)", arr, `hello, world` );
2847
2848 auto aa1 = [1:"hello", 2:"world"];
2849 formatTest( "%(%s:%s, %)", aa1, [`1:"hello", 2:"world"`, `2:"world", 1:"hello"`] );
2850 formatTest( "%-(%s:%s, %)", aa1, [`1:hello, 2:world`, `2:world, 1:hello`] );
2851
2852 auto aa2 = [1:["ab", "cd"], 2:["ef", "gh"]];
2853 formatTest( "%-(%s:%s, %)", aa2, [`1:["ab", "cd"], 2:["ef", "gh"]`, `2:["ef", "gh"], 1:["ab", "cd"]`] );
2854 formatTest( "%-(%s:%(%s%), %)", aa2, [`1:"ab""cd", 2:"ef""gh"`, `2:"ef""gh", 1:"ab""cd"`] );
2855 formatTest( "%-(%s:%-(%s%)%|, %)", aa2, [`1:abcd, 2:efgh`, `2:efgh, 1:abcd`] );
2856 }
2857
2858 // input range formatting
2859 private void formatRange(Writer, T, Char)(ref Writer w, ref T val, const ref FormatSpec!Char f)
2860 if (isInputRange!T)
2861 {
2862 import std.conv : text;
2863
2864 // Formatting character ranges like string
2865 if (f.spec == 's')
2866 {
2867 alias E = ElementType!T;
2868
2869 static if (!is(E == enum) && is(CharTypeOf!E))
2870 {
2871 static if (is(StringTypeOf!T))
2872 {
2873 auto s = val[0 .. f.precision < $ ? f.precision : $];
2874 if (!f.flDash)
2875 {
2876 // right align
2877 if (f.width > s.length)
2878 foreach (i ; 0 .. f.width - s.length) put(w, ' ');
2879 put(w, s);
2880 }
2881 else
2882 {
2883 // left align
2884 put(w, s);
2885 if (f.width > s.length)
2886 foreach (i ; 0 .. f.width - s.length) put(w, ' ');
2887 }
2888 }
2889 else
2890 {
2891 if (!f.flDash)
2892 {
2893 static if (hasLength!T)
2894 {
2895 // right align
2896 auto len = val.length;
2897 }
2898 else static if (isForwardRange!T && !isInfinite!T)
2899 {
2900 auto len = walkLength(val.save);
2901 }
2902 else
2903 {
2904 enforce(f.width == 0, "Cannot right-align a range without length");
2905 size_t len = 0;
2906 }
2907 if (f.precision != f.UNSPECIFIED && len > f.precision)
2908 len = f.precision;
2909
2910 if (f.width > len)
2911 foreach (i ; 0 .. f.width - len)
2912 put(w, ' ');
2913 if (f.precision == f.UNSPECIFIED)
2914 put(w, val);
2915 else
2916 {
2917 size_t printed = 0;
2918 for (; !val.empty && printed < f.precision; val.popFront(), ++printed)
2919 put(w, val.front);
2920 }
2921 }
2922 else
2923 {
2924 size_t printed = void;
2925
2926 // left align
2927 if (f.precision == f.UNSPECIFIED)
2928 {
2929 static if (hasLength!T)
2930 {
2931 printed = val.length;
2932 put(w, val);
2933 }
2934 else
2935 {
2936 printed = 0;
2937 for (; !val.empty; val.popFront(), ++printed)
2938 put(w, val.front);
2939 }
2940 }
2941 else
2942 {
2943 printed = 0;
2944 for (; !val.empty && printed < f.precision; val.popFront(), ++printed)
2945 put(w, val.front);
2946 }
2947
2948 if (f.width > printed)
2949 foreach (i ; 0 .. f.width - printed)
2950 put(w, ' ');
2951 }
2952 }
2953 }
2954 else
2955 {
2956 put(w, f.seqBefore);
2957 if (!val.empty)
2958 {
2959 formatElement(w, val.front, f);
2960 val.popFront();
2961 for (size_t i; !val.empty; val.popFront(), ++i)
2962 {
2963 put(w, f.seqSeparator);
2964 formatElement(w, val.front, f);
2965 }
2966 }
2967 static if (!isInfinite!T) put(w, f.seqAfter);
2968 }
2969 }
2970 else if (f.spec == 'r')
2971 {
2972 static if (is(DynamicArrayTypeOf!T))
2973 {
2974 alias ARR = DynamicArrayTypeOf!T;
2975 foreach (e ; cast(ARR) val)
2976 {
2977 formatValue(w, e, f);
2978 }
2979 }
2980 else
2981 {
2982 for (size_t i; !val.empty; val.popFront(), ++i)
2983 {
2984 formatValue(w, val.front, f);
2985 }
2986 }
2987 }
2988 else if (f.spec == '(')
2989 {
2990 if (val.empty)
2991 return;
2992 // Nested specifier is to be used
2993 for (;;)
2994 {
2995 auto fmt = FormatSpec!Char(f.nested);
2996 fmt.writeUpToNextSpec(w);
2997 if (f.flDash)
2998 formatValue(w, val.front, fmt);
2999 else
3000 formatElement(w, val.front, fmt);
3001 if (f.sep !is null)
3002 {
3003 put(w, fmt.trailing);
3004 val.popFront();
3005 if (val.empty)
3006 break;
3007 put(w, f.sep);
3008 }
3009 else
3010 {
3011 val.popFront();
3012 if (val.empty)
3013 break;
3014 put(w, fmt.trailing);
3015 }
3016 }
3017 }
3018 else
3019 throw new Exception(text("Incorrect format specifier for range: %", f.spec));
3020 }
3021
3022 @safe pure unittest
3023 {
3024 assert(collectExceptionMsg(format("%d", "hi")).back == 'd');
3025 }
3026
3027 // character formatting with ecaping
3028 private void formatChar(Writer)(ref Writer w, in dchar c, in char quote)
3029 {
3030 import std.uni : isGraphical;
3031
3032 string fmt;
3033 if (isGraphical(c))
3034 {
3035 if (c == quote || c == '\\')
3036 put(w, '\\');
3037 put(w, c);
3038 return;
3039 }
3040 else if (c <= 0xFF)
3041 {
3042 if (c < 0x20)
3043 {
3044 foreach (i, k; "\n\r\t\a\b\f\v\0")
3045 {
3046 if (c == k)
3047 {
3048 put(w, '\\');
3049 put(w, "nrtabfv0"[i]);
3050 return;
3051 }
3052 }
3053 }
3054 fmt = "\\x%02X";
3055 }
3056 else if (c <= 0xFFFF)
3057 fmt = "\\u%04X";
3058 else
3059 fmt = "\\U%08X";
3060
3061 formattedWrite(w, fmt, cast(uint) c);
3062 }
3063
3064 // undocumented because of deprecation
3065 // string elements are formatted like UTF-8 string literals.
3066 void formatElement(Writer, T, Char)(auto ref Writer w, T val, const ref FormatSpec!Char f)
3067 if (is(StringTypeOf!T) && !is(T == enum))
3068 {
3069 import std.array : appender;
3070 import std.utf : UTFException;
3071
3072 StringTypeOf!T str = val; // bug 8015
3073
3074 if (f.spec == 's')
3075 {
3076 try
3077 {
3078 // ignore other specifications and quote
3079 auto app = appender!(typeof(val[0])[])();
3080 put(app, '\"');
3081 for (size_t i = 0; i < str.length; )
3082 {
3083 import std.utf : decode;
3084
3085 auto c = decode(str, i);
3086 // \uFFFE and \uFFFF are considered valid by isValidDchar,
3087 // so need checking for interchange.
3088 if (c == 0xFFFE || c == 0xFFFF)
3089 goto LinvalidSeq;
3090 formatChar(app, c, '"');
3091 }
3092 put(app, '\"');
3093 put(w, app.data);
3094 return;
3095 }
3096 catch (UTFException)
3097 {
3098 }
3099
3100 // If val contains invalid UTF sequence, formatted like HexString literal
3101 LinvalidSeq:
3102 static if (is(typeof(str[0]) : const(char)))
3103 {
3104 enum postfix = 'c';
3105 alias IntArr = const(ubyte)[];
3106 }
3107 else static if (is(typeof(str[0]) : const(wchar)))
3108 {
3109 enum postfix = 'w';
3110 alias IntArr = const(ushort)[];
3111 }
3112 else static if (is(typeof(str[0]) : const(dchar)))
3113 {
3114 enum postfix = 'd';
3115 alias IntArr = const(uint)[];
3116 }
3117 formattedWrite(w, "x\"%(%02X %)\"%s", cast(IntArr) str, postfix);
3118 }
3119 else
3120 formatValue(w, str, f);
3121 }
3122
3123 @safe pure unittest
3124 {
3125 import std.array : appender;
3126 auto w = appender!string();
3127 auto spec = singleSpec("%s");
3128 formatElement(w, "Hello World", spec);
3129
3130 assert(w.data == "\"Hello World\"");
3131 }
3132
3133 @safe unittest
3134 {
3135 // Test for bug 8015
3136 import std.typecons;
3137
3138 struct MyStruct {
3139 string str;
3140 @property string toStr() {
3141 return str;
3142 }
3143 alias toStr this;
3144 }
3145
3146 Tuple!(MyStruct) t;
3147 }
3148
3149 // undocumented because of deprecation
3150 // Character elements are formatted like UTF-8 character literals.
3151 void formatElement(Writer, T, Char)(auto ref Writer w, T val, const ref FormatSpec!Char f)
3152 if (is(CharTypeOf!T) && !is(T == enum))
3153 {
3154 if (f.spec == 's')
3155 {
3156 put(w, '\'');
3157 formatChar(w, val, '\'');
3158 put(w, '\'');
3159 }
3160 else
3161 formatValue(w, val, f);
3162 }
3163
3164 ///
3165 @safe unittest
3166 {
3167 import std.array : appender;
3168 auto w = appender!string();
3169 auto spec = singleSpec("%s");
3170 formatElement(w, "H", spec);
3171
3172 assert(w.data == "\"H\"", w.data);
3173 }
3174
3175 // undocumented
3176 // Maybe T is noncopyable struct, so receive it by 'auto ref'.
3177 void formatElement(Writer, T, Char)(auto ref Writer w, auto ref T val, const ref FormatSpec!Char f)
3178 if (!is(StringTypeOf!T) && !is(CharTypeOf!T) || is(T == enum))
3179 {
3180 formatValue(w, val, f);
3181 }
3182
3183 /**
3184 Associative arrays are formatted by using $(D ':') and $(D ", ") as
3185 separators, and enclosed by $(D '[') and $(D ']').
3186
3187 Params:
3188 w = The $(D OutputRange) to write to.
3189 obj = The value to write.
3190 f = The $(D FormatSpec) defining how to write the value.
3191 */
3192 void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f)
3193 if (is(AssocArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
3194 {
3195 AssocArrayTypeOf!T val = obj;
3196
3197 enforceFmt(f.spec == 's' || f.spec == '(',
3198 "incompatible format character for associative array argument: %" ~ f.spec);
3199
3200 enum const(Char)[] defSpec = "%s" ~ f.keySeparator ~ "%s" ~ f.seqSeparator;
3201 auto fmtSpec = f.spec == '(' ? f.nested : defSpec;
3202
3203 size_t i = 0;
3204 immutable end = val.length;
3205
3206 if (f.spec == 's')
3207 put(w, f.seqBefore);
3208 foreach (k, ref v; val)
3209 {
3210 auto fmt = FormatSpec!Char(fmtSpec);
3211 fmt.writeUpToNextSpec(w);
3212 if (f.flDash)
3213 {
3214 formatValue(w, k, fmt);
3215 fmt.writeUpToNextSpec(w);
3216 formatValue(w, v, fmt);
3217 }
3218 else
3219 {
3220 formatElement(w, k, fmt);
3221 fmt.writeUpToNextSpec(w);
3222 formatElement(w, v, fmt);
3223 }
3224 if (f.sep !is null)
3225 {
3226 fmt.writeUpToNextSpec(w);
3227 if (++i != end)
3228 put(w, f.sep);
3229 }
3230 else
3231 {
3232 if (++i != end)
3233 fmt.writeUpToNextSpec(w);
3234 }
3235 }
3236 if (f.spec == 's')
3237 put(w, f.seqAfter);
3238 }
3239
3240 ///
3241 @safe pure unittest
3242 {
3243 import std.array : appender;
3244 auto w = appender!string();
3245 auto spec = singleSpec("%s");
3246 auto aa = ["H":"W"];
3247 formatElement(w, aa, spec);
3248
3249 assert(w.data == "[\"H\":\"W\"]", w.data);
3250 }
3251
3252 @safe unittest
3253 {
3254 assert(collectExceptionMsg!FormatException(format("%d", [0:1])).back == 'd');
3255
3256 int[string] aa0;
3257 formatTest( aa0, `[]` );
3258
3259 // elements escaping
3260 formatTest( ["aaa":1, "bbb":2],
3261 [`["aaa":1, "bbb":2]`, `["bbb":2, "aaa":1]`] );
3262 formatTest( ['c':"str"],
3263 `['c':"str"]` );
3264 formatTest( ['"':"\"", '\'':"'"],
3265 [`['"':"\"", '\'':"'"]`, `['\'':"'", '"':"\""]`] );
3266
3267 // range formatting for AA
3268 auto aa3 = [1:"hello", 2:"world"];
3269 // escape
3270 formatTest( "{%(%s:%s $ %)}", aa3,
3271 [`{1:"hello" $ 2:"world"}`, `{2:"world" $ 1:"hello"}`]);
3272 // use range formatting for key and value, and use %|
3273 formatTest( "{%([%04d->%(%c.%)]%| $ %)}", aa3,
3274 [`{[0001->h.e.l.l.o] $ [0002->w.o.r.l.d]}`, `{[0002->w.o.r.l.d] $ [0001->h.e.l.l.o]}`] );
3275
3276 // issue 12135
3277 formatTest("%(%s:<%s>%|,%)", [1:2], "1:<2>");
3278 formatTest("%(%s:<%s>%|%)" , [1:2], "1:<2>");
3279 }
3280
3281 @system unittest
3282 {
3283 class C1 { int[char] val; alias val this; this(int[char] v){ val = v; } }
3284 class C2 { int[char] val; alias val this; this(int[char] v){ val = v; }
3285 override string toString() const { return "C"; } }
3286 formatTest( new C1(['c':1, 'd':2]), [`['c':1, 'd':2]`, `['d':2, 'c':1]`] );
3287 formatTest( new C2(['c':1, 'd':2]), "C" );
3288
3289 struct S1 { int[char] val; alias val this; }
3290 struct S2 { int[char] val; alias val this;
3291 string toString() const { return "S"; } }
3292 formatTest( S1(['c':1, 'd':2]), [`['c':1, 'd':2]`, `['d':2, 'c':1]`] );
3293 formatTest( S2(['c':1, 'd':2]), "S" );
3294 }
3295
3296 @safe unittest // Issue 8921
3297 {
3298 enum E : char { A = 'a', B = 'b', C = 'c' }
3299 E[3] e = [E.A, E.B, E.C];
3300 formatTest(e, "[A, B, C]");
3301
3302 E[] e2 = [E.A, E.B, E.C];
3303 formatTest(e2, "[A, B, C]");
3304 }
3305
3306 template hasToString(T, Char)
3307 {
3308 static if (isPointer!T && !isAggregateType!T)
3309 {
3310 // X* does not have toString, even if X is aggregate type has toString.
3311 enum hasToString = 0;
3312 }
3313 else static if (is(typeof({ T val = void; FormatSpec!Char f; val.toString((const(char)[] s){}, f); })))
3314 {
3315 enum hasToString = 4;
3316 }
3317 else static if (is(typeof({ T val = void; val.toString((const(char)[] s){}, "%s"); })))
3318 {
3319 enum hasToString = 3;
3320 }
3321 else static if (is(typeof({ T val = void; val.toString((const(char)[] s){}); })))
3322 {
3323 enum hasToString = 2;
3324 }
3325 else static if (is(typeof({ T val = void; return val.toString(); }()) S) && isSomeString!S)
3326 {
3327 enum hasToString = 1;
3328 }
3329 else
3330 {
3331 enum hasToString = 0;
3332 }
3333 }
3334
3335 // object formatting with toString
3336 private void formatObject(Writer, T, Char)(ref Writer w, ref T val, const ref FormatSpec!Char f)
3337 if (hasToString!(T, Char))
3338 {
3339 static if (is(typeof(val.toString((const(char)[] s){}, f))))
3340 {
3341 val.toString((const(char)[] s) { put(w, s); }, f);
3342 }
3343 else static if (is(typeof(val.toString((const(char)[] s){}, "%s"))))
3344 {
3345 val.toString((const(char)[] s) { put(w, s); }, f.getCurFmtStr());
3346 }
3347 else static if (is(typeof(val.toString((const(char)[] s){}))))
3348 {
3349 val.toString((const(char)[] s) { put(w, s); });
3350 }
3351 else static if (is(typeof(val.toString()) S) && isSomeString!S)
3352 {
3353 put(w, val.toString());
3354 }
3355 else
3356 static assert(0);
3357 }
3358
3359 void enforceValidFormatSpec(T, Char)(const ref FormatSpec!Char f)
3360 {
3361 static if (!isInputRange!T && hasToString!(T, Char) != 4)
3362 {
3363 enforceFmt(f.spec == 's',
3364 "Expected '%s' format specifier for type '" ~ T.stringof ~ "'");
3365 }
3366 }
3367
3368 @system unittest
3369 {
3370 static interface IF1 { }
3371 class CIF1 : IF1 { }
3372 static struct SF1 { }
3373 static union UF1 { }
3374 static class CF1 { }
3375
3376 static interface IF2 { string toString(); }
3377 static class CIF2 : IF2 { override string toString() { return ""; } }
3378 static struct SF2 { string toString() { return ""; } }
3379 static union UF2 { string toString() { return ""; } }
3380 static class CF2 { override string toString() { return ""; } }
3381
3382 static interface IK1 { void toString(scope void delegate(const(char)[]) sink,
3383 FormatSpec!char) const; }
3384 static class CIK1 : IK1 { override void toString(scope void delegate(const(char)[]) sink,
3385 FormatSpec!char) const { sink("CIK1"); } }
3386 static struct KS1 { void toString(scope void delegate(const(char)[]) sink,
3387 FormatSpec!char) const { sink("KS1"); } }
3388
3389 static union KU1 { void toString(scope void delegate(const(char)[]) sink,
3390 FormatSpec!char) const { sink("KU1"); } }
3391
3392 static class KC1 { void toString(scope void delegate(const(char)[]) sink,
3393 FormatSpec!char) const { sink("KC1"); } }
3394
3395 IF1 cif1 = new CIF1;
3396 assertThrown!FormatException(format("%f", cif1));
3397 assertThrown!FormatException(format("%f", SF1()));
3398 assertThrown!FormatException(format("%f", UF1()));
3399 assertThrown!FormatException(format("%f", new CF1()));
3400
3401 IF2 cif2 = new CIF2;
3402 assertThrown!FormatException(format("%f", cif2));
3403 assertThrown!FormatException(format("%f", SF2()));
3404 assertThrown!FormatException(format("%f", UF2()));
3405 assertThrown!FormatException(format("%f", new CF2()));
3406
3407 IK1 cik1 = new CIK1;
3408 assert(format("%f", cik1) == "CIK1");
3409 assert(format("%f", KS1()) == "KS1");
3410 assert(format("%f", KU1()) == "KU1");
3411 assert(format("%f", new KC1()) == "KC1");
3412 }
3413
3414 /**
3415 Aggregates ($(D struct), $(D union), $(D class), and $(D interface)) are
3416 basically formatted by calling $(D toString).
3417 $(D toString) should have one of the following signatures:
3418
3419 ---
3420 const void toString(scope void delegate(const(char)[]) sink, FormatSpec fmt);
3421 const void toString(scope void delegate(const(char)[]) sink, string fmt);
3422 const void toString(scope void delegate(const(char)[]) sink);
3423 const string toString();
3424 ---
3425
3426 For the class objects which have input range interface,
3427 $(UL $(LI If the instance $(D toString) has overridden
3428 $(D Object.toString), it is used.)
3429 $(LI Otherwise, the objects are formatted as input range.))
3430
3431 For the struct and union objects which does not have $(D toString),
3432 $(UL $(LI If they have range interface, formatted as input range.)
3433 $(LI Otherwise, they are formatted like $(D Type(field1, filed2, ...)).))
3434
3435 Otherwise, are formatted just as their type name.
3436 */
3437 void formatValue(Writer, T, Char)(auto ref Writer w, T val, const ref FormatSpec!Char f)
3438 if (is(T == class) && !is(T == enum))
3439 {
3440 enforceValidFormatSpec!(T, Char)(f);
3441 // TODO: Change this once toString() works for shared objects.
3442 static assert(!is(T == shared), "unable to format shared objects");
3443
3444 if (val is null)
3445 put(w, "null");
3446 else
3447 {
3448 static if (hasToString!(T, Char) > 1 || (!isInputRange!T && !is(BuiltinTypeOf!T)))
3449 {
3450 formatObject!(Writer, T, Char)(w, val, f);
3451 }
3452 else
3453 {
3454 //string delegate() dg = &val.toString;
3455 Object o = val; // workaround
3456 string delegate() dg = &o.toString;
3457 if (dg.funcptr != &Object.toString) // toString is overridden
3458 {
3459 formatObject(w, val, f);
3460 }
3461 else static if (isInputRange!T)
3462 {
3463 formatRange(w, val, f);
3464 }
3465 else static if (is(BuiltinTypeOf!T X))
3466 {
3467 X x = val;
3468 formatValue(w, x, f);
3469 }
3470 else
3471 {
3472 formatObject(w, val, f);
3473 }
3474 }
3475 }
3476 }
3477
3478 /++
3479 $(D formatValue) allows to reuse existing format specifiers:
3480 +/
3481 @system unittest
3482 {
3483 import std.format;
3484
3485 struct Point
3486 {
3487 int x, y;
3488
3489 void toString(scope void delegate(const(char)[]) sink,
3490 FormatSpec!char fmt) const
3491 {
3492 sink("(");
3493 sink.formatValue(x, fmt);
3494 sink(",");
3495 sink.formatValue(y, fmt);
3496 sink(")");
3497 }
3498 }
3499
3500 auto p = Point(16,11);
3501 assert(format("%03d", p) == "(016,011)");
3502 assert(format("%02x", p) == "(10,0b)");
3503 }
3504
3505 /++
3506 The following code compares the use of $(D formatValue) and $(D formattedWrite).
3507 +/
3508 @safe pure unittest
3509 {
3510 import std.array : appender;
3511 import std.format;
3512
3513 auto writer1 = appender!string();
3514 writer1.formattedWrite("%08b", 42);
3515
3516 auto writer2 = appender!string();
3517 auto f = singleSpec("%08b");
3518 writer2.formatValue(42, f);
3519
3520 assert(writer1.data == writer2.data && writer1.data == "00101010");
3521 }
3522
3523 @system unittest
3524 {
3525 import std.array : appender;
3526 import std.range.interfaces;
3527 // class range (issue 5154)
3528 auto c = inputRangeObject([1,2,3,4]);
3529 formatTest( c, "[1, 2, 3, 4]" );
3530 assert(c.empty);
3531 c = null;
3532 formatTest( c, "null" );
3533 }
3534
3535 @system unittest
3536 {
3537 // 5354
3538 // If the class has both range I/F and custom toString, the use of custom
3539 // toString routine is prioritized.
3540
3541 // Enable the use of custom toString that gets a sink delegate
3542 // for class formatting.
3543
3544 enum inputRangeCode =
3545 q{
3546 int[] arr;
3547 this(int[] a){ arr = a; }
3548 @property int front() const { return arr[0]; }
3549 @property bool empty() const { return arr.length == 0; }
3550 void popFront(){ arr = arr[1..$]; }
3551 };
3552
3553 class C1
3554 {
3555 mixin(inputRangeCode);
3556 void toString(scope void delegate(const(char)[]) dg, const ref FormatSpec!char f) const { dg("[012]"); }
3557 }
3558 class C2
3559 {
3560 mixin(inputRangeCode);
3561 void toString(scope void delegate(const(char)[]) dg, string f) const { dg("[012]"); }
3562 }
3563 class C3
3564 {
3565 mixin(inputRangeCode);
3566 void toString(scope void delegate(const(char)[]) dg) const { dg("[012]"); }
3567 }
3568 class C4
3569 {
3570 mixin(inputRangeCode);
3571 override string toString() const { return "[012]"; }
3572 }
3573 class C5
3574 {
3575 mixin(inputRangeCode);
3576 }
3577
3578 formatTest( new C1([0, 1, 2]), "[012]" );
3579 formatTest( new C2([0, 1, 2]), "[012]" );
3580 formatTest( new C3([0, 1, 2]), "[012]" );
3581 formatTest( new C4([0, 1, 2]), "[012]" );
3582 formatTest( new C5([0, 1, 2]), "[0, 1, 2]" );
3583 }
3584
3585 /// ditto
3586 void formatValue(Writer, T, Char)(auto ref Writer w, T val, const ref FormatSpec!Char f)
3587 if (is(T == interface) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is(T == enum))
3588 {
3589 enforceValidFormatSpec!(T, Char)(f);
3590 if (val is null)
3591 put(w, "null");
3592 else
3593 {
3594 static if (hasToString!(T, Char))
3595 {
3596 formatObject(w, val, f);
3597 }
3598 else static if (isInputRange!T)
3599 {
3600 formatRange(w, val, f);
3601 }
3602 else
3603 {
3604 version (Windows)
3605 {
3606 import core.sys.windows.com : IUnknown;
3607 static if (is(T : IUnknown))
3608 {
3609 formatValue(w, *cast(void**)&val, f);
3610 }
3611 else
3612 {
3613 formatValue(w, cast(Object) val, f);
3614 }
3615 }
3616 else
3617 {
3618 formatValue(w, cast(Object) val, f);
3619 }
3620 }
3621 }
3622 }
3623
3624 @system unittest
3625 {
3626 // interface
3627 import std.range.interfaces;
3628 InputRange!int i = inputRangeObject([1,2,3,4]);
3629 formatTest( i, "[1, 2, 3, 4]" );
3630 assert(i.empty);
3631 i = null;
3632 formatTest( i, "null" );
3633
3634 // interface (downcast to Object)
3635 interface Whatever {}
3636 class C : Whatever
3637 {
3638 override @property string toString() const { return "ab"; }
3639 }
3640 Whatever val = new C;
3641 formatTest( val, "ab" );
3642
3643 // Issue 11175
3644 version (Windows)
3645 {
3646 import core.sys.windows.com : IUnknown, IID;
3647 import core.sys.windows.windows : HRESULT;
3648
3649 interface IUnknown2 : IUnknown { }
3650
3651 class D : IUnknown2
3652 {
3653 extern(Windows) HRESULT QueryInterface(const(IID)* riid, void** pvObject) { return typeof(return).init; }
3654 extern(Windows) uint AddRef() { return 0; }
3655 extern(Windows) uint Release() { return 0; }
3656 }
3657
3658 IUnknown2 d = new D;
3659 string expected = format("%X", cast(void*) d);
3660 formatTest(d, expected);
3661 }
3662 }
3663
3664 /// ditto
3665 // Maybe T is noncopyable struct, so receive it by 'auto ref'.
3666 void formatValue(Writer, T, Char)(auto ref Writer w, auto ref T val, const ref FormatSpec!Char f)
3667 if ((is(T == struct) || is(T == union)) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is(T == enum))
3668 {
3669 enforceValidFormatSpec!(T, Char)(f);
3670 static if (hasToString!(T, Char))
3671 {
3672 formatObject(w, val, f);
3673 }
3674 else static if (isInputRange!T)
3675 {
3676 formatRange(w, val, f);
3677 }
3678 else static if (is(T == struct))
3679 {
3680 enum left = T.stringof~"(";
3681 enum separator = ", ";
3682 enum right = ")";
3683
3684 put(w, left);
3685 foreach (i, e; val.tupleof)
3686 {
3687 static if (0 < i && val.tupleof[i-1].offsetof == val.tupleof[i].offsetof)
3688 {
3689 static if (i == val.tupleof.length - 1 || val.tupleof[i].offsetof != val.tupleof[i+1].offsetof)
3690 put(w, separator~val.tupleof[i].stringof[4..$]~"}");
3691 else
3692 put(w, separator~val.tupleof[i].stringof[4..$]);
3693 }
3694 else
3695 {
3696 static if (i+1 < val.tupleof.length && val.tupleof[i].offsetof == val.tupleof[i+1].offsetof)
3697 put(w, (i > 0 ? separator : "")~"#{overlap "~val.tupleof[i].stringof[4..$]);
3698 else
3699 {
3700 static if (i > 0)
3701 put(w, separator);
3702 formatElement(w, e, f);
3703 }
3704 }
3705 }
3706 put(w, right);
3707 }
3708 else
3709 {
3710 put(w, T.stringof);
3711 }
3712 }
3713
3714 @safe unittest
3715 {
3716 // bug 4638
3717 struct U8 { string toString() const { return "blah"; } }
3718 struct U16 { wstring toString() const { return "blah"; } }
3719 struct U32 { dstring toString() const { return "blah"; } }
3720 formatTest( U8(), "blah" );
3721 formatTest( U16(), "blah" );
3722 formatTest( U32(), "blah" );
3723 }
3724
3725 @safe unittest
3726 {
3727 // 3890
3728 struct Int{ int n; }
3729 struct Pair{ string s; Int i; }
3730 formatTest( Pair("hello", Int(5)),
3731 `Pair("hello", Int(5))` );
3732 }
3733
3734 @system unittest
3735 {
3736 // union formatting without toString
3737 union U1
3738 {
3739 int n;
3740 string s;
3741 }
3742 U1 u1;
3743 formatTest( u1, "U1" );
3744
3745 // union formatting with toString
3746 union U2
3747 {
3748 int n;
3749 string s;
3750 string toString() const { return s; }
3751 }
3752 U2 u2;
3753 u2.s = "hello";
3754 formatTest( u2, "hello" );
3755 }
3756
3757 @system unittest
3758 {
3759 import std.array;
3760 // 7230
3761 static struct Bug7230
3762 {
3763 string s = "hello";
3764 union {
3765 string a;
3766 int b;
3767 double c;
3768 }
3769 long x = 10;
3770 }
3771
3772 Bug7230 bug;
3773 bug.b = 123;
3774
3775 FormatSpec!char f;
3776 auto w = appender!(char[])();
3777 formatValue(w, bug, f);
3778 assert(w.data == `Bug7230("hello", #{overlap a, b, c}, 10)`);
3779 }
3780
3781 @safe unittest
3782 {
3783 import std.array;
3784 static struct S{ @disable this(this); }
3785 S s;
3786
3787 FormatSpec!char f;
3788 auto w = appender!string();
3789 formatValue(w, s, f);
3790 assert(w.data == "S()");
3791 }
3792
3793 /**
3794 $(D enum) is formatted like its base value.
3795
3796 Params:
3797 w = The $(D OutputRange) to write to.
3798 val = The value to write.
3799 f = The $(D FormatSpec) defining how to write the value.
3800 */
3801 void formatValue(Writer, T, Char)(auto ref Writer w, T val, const ref FormatSpec!Char f)
3802 if (is(T == enum))
3803 {
3804 if (f.spec == 's')
3805 {
3806 foreach (i, e; EnumMembers!T)
3807 {
3808 if (val == e)
3809 {
3810 formatValue(w, __traits(allMembers, T)[i], f);
3811 return;
3812 }
3813 }
3814
3815 // val is not a member of T, output cast(T) rawValue instead.
3816 put(w, "cast(" ~ T.stringof ~ ")");
3817 static assert(!is(OriginalType!T == T));
3818 }
3819 formatValue(w, cast(OriginalType!T) val, f);
3820 }
3821
3822 ///
3823 @safe pure unittest
3824 {
3825 import std.array : appender;
3826 auto w = appender!string();
3827 auto spec = singleSpec("%s");
3828
3829 enum A { first, second, third }
3830
3831 formatElement(w, A.second, spec);
3832
3833 assert(w.data == "second");
3834 }
3835
3836 @safe unittest
3837 {
3838 enum A { first, second, third }
3839 formatTest( A.second, "second" );
3840 formatTest( cast(A) 72, "cast(A)72" );
3841 }
3842 @safe unittest
3843 {
3844 enum A : string { one = "uno", two = "dos", three = "tres" }
3845 formatTest( A.three, "three" );
3846 formatTest( cast(A)"mill\ón", "cast(A)mill\ón" );
3847 }
3848 @safe unittest
3849 {
3850 enum A : bool { no, yes }
3851 formatTest( A.yes, "yes" );
3852 formatTest( A.no, "no" );
3853 }
3854 @safe unittest
3855 {
3856 // Test for bug 6892
3857 enum Foo { A = 10 }
3858 formatTest("%s", Foo.A, "A");
3859 formatTest(">%4s<", Foo.A, "> A<");
3860 formatTest("%04d", Foo.A, "0010");
3861 formatTest("%+2u", Foo.A, "+10");
3862 formatTest("%02x", Foo.A, "0a");
3863 formatTest("%3o", Foo.A, " 12");
3864 formatTest("%b", Foo.A, "1010");
3865 }
3866
3867 /**
3868 Pointers are formatted as hex integers.
3869 */
3870 void formatValue(Writer, T, Char)(auto ref Writer w, T val, const ref FormatSpec!Char f)
3871 if (isPointer!T && !is(T == enum) && !hasToString!(T, Char))
3872 {
3873 static if (isInputRange!T)
3874 {
3875 if (val !is null)
3876 {
3877 formatRange(w, *val, f);
3878 return;
3879 }
3880 }
3881
3882 static if (is(typeof({ shared const void* p = val; })))
3883 alias SharedOf(T) = shared(T);
3884 else
3885 alias SharedOf(T) = T;
3886
3887 const SharedOf!(void*) p = val;
3888 const pnum = ()@trusted{ return cast(ulong) p; }();
3889
3890 if (f.spec == 's')
3891 {
3892 if (p is null)
3893 {
3894 put(w, "null");
3895 return;
3896 }
3897 FormatSpec!Char fs = f; // fs is copy for change its values.
3898 fs.spec = 'X';
3899 formatValue(w, pnum, fs);
3900 }
3901 else
3902 {
3903 enforceFmt(f.spec == 'X' || f.spec == 'x',
3904 "Expected one of %s, %x or %X for pointer type.");
3905 formatValue(w, pnum, f);
3906 }
3907 }
3908
3909 @safe pure unittest
3910 {
3911 // pointer
3912 import std.range;
3913 auto r = retro([1,2,3,4]);
3914 auto p = ()@trusted{ auto p = &r; return p; }();
3915 formatTest( p, "[4, 3, 2, 1]" );
3916 assert(p.empty);
3917 p = null;
3918 formatTest( p, "null" );
3919
3920 auto q = ()@trusted{ return cast(void*) 0xFFEECCAA; }();
3921 formatTest( q, "FFEECCAA" );
3922 }
3923
3924 @system pure unittest
3925 {
3926 // Test for issue 7869
3927 struct S
3928 {
3929 string toString() const { return ""; }
3930 }
3931 S* p = null;
3932 formatTest( p, "null" );
3933
3934 S* q = cast(S*) 0xFFEECCAA;
3935 formatTest( q, "FFEECCAA" );
3936 }
3937
3938 @system unittest
3939 {
3940 // Test for issue 8186
3941 class B
3942 {
3943 int*a;
3944 this(){ a = new int; }
3945 alias a this;
3946 }
3947 formatTest( B.init, "null" );
3948 }
3949
3950 @system pure unittest
3951 {
3952 // Test for issue 9336
3953 shared int i;
3954 format("%s", &i);
3955 }
3956
3957 @system pure unittest
3958 {
3959 // Test for issue 11778
3960 int* p = null;
3961 assertThrown(format("%d", p));
3962 assertThrown(format("%04d", p + 2));
3963 }
3964
3965 @safe pure unittest
3966 {
3967 // Test for issue 12505
3968 void* p = null;
3969 formatTest( "%08X", p, "00000000" );
3970 }
3971
3972 /**
3973 Delegates are formatted by 'ReturnType delegate(Parameters) FunctionAttributes'
3974 */
3975 void formatValue(Writer, T, Char)(auto ref Writer w, scope T, const ref FormatSpec!Char f)
3976 if (isDelegate!T)
3977 {
3978 formatValue(w, T.stringof, f);
3979 }
3980
3981 ///
3982 @safe pure unittest
3983 {
3984 import std.conv : to;
3985
3986 int i;
3987
3988 int foo(short k) @nogc
3989 {
3990 return i + k;
3991 }
3992
3993 @system int delegate(short) @nogc bar() nothrow pure
3994 {
3995 int* p = new int;
3996 return &foo;
3997 }
3998
3999 assert(to!string(&bar) == "int delegate(short) @nogc delegate() pure nothrow @system");
4000 }
4001
4002 @safe unittest
4003 {
4004 void func() @system { __gshared int x; ++x; throw new Exception("msg"); }
4005 version (linux) formatTest( &func, "void delegate() @system" );
4006 }
4007
4008 @safe pure unittest
4009 {
4010 int[] a = [ 1, 3, 2 ];
4011 formatTest( "testing %(%s & %) embedded", a,
4012 "testing 1 & 3 & 2 embedded");
4013 formatTest( "testing %((%s) %)) wyda3", a,
4014 "testing (1) (3) (2) wyda3" );
4015
4016 int[0] empt = [];
4017 formatTest( "(%s)", empt,
4018 "([])" );
4019 }
4020
4021 //------------------------------------------------------------------------------
4022 // Fix for issue 1591
4023 private int getNthInt(string kind, A...)(uint index, A args)
4024 {
4025 return getNth!(kind, isIntegral,int)(index, args);
4026 }
4027
4028 private T getNth(string kind, alias Condition, T, A...)(uint index, A args)
4029 {
4030 import std.conv : text, to;
4031
4032 switch (index)
4033 {
4034 foreach (n, _; A)
4035 {
4036 case n:
4037 static if (Condition!(typeof(args[n])))
4038 {
4039 return to!T(args[n]);
4040 }
4041 else
4042 {
4043 throw new FormatException(
4044 text(kind, " expected, not ", typeof(args[n]).stringof,
4045 " for argument #", index + 1));
4046 }
4047 }
4048 default:
4049 throw new FormatException(
4050 text("Missing ", kind, " argument"));
4051 }
4052 }
4053
4054 @safe unittest
4055 {
4056 // width/precision
4057 assert(collectExceptionMsg!FormatException(format("%*.d", 5.1, 2))
4058 == "integer width expected, not double for argument #1");
4059 assert(collectExceptionMsg!FormatException(format("%-1*.d", 5.1, 2))
4060 == "integer width expected, not double for argument #1");
4061
4062 assert(collectExceptionMsg!FormatException(format("%.*d", '5', 2))
4063 == "integer precision expected, not char for argument #1");
4064 assert(collectExceptionMsg!FormatException(format("%-1.*d", 4.7, 3))
4065 == "integer precision expected, not double for argument #1");
4066 assert(collectExceptionMsg!FormatException(format("%.*d", 5))
4067 == "Orphan format specifier: %d");
4068 assert(collectExceptionMsg!FormatException(format("%*.*d", 5))
4069 == "Missing integer precision argument");
4070
4071 // separatorCharPos
4072 assert(collectExceptionMsg!FormatException(format("%,?d", 5))
4073 == "separator character expected, not int for argument #1");
4074 assert(collectExceptionMsg!FormatException(format("%,?d", '?'))
4075 == "Orphan format specifier: %d");
4076 assert(collectExceptionMsg!FormatException(format("%.*,*?d", 5))
4077 == "Missing separator digit width argument");
4078 }
4079
4080 /* ======================== Unit Tests ====================================== */
4081
4082 version (unittest)
4083 void formatTest(T)(T val, string expected, size_t ln = __LINE__, string fn = __FILE__)
4084 {
4085 import core.exception : AssertError;
4086 import std.array : appender;
4087 import std.conv : text;
4088 FormatSpec!char f;
4089 auto w = appender!string();
4090 formatValue(w, val, f);
4091 enforce!AssertError(
4092 w.data == expected,
4093 text("expected = `", expected, "`, result = `", w.data, "`"), fn, ln);
4094 }
4095
4096 version (unittest)
4097 void formatTest(T)(string fmt, T val, string expected, size_t ln = __LINE__, string fn = __FILE__) @safe
4098 {
4099 import core.exception : AssertError;
4100 import std.array : appender;
4101 import std.conv : text;
4102 auto w = appender!string();
4103 formattedWrite(w, fmt, val);
4104 enforce!AssertError(
4105 w.data == expected,
4106 text("expected = `", expected, "`, result = `", w.data, "`"), fn, ln);
4107 }
4108
4109 version (unittest)
4110 void formatTest(T)(T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__)
4111 {
4112 import core.exception : AssertError;
4113 import std.array : appender;
4114 import std.conv : text;
4115 FormatSpec!char f;
4116 auto w = appender!string();
4117 formatValue(w, val, f);
4118 foreach (cur; expected)
4119 {
4120 if (w.data == cur) return;
4121 }
4122 enforce!AssertError(
4123 false,
4124 text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln);
4125 }
4126
4127 version (unittest)
4128 void formatTest(T)(string fmt, T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__) @safe
4129 {
4130 import core.exception : AssertError;
4131 import std.array : appender;
4132 import std.conv : text;
4133 auto w = appender!string();
4134 formattedWrite(w, fmt, val);
4135 foreach (cur; expected)
4136 {
4137 if (w.data == cur) return;
4138 }
4139 enforce!AssertError(
4140 false,
4141 text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln);
4142 }
4143
4144 @safe /*pure*/ unittest // formatting floating point values is now impure
4145 {
4146 import std.array;
4147
4148 auto stream = appender!string();
4149 formattedWrite(stream, "%s", 1.1);
4150 assert(stream.data == "1.1", stream.data);
4151 }
4152
4153 @safe pure unittest
4154 {
4155 import std.algorithm;
4156 import std.array;
4157
4158 auto stream = appender!string();
4159 formattedWrite(stream, "%s", map!"a*a"([2, 3, 5]));
4160 assert(stream.data == "[4, 9, 25]", stream.data);
4161
4162 // Test shared data.
4163 stream = appender!string();
4164 shared int s = 6;
4165 formattedWrite(stream, "%s", s);
4166 assert(stream.data == "6");
4167 }
4168
4169 @safe pure unittest
4170 {
4171 import std.array;
4172 auto stream = appender!string();
4173 formattedWrite(stream, "%u", 42);
4174 assert(stream.data == "42", stream.data);
4175 }
4176
4177 @safe pure unittest
4178 {
4179 // testing raw writes
4180 import std.array;
4181 auto w = appender!(char[])();
4182 uint a = 0x02030405;
4183 formattedWrite(w, "%+r", a);
4184 assert(w.data.length == 4 && w.data[0] == 2 && w.data[1] == 3
4185 && w.data[2] == 4 && w.data[3] == 5);
4186 w.clear();
4187 formattedWrite(w, "%-r", a);
4188 assert(w.data.length == 4 && w.data[0] == 5 && w.data[1] == 4
4189 && w.data[2] == 3 && w.data[3] == 2);
4190 }
4191
4192 @safe pure unittest
4193 {
4194 // testing positional parameters
4195 import std.array;
4196 auto w = appender!(char[])();
4197 formattedWrite(w,
4198 "Numbers %2$s and %1$s are reversed and %1$s%2$s repeated",
4199 42, 0);
4200 assert(w.data == "Numbers 0 and 42 are reversed and 420 repeated",
4201 w.data);
4202 assert(collectExceptionMsg!FormatException(formattedWrite(w, "%1$s, %3$s", 1, 2))
4203 == "Positional specifier %3$s index exceeds 2");
4204
4205 w.clear();
4206 formattedWrite(w, "asd%s", 23);
4207 assert(w.data == "asd23", w.data);
4208 w.clear();
4209 formattedWrite(w, "%s%s", 23, 45);
4210 assert(w.data == "2345", w.data);
4211 }
4212
4213 @safe unittest
4214 {
4215 import core.stdc.string : strlen;
4216 import std.array : appender;
4217 import std.conv : text, octal;
4218 import core.stdc.stdio : snprintf;
4219
4220 debug(format) printf("std.format.format.unittest\n");
4221
4222 auto stream = appender!(char[])();
4223 //goto here;
4224
4225 formattedWrite(stream,
4226 "hello world! %s %s ", true, 57, 1_000_000_000, 'x', " foo");
4227 assert(stream.data == "hello world! true 57 ",
4228 stream.data);
4229
4230 stream.clear();
4231 formattedWrite(stream, "%g %A %s", 1.67, -1.28, float.nan);
4232 // core.stdc.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr);
4233
4234 /* The host C library is used to format floats. C99 doesn't
4235 * specify what the hex digit before the decimal point is for
4236 * %A. */
4237
4238 version (CRuntime_Glibc)
4239 {
4240 assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan",
4241 stream.data);
4242 }
4243 else version (OSX)
4244 {
4245 assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan",
4246 stream.data);
4247 }
4248 else version (MinGW)
4249 {
4250 assert(stream.data == "1.67 -0XA.3D70A3D70A3D8P-3 nan",
4251 stream.data);
4252 }
4253 else version (CRuntime_Microsoft)
4254 {
4255 assert(stream.data == "1.67 -0X1.47AE14P+0 nan"
4256 || stream.data == "1.67 -0X1.47AE147AE147BP+0 nan", // MSVCRT 14+ (VS 2015)
4257 stream.data);
4258 }
4259 else
4260 {
4261 assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan",
4262 stream.data);
4263 }
4264 stream.clear();
4265
4266 formattedWrite(stream, "%x %X", 0x1234AF, 0xAFAFAFAF);
4267 assert(stream.data == "1234af AFAFAFAF");
4268 stream.clear();
4269
4270 formattedWrite(stream, "%b %o", 0x1234AF, 0xAFAFAFAF);
4271 assert(stream.data == "100100011010010101111 25753727657");
4272 stream.clear();
4273
4274 formattedWrite(stream, "%d %s", 0x1234AF, 0xAFAFAFAF);
4275 assert(stream.data == "1193135 2947526575");
4276 stream.clear();
4277
4278 // formattedWrite(stream, "%s", 1.2 + 3.4i);
4279 // assert(stream.data == "1.2+3.4i");
4280 // stream.clear();
4281
4282 formattedWrite(stream, "%a %A", 1.32, 6.78f);
4283 //formattedWrite(stream, "%x %X", 1.32);
4284 version (CRuntime_Microsoft)
4285 assert(stream.data == "0x1.51eb85p+0 0X1.B1EB86P+2"
4286 || stream.data == "0x1.51eb851eb851fp+0 0X1.B1EB860000000P+2"); // MSVCRT 14+ (VS 2015)
4287 else
4288 assert(stream.data == "0x1.51eb851eb851fp+0 0X1.B1EB86P+2");
4289 stream.clear();
4290
4291 formattedWrite(stream, "%#06.*f",2,12.345);
4292 assert(stream.data == "012.35");
4293 stream.clear();
4294
4295 formattedWrite(stream, "%#0*.*f",6,2,12.345);
4296 assert(stream.data == "012.35");
4297 stream.clear();
4298
4299 const real constreal = 1;
4300 formattedWrite(stream, "%g",constreal);
4301 assert(stream.data == "1");
4302 stream.clear();
4303
4304 formattedWrite(stream, "%7.4g:", 12.678);
4305 assert(stream.data == " 12.68:");
4306 stream.clear();
4307
4308 formattedWrite(stream, "%7.4g:", 12.678L);
4309 assert(stream.data == " 12.68:");
4310 stream.clear();
4311
4312 formattedWrite(stream, "%04f|%05d|%#05x|%#5x",-4.0,-10,1,1);
4313 assert(stream.data == "-4.000000|-0010|0x001| 0x1",
4314 stream.data);
4315 stream.clear();
4316
4317 int i;
4318 string s;
4319
4320 i = -10;
4321 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
4322 assert(stream.data == "-10|-10|-10|-10|-10.0000");
4323 stream.clear();
4324
4325 i = -5;
4326 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
4327 assert(stream.data == "-5| -5|-05|-5|-5.0000");
4328 stream.clear();
4329
4330 i = 0;
4331 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
4332 assert(stream.data == "0| 0|000|0|0.0000");
4333 stream.clear();
4334
4335 i = 5;
4336 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
4337 assert(stream.data == "5| 5|005|5|5.0000");
4338 stream.clear();
4339
4340 i = 10;
4341 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
4342 assert(stream.data == "10| 10|010|10|10.0000");
4343 stream.clear();
4344
4345 formattedWrite(stream, "%.0d", 0);
4346 assert(stream.data == "");
4347 stream.clear();
4348
4349 formattedWrite(stream, "%.g", .34);
4350 assert(stream.data == "0.3");
4351 stream.clear();
4352
4353 stream.clear(); formattedWrite(stream, "%.0g", .34);
4354 assert(stream.data == "0.3");
4355
4356 stream.clear(); formattedWrite(stream, "%.2g", .34);
4357 assert(stream.data == "0.34");
4358
4359 stream.clear(); formattedWrite(stream, "%0.0008f", 1e-08);
4360 assert(stream.data == "0.00000001");
4361
4362 stream.clear(); formattedWrite(stream, "%0.0008f", 1e-05);
4363 assert(stream.data == "0.00001000");
4364
4365 //return;
4366 //core.stdc.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr);
4367
4368 s = "helloworld";
4369 string r;
4370 stream.clear(); formattedWrite(stream, "%.2s", s[0 .. 5]);
4371 assert(stream.data == "he");
4372 stream.clear(); formattedWrite(stream, "%.20s", s[0 .. 5]);
4373 assert(stream.data == "hello");
4374 stream.clear(); formattedWrite(stream, "%8s", s[0 .. 5]);
4375 assert(stream.data == " hello");
4376
4377 byte[] arrbyte = new byte[4];
4378 arrbyte[0] = 100;
4379 arrbyte[1] = -99;
4380 arrbyte[3] = 0;
4381 stream.clear(); formattedWrite(stream, "%s", arrbyte);
4382 assert(stream.data == "[100, -99, 0, 0]", stream.data);
4383
4384 ubyte[] arrubyte = new ubyte[4];
4385 arrubyte[0] = 100;
4386 arrubyte[1] = 200;
4387 arrubyte[3] = 0;
4388 stream.clear(); formattedWrite(stream, "%s", arrubyte);
4389 assert(stream.data == "[100, 200, 0, 0]", stream.data);
4390
4391 short[] arrshort = new short[4];
4392 arrshort[0] = 100;
4393 arrshort[1] = -999;
4394 arrshort[3] = 0;
4395 stream.clear(); formattedWrite(stream, "%s", arrshort);
4396 assert(stream.data == "[100, -999, 0, 0]");
4397 stream.clear(); formattedWrite(stream, "%s",arrshort);
4398 assert(stream.data == "[100, -999, 0, 0]");
4399
4400 ushort[] arrushort = new ushort[4];
4401 arrushort[0] = 100;
4402 arrushort[1] = 20_000;
4403 arrushort[3] = 0;
4404 stream.clear(); formattedWrite(stream, "%s", arrushort);
4405 assert(stream.data == "[100, 20000, 0, 0]");
4406
4407 int[] arrint = new int[4];
4408 arrint[0] = 100;
4409 arrint[1] = -999;
4410 arrint[3] = 0;
4411 stream.clear(); formattedWrite(stream, "%s", arrint);
4412 assert(stream.data == "[100, -999, 0, 0]");
4413 stream.clear(); formattedWrite(stream, "%s",arrint);
4414 assert(stream.data == "[100, -999, 0, 0]");
4415
4416 long[] arrlong = new long[4];
4417 arrlong[0] = 100;
4418 arrlong[1] = -999;
4419 arrlong[3] = 0;
4420 stream.clear(); formattedWrite(stream, "%s", arrlong);
4421 assert(stream.data == "[100, -999, 0, 0]");
4422 stream.clear(); formattedWrite(stream, "%s",arrlong);
4423 assert(stream.data == "[100, -999, 0, 0]");
4424
4425 ulong[] arrulong = new ulong[4];
4426 arrulong[0] = 100;
4427 arrulong[1] = 999;
4428 arrulong[3] = 0;
4429 stream.clear(); formattedWrite(stream, "%s", arrulong);
4430 assert(stream.data == "[100, 999, 0, 0]");
4431
4432 string[] arr2 = new string[4];
4433 arr2[0] = "hello";
4434 arr2[1] = "world";
4435 arr2[3] = "foo";
4436 stream.clear(); formattedWrite(stream, "%s", arr2);
4437 assert(stream.data == `["hello", "world", "", "foo"]`, stream.data);
4438
4439 stream.clear(); formattedWrite(stream, "%.8d", 7);
4440 assert(stream.data == "00000007");
4441
4442 stream.clear(); formattedWrite(stream, "%.8x", 10);
4443 assert(stream.data == "0000000a");
4444
4445 stream.clear(); formattedWrite(stream, "%-3d", 7);
4446 assert(stream.data == "7 ");
4447
4448 stream.clear(); formattedWrite(stream, "%*d", -3, 7);
4449 assert(stream.data == "7 ");
4450
4451 stream.clear(); formattedWrite(stream, "%.*d", -3, 7);
4452 //writeln(stream.data);
4453 assert(stream.data == "7");
4454
4455 stream.clear(); formattedWrite(stream, "%s", "abc"c);
4456 assert(stream.data == "abc");
4457 stream.clear(); formattedWrite(stream, "%s", "def"w);
4458 assert(stream.data == "def", text(stream.data.length));
4459 stream.clear(); formattedWrite(stream, "%s", "ghi"d);
4460 assert(stream.data == "ghi");
4461
4462 here:
4463 @trusted void* deadBeef() { return cast(void*) 0xDEADBEEF; }
4464 stream.clear(); formattedWrite(stream, "%s", deadBeef());
4465 assert(stream.data == "DEADBEEF", stream.data);
4466
4467 stream.clear(); formattedWrite(stream, "%#x", 0xabcd);
4468 assert(stream.data == "0xabcd");
4469 stream.clear(); formattedWrite(stream, "%#X", 0xABCD);
4470 assert(stream.data == "0XABCD");
4471
4472 stream.clear(); formattedWrite(stream, "%#o", octal!12345);
4473 assert(stream.data == "012345");
4474 stream.clear(); formattedWrite(stream, "%o", 9);
4475 assert(stream.data == "11");
4476
4477 stream.clear(); formattedWrite(stream, "%+d", 123);
4478 assert(stream.data == "+123");
4479 stream.clear(); formattedWrite(stream, "%+d", -123);
4480 assert(stream.data == "-123");
4481 stream.clear(); formattedWrite(stream, "% d", 123);
4482 assert(stream.data == " 123");
4483 stream.clear(); formattedWrite(stream, "% d", -123);
4484 assert(stream.data == "-123");
4485
4486 stream.clear(); formattedWrite(stream, "%%");
4487 assert(stream.data == "%");
4488
4489 stream.clear(); formattedWrite(stream, "%d", true);
4490 assert(stream.data == "1");
4491 stream.clear(); formattedWrite(stream, "%d", false);
4492 assert(stream.data == "0");
4493
4494 stream.clear(); formattedWrite(stream, "%d", 'a');
4495 assert(stream.data == "97", stream.data);
4496 wchar wc = 'a';
4497 stream.clear(); formattedWrite(stream, "%d", wc);
4498 assert(stream.data == "97");
4499 dchar dc = 'a';
4500 stream.clear(); formattedWrite(stream, "%d", dc);
4501 assert(stream.data == "97");
4502
4503 byte b = byte.max;
4504 stream.clear(); formattedWrite(stream, "%x", b);
4505 assert(stream.data == "7f");
4506 stream.clear(); formattedWrite(stream, "%x", ++b);
4507 assert(stream.data == "80");
4508 stream.clear(); formattedWrite(stream, "%x", ++b);
4509 assert(stream.data == "81");
4510
4511 short sh = short.max;
4512 stream.clear(); formattedWrite(stream, "%x", sh);
4513 assert(stream.data == "7fff");
4514 stream.clear(); formattedWrite(stream, "%x", ++sh);
4515 assert(stream.data == "8000");
4516 stream.clear(); formattedWrite(stream, "%x", ++sh);
4517 assert(stream.data == "8001");
4518
4519 i = int.max;
4520 stream.clear(); formattedWrite(stream, "%x", i);
4521 assert(stream.data == "7fffffff");
4522 stream.clear(); formattedWrite(stream, "%x", ++i);
4523 assert(stream.data == "80000000");
4524 stream.clear(); formattedWrite(stream, "%x", ++i);
4525 assert(stream.data == "80000001");
4526
4527 stream.clear(); formattedWrite(stream, "%x", 10);
4528 assert(stream.data == "a");
4529 stream.clear(); formattedWrite(stream, "%X", 10);
4530 assert(stream.data == "A");
4531 stream.clear(); formattedWrite(stream, "%x", 15);
4532 assert(stream.data == "f");
4533 stream.clear(); formattedWrite(stream, "%X", 15);
4534 assert(stream.data == "F");
4535
4536 @trusted void ObjectTest()
4537 {
4538 Object c = null;
4539 stream.clear(); formattedWrite(stream, "%s", c);
4540 assert(stream.data == "null");
4541 }
4542 ObjectTest();
4543
4544 enum TestEnum
4545 {
4546 Value1, Value2
4547 }
4548 stream.clear(); formattedWrite(stream, "%s", TestEnum.Value2);
4549 assert(stream.data == "Value2", stream.data);
4550 stream.clear(); formattedWrite(stream, "%s", cast(TestEnum) 5);
4551 assert(stream.data == "cast(TestEnum)5", stream.data);
4552
4553 //immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]);
4554 //stream.clear(); formattedWrite(stream, "%s", aa.values);
4555 //core.stdc.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr);
4556 //assert(stream.data == "[[h,e,l,l,o],[b,e,t,t,y]]");
4557 //stream.clear(); formattedWrite(stream, "%s", aa);
4558 //assert(stream.data == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]");
4559
4560 static const dchar[] ds = ['a','b'];
4561 for (int j = 0; j < ds.length; ++j)
4562 {
4563 stream.clear(); formattedWrite(stream, " %d", ds[j]);
4564 if (j == 0)
4565 assert(stream.data == " 97");
4566 else
4567 assert(stream.data == " 98");
4568 }
4569
4570 stream.clear(); formattedWrite(stream, "%.-3d", 7);
4571 assert(stream.data == "7", ">" ~ stream.data ~ "<");
4572 }
4573
4574 @safe unittest
4575 {
4576 import std.array;
4577 import std.stdio;
4578
4579 immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]);
4580 assert(aa[3] == "hello");
4581 assert(aa[4] == "betty");
4582
4583 auto stream = appender!(char[])();
4584 alias AllNumerics =
4585 AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong,
4586 float, double, real);
4587 foreach (T; AllNumerics)
4588 {
4589 T value = 1;
4590 stream.clear();
4591 formattedWrite(stream, "%s", value);
4592 assert(stream.data == "1");
4593 }
4594
4595 stream.clear();
4596 formattedWrite(stream, "%s", aa);
4597 }
4598
4599 @system unittest
4600 {
4601 string s = "hello!124:34.5";
4602 string a;
4603 int b;
4604 double c;
4605 formattedRead(s, "%s!%s:%s", &a, &b, &c);
4606 assert(a == "hello" && b == 124 && c == 34.5);
4607 }
4608
4609 version (unittest)
4610 void formatReflectTest(T)(ref T val, string fmt, string formatted, string fn = __FILE__, size_t ln = __LINE__)
4611 {
4612 import core.exception : AssertError;
4613 import std.array : appender;
4614 auto w = appender!string();
4615 formattedWrite(w, fmt, val);
4616
4617 auto input = w.data;
4618 enforce!AssertError(
4619 input == formatted,
4620 input, fn, ln);
4621
4622 T val2;
4623 formattedRead(input, fmt, &val2);
4624 static if (isAssociativeArray!T)
4625 if (__ctfe)
4626 {
4627 alias aa1 = val;
4628 alias aa2 = val2;
4629 assert(aa1 == aa2);
4630
4631 assert(aa1.length == aa2.length);
4632
4633 assert(aa1.keys == aa2.keys);
4634
4635 assert(aa1.values == aa2.values);
4636 assert(aa1.values.length == aa2.values.length);
4637 foreach (i; 0 .. aa1.values.length)
4638 assert(aa1.values[i] == aa2.values[i]);
4639
4640 foreach (i, key; aa1.keys)
4641 assert(aa1.values[i] == aa1[key]);
4642 foreach (i, key; aa2.keys)
4643 assert(aa2.values[i] == aa2[key]);
4644 return;
4645 }
4646 enforce!AssertError(
4647 val == val2,
4648 input, fn, ln);
4649 }
4650
4651 version (unittest)
4652 void formatReflectTest(T)(ref T val, string fmt, string[] formatted, string fn = __FILE__, size_t ln = __LINE__)
4653 {
4654 import core.exception : AssertError;
4655 import std.array : appender;
4656 auto w = appender!string();
4657 formattedWrite(w, fmt, val);
4658
4659 auto input = w.data;
4660
4661 foreach (cur; formatted)
4662 {
4663 if (input == cur) return;
4664 }
4665 enforce!AssertError(
4666 false,
4667 input,
4668 fn,
4669 ln);
4670
4671 T val2;
4672 formattedRead(input, fmt, &val2);
4673 static if (isAssociativeArray!T)
4674 if (__ctfe)
4675 {
4676 alias aa1 = val;
4677 alias aa2 = val2;
4678 assert(aa1 == aa2);
4679
4680 assert(aa1.length == aa2.length);
4681
4682 assert(aa1.keys == aa2.keys);
4683
4684 assert(aa1.values == aa2.values);
4685 assert(aa1.values.length == aa2.values.length);
4686 foreach (i; 0 .. aa1.values.length)
4687 assert(aa1.values[i] == aa2.values[i]);
4688
4689 foreach (i, key; aa1.keys)
4690 assert(aa1.values[i] == aa1[key]);
4691 foreach (i, key; aa2.keys)
4692 assert(aa2.values[i] == aa2[key]);
4693 return;
4694 }
4695 enforce!AssertError(
4696 val == val2,
4697 input, fn, ln);
4698 }
4699
4700 @system unittest
4701 {
4702 void booleanTest()
4703 {
4704 auto b = true;
4705 formatReflectTest(b, "%s", `true`);
4706 formatReflectTest(b, "%b", `1`);
4707 formatReflectTest(b, "%o", `1`);
4708 formatReflectTest(b, "%d", `1`);
4709 formatReflectTest(b, "%u", `1`);
4710 formatReflectTest(b, "%x", `1`);
4711 }
4712
4713 void integerTest()
4714 {
4715 auto n = 127;
4716 formatReflectTest(n, "%s", `127`);
4717 formatReflectTest(n, "%b", `1111111`);
4718 formatReflectTest(n, "%o", `177`);
4719 formatReflectTest(n, "%d", `127`);
4720 formatReflectTest(n, "%u", `127`);
4721 formatReflectTest(n, "%x", `7f`);
4722 }
4723
4724 void floatingTest()
4725 {
4726 auto f = 3.14;
4727 formatReflectTest(f, "%s", `3.14`);
4728 version (MinGW)
4729 formatReflectTest(f, "%e", `3.140000e+000`);
4730 else
4731 formatReflectTest(f, "%e", `3.140000e+00`);
4732 formatReflectTest(f, "%f", `3.140000`);
4733 formatReflectTest(f, "%g", `3.14`);
4734 }
4735
4736 void charTest()
4737 {
4738 auto c = 'a';
4739 formatReflectTest(c, "%s", `a`);
4740 formatReflectTest(c, "%c", `a`);
4741 formatReflectTest(c, "%b", `1100001`);
4742 formatReflectTest(c, "%o", `141`);
4743 formatReflectTest(c, "%d", `97`);
4744 formatReflectTest(c, "%u", `97`);
4745 formatReflectTest(c, "%x", `61`);
4746 }
4747
4748 void strTest()
4749 {
4750 auto s = "hello";
4751 formatReflectTest(s, "%s", `hello`);
4752 formatReflectTest(s, "%(%c,%)", `h,e,l,l,o`);
4753 formatReflectTest(s, "%(%s,%)", `'h','e','l','l','o'`);
4754 formatReflectTest(s, "[%(<%c>%| $ %)]", `[<h> $ <e> $ <l> $ <l> $ <o>]`);
4755 }
4756
4757 void daTest()
4758 {
4759 auto a = [1,2,3,4];
4760 formatReflectTest(a, "%s", `[1, 2, 3, 4]`);
4761 formatReflectTest(a, "[%(%s; %)]", `[1; 2; 3; 4]`);
4762 formatReflectTest(a, "[%(<%s>%| $ %)]", `[<1> $ <2> $ <3> $ <4>]`);
4763 }
4764
4765 void saTest()
4766 {
4767 int[4] sa = [1,2,3,4];
4768 formatReflectTest(sa, "%s", `[1, 2, 3, 4]`);
4769 formatReflectTest(sa, "[%(%s; %)]", `[1; 2; 3; 4]`);
4770 formatReflectTest(sa, "[%(<%s>%| $ %)]", `[<1> $ <2> $ <3> $ <4>]`);
4771 }
4772
4773 void aaTest()
4774 {
4775 auto aa = [1:"hello", 2:"world"];
4776 formatReflectTest(aa, "%s", [`[1:"hello", 2:"world"]`, `[2:"world", 1:"hello"]`]);
4777 formatReflectTest(aa, "[%(%s->%s, %)]", [`[1->"hello", 2->"world"]`, `[2->"world", 1->"hello"]`]);
4778 formatReflectTest(aa, "{%([%s=%(%c%)]%|; %)}", [`{[1=hello]; [2=world]}`, `{[2=world]; [1=hello]}`]);
4779 }
4780
4781 import std.exception;
4782 assertCTFEable!(
4783 {
4784 booleanTest();
4785 integerTest();
4786 if (!__ctfe) floatingTest(); // snprintf
4787 charTest();
4788 strTest();
4789 daTest();
4790 saTest();
4791 aaTest();
4792 return true;
4793 });
4794 }
4795
4796 //------------------------------------------------------------------------------
4797 private void skipData(Range, Char)(ref Range input, const ref FormatSpec!Char spec)
4798 {
4799 import std.ascii : isDigit;
4800 import std.conv : text;
4801
4802 switch (spec.spec)
4803 {
4804 case 'c': input.popFront(); break;
4805 case 'd':
4806 if (input.front == '+' || input.front == '-') input.popFront();
4807 goto case 'u';
4808 case 'u':
4809 while (!input.empty && isDigit(input.front)) input.popFront();
4810 break;
4811 default:
4812 assert(false,
4813 text("Format specifier not understood: %", spec.spec));
4814 }
4815 }
4816
4817 private template acceptedSpecs(T)
4818 {
4819 static if (isIntegral!T) enum acceptedSpecs = "bdosuxX";
4820 else static if (isFloatingPoint!T) enum acceptedSpecs = "seEfgG";
4821 else static if (isSomeChar!T) enum acceptedSpecs = "bcdosuxX"; // integral + 'c'
4822 else enum acceptedSpecs = "";
4823 }
4824
4825 /**
4826 * Reads a value from the given _input range according to spec
4827 * and returns it as type `T`.
4828 *
4829 * Params:
4830 * T = the type to return
4831 * input = the _input range to read from
4832 * spec = the `FormatSpec` to use when reading from `input`
4833 * Returns:
4834 * A value from `input` of type `T`
4835 * Throws:
4836 * An `Exception` if `spec` cannot read a type `T`
4837 * See_Also:
4838 * $(REF parse, std, conv) and $(REF to, std, conv)
4839 */
4840 T unformatValue(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec)
4841 {
4842 return unformatValueImpl!T(input, spec);
4843 }
4844
4845 /// Booleans
4846 @safe pure unittest
4847 {
4848 auto str = "false";
4849 auto spec = singleSpec("%s");
4850 assert(unformatValue!bool(str, spec) == false);
4851
4852 str = "1";
4853 spec = singleSpec("%d");
4854 assert(unformatValue!bool(str, spec));
4855 }
4856
4857 /// Null values
4858 @safe pure unittest
4859 {
4860 auto str = "null";
4861 auto spec = singleSpec("%s");
4862 assert(str.unformatValue!(typeof(null))(spec) == null);
4863 }
4864
4865 /// Integrals
4866 @safe pure unittest
4867 {
4868 auto str = "123";
4869 auto spec = singleSpec("%s");
4870 assert(str.unformatValue!int(spec) == 123);
4871
4872 str = "ABC";
4873 spec = singleSpec("%X");
4874 assert(str.unformatValue!int(spec) == 2748);
4875
4876 str = "11610";
4877 spec = singleSpec("%o");
4878 assert(str.unformatValue!int(spec) == 5000);
4879 }
4880
4881 /// Floating point numbers
4882 @safe pure unittest
4883 {
4884 import std.math : approxEqual;
4885
4886 auto str = "123.456";
4887 auto spec = singleSpec("%s");
4888 assert(str.unformatValue!double(spec).approxEqual(123.456));
4889 }
4890
4891 /// Character input ranges
4892 @safe pure unittest
4893 {
4894 auto str = "aaa";
4895 auto spec = singleSpec("%s");
4896 assert(str.unformatValue!char(spec) == 'a');
4897
4898 // Using a numerical format spec reads a Unicode value from a string
4899 str = "65";
4900 spec = singleSpec("%d");
4901 assert(str.unformatValue!char(spec) == 'A');
4902
4903 str = "41";
4904 spec = singleSpec("%x");
4905 assert(str.unformatValue!char(spec) == 'A');
4906
4907 str = "10003";
4908 spec = singleSpec("%d");
4909 assert(str.unformatValue!dchar(spec) == '✓');
4910 }
4911
4912 /// Arrays and static arrays
4913 @safe pure unittest
4914 {
4915 string str = "aaa";
4916 auto spec = singleSpec("%s");
4917 assert(str.unformatValue!(dchar[])(spec) == "aaa"d);
4918
4919 str = "aaa";
4920 spec = singleSpec("%s");
4921 dchar[3] ret = ['a', 'a', 'a'];
4922 assert(str.unformatValue!(dchar[3])(spec) == ret);
4923
4924 str = "[1, 2, 3, 4]";
4925 spec = singleSpec("%s");
4926 assert(str.unformatValue!(int[])(spec) == [1, 2, 3, 4]);
4927
4928 str = "[1, 2, 3, 4]";
4929 spec = singleSpec("%s");
4930 int[4] ret2 = [1, 2, 3, 4];
4931 assert(str.unformatValue!(int[4])(spec) == ret2);
4932 }
4933
4934 /// Associative arrays
4935 @safe pure unittest
4936 {
4937 auto str = `["one": 1, "two": 2]`;
4938 auto spec = singleSpec("%s");
4939 assert(str.unformatValue!(int[string])(spec) == ["one": 1, "two": 2]);
4940 }
4941
4942 @safe pure unittest
4943 {
4944 // 7241
4945 string input = "a";
4946 auto spec = FormatSpec!char("%s");
4947 spec.readUpToNextSpec(input);
4948 auto result = unformatValue!(dchar[1])(input, spec);
4949 assert(result[0] == 'a');
4950 }
4951
4952 private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec)
4953 if (isInputRange!Range && is(Unqual!T == bool))
4954 {
4955 import std.algorithm.searching : find;
4956 import std.conv : parse, text;
4957
4958 if (spec.spec == 's') return parse!T(input);
4959
4960 enforce(find(acceptedSpecs!long, spec.spec).length,
4961 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
4962
4963 return unformatValue!long(input, spec) != 0;
4964 }
4965
4966 private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec)
4967 if (isInputRange!Range && is(T == typeof(null)))
4968 {
4969 import std.conv : parse, text;
4970 enforce(spec.spec == 's',
4971 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
4972
4973 return parse!T(input);
4974 }
4975
4976 /// ditto
4977 private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec)
4978 if (isInputRange!Range && isIntegral!T && !is(T == enum) && isSomeChar!(ElementType!Range))
4979 {
4980
4981 import std.algorithm.searching : find;
4982 import std.conv : parse, text;
4983
4984 if (spec.spec == 'r')
4985 {
4986 static if (is(Unqual!(ElementEncodingType!Range) == char)
4987 || is(Unqual!(ElementEncodingType!Range) == byte)
4988 || is(Unqual!(ElementEncodingType!Range) == ubyte))
4989 return rawRead!T(input);
4990 else
4991 throw new Exception("The raw read specifier %r may only be used with narrow strings and ranges of bytes.");
4992 }
4993
4994 enforce(find(acceptedSpecs!T, spec.spec).length,
4995 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
4996
4997 enforce(spec.width == 0, "Parsing integers with a width specification is not implemented"); // TODO
4998
4999 immutable uint base =
5000 spec.spec == 'x' || spec.spec == 'X' ? 16 :
5001 spec.spec == 'o' ? 8 :
5002 spec.spec == 'b' ? 2 :
5003 spec.spec == 's' || spec.spec == 'd' || spec.spec == 'u' ? 10 : 0;
5004 assert(base != 0);
5005
5006 return parse!T(input, base);
5007
5008 }
5009
5010 /// ditto
5011 private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec)
5012 if (isFloatingPoint!T && !is(T == enum) && isInputRange!Range
5013 && isSomeChar!(ElementType!Range)&& !is(Range == enum))
5014 {
5015 import std.algorithm.searching : find;
5016 import std.conv : parse, text;
5017
5018 if (spec.spec == 'r')
5019 {
5020 static if (is(Unqual!(ElementEncodingType!Range) == char)
5021 || is(Unqual!(ElementEncodingType!Range) == byte)
5022 || is(Unqual!(ElementEncodingType!Range) == ubyte))
5023 return rawRead!T(input);
5024 else
5025 throw new Exception("The raw read specifier %r may only be used with narrow strings and ranges of bytes.");
5026 }
5027
5028 enforce(find(acceptedSpecs!T, spec.spec).length,
5029 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
5030
5031 return parse!T(input);
5032 }
5033
5034 /// ditto
5035 private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec)
5036 if (isInputRange!Range && isSomeChar!T && !is(T == enum) && isSomeChar!(ElementType!Range))
5037 {
5038 import std.algorithm.searching : find;
5039 import std.conv : to, text;
5040 if (spec.spec == 's' || spec.spec == 'c')
5041 {
5042 auto result = to!T(input.front);
5043 input.popFront();
5044 return result;
5045 }
5046 enforce(find(acceptedSpecs!T, spec.spec).length,
5047 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
5048
5049 static if (T.sizeof == 1)
5050 return unformatValue!ubyte(input, spec);
5051 else static if (T.sizeof == 2)
5052 return unformatValue!ushort(input, spec);
5053 else static if (T.sizeof == 4)
5054 return unformatValue!uint(input, spec);
5055 else
5056 static assert(0);
5057 }
5058
5059 /// ditto
5060 private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec)
5061 if (isInputRange!Range && is(StringTypeOf!T) && !isAggregateType!T && !is(T == enum))
5062 {
5063 import std.conv : text;
5064
5065 if (spec.spec == '(')
5066 {
5067 return unformatRange!T(input, spec);
5068 }
5069 enforce(spec.spec == 's',
5070 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
5071
5072 static if (isStaticArray!T)
5073 {
5074 T result;
5075 auto app = result[];
5076 }
5077 else
5078 {
5079 import std.array : appender;
5080 auto app = appender!T();
5081 }
5082 if (spec.trailing.empty)
5083 {
5084 for (; !input.empty; input.popFront())
5085 {
5086 static if (isStaticArray!T)
5087 if (app.empty)
5088 break;
5089 app.put(input.front);
5090 }
5091 }
5092 else
5093 {
5094 immutable end = spec.trailing.front;
5095 for (; !input.empty && input.front != end; input.popFront())
5096 {
5097 static if (isStaticArray!T)
5098 if (app.empty)
5099 break;
5100 app.put(input.front);
5101 }
5102 }
5103 static if (isStaticArray!T)
5104 {
5105 enforce(app.empty, "need more input");
5106 return result;
5107 }
5108 else
5109 return app.data;
5110 }
5111
5112 /// ditto
5113 private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec)
5114 if (isInputRange!Range && isArray!T && !is(StringTypeOf!T) && !isAggregateType!T && !is(T == enum))
5115 {
5116 import std.conv : parse, text;
5117 if (spec.spec == '(')
5118 {
5119 return unformatRange!T(input, spec);
5120 }
5121 enforce(spec.spec == 's',
5122 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
5123
5124 return parse!T(input);
5125 }
5126
5127 /// ditto
5128 private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec)
5129 if (isInputRange!Range && isAssociativeArray!T && !is(T == enum))
5130 {
5131 import std.conv : parse, text;
5132 if (spec.spec == '(')
5133 {
5134 return unformatRange!T(input, spec);
5135 }
5136 enforce(spec.spec == 's',
5137 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
5138
5139 return parse!T(input);
5140 }
5141
5142 /**
5143 * Function that performs raw reading. Used by unformatValue
5144 * for integral and float types.
5145 */
5146 private T rawRead(T, Range)(ref Range input)
5147 if (is(Unqual!(ElementEncodingType!Range) == char)
5148 || is(Unqual!(ElementEncodingType!Range) == byte)
5149 || is(Unqual!(ElementEncodingType!Range) == ubyte))
5150 {
5151 union X
5152 {
5153 ubyte[T.sizeof] raw;
5154 T typed;
5155 }
5156 X x;
5157 foreach (i; 0 .. T.sizeof)
5158 {
5159 static if (isSomeString!Range)
5160 {
5161 x.raw[i] = input[0];
5162 input = input[1 .. $];
5163 }
5164 else
5165 {
5166 // TODO: recheck this
5167 x.raw[i] = input.front;
5168 input.popFront();
5169 }
5170 }
5171 return x.typed;
5172 }
5173
5174 //debug = unformatRange;
5175
5176 private T unformatRange(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec)
5177 in
5178 {
5179 assert(spec.spec == '(');
5180 }
5181 body
5182 {
5183 debug (unformatRange) printf("unformatRange:\n");
5184
5185 T result;
5186 static if (isStaticArray!T)
5187 {
5188 size_t i;
5189 }
5190
5191 const(Char)[] cont = spec.trailing;
5192 for (size_t j = 0; j < spec.trailing.length; ++j)
5193 {
5194 if (spec.trailing[j] == '%')
5195 {
5196 cont = spec.trailing[0 .. j];
5197 break;
5198 }
5199 }
5200 debug (unformatRange) printf("\t");
5201 debug (unformatRange) if (!input.empty) printf("input.front = %c, ", input.front);
5202 debug (unformatRange) printf("cont = %.*s\n", cast(int) cont.length, cont.ptr);
5203
5204 bool checkEnd()
5205 {
5206 return input.empty || !cont.empty && input.front == cont.front;
5207 }
5208
5209 if (!checkEnd())
5210 {
5211 for (;;)
5212 {
5213 auto fmt = FormatSpec!Char(spec.nested);
5214 fmt.readUpToNextSpec(input);
5215 enforce(!input.empty, "Unexpected end of input when parsing range");
5216
5217 debug (unformatRange) printf("\t) spec = %c, front = %c ", fmt.spec, input.front);
5218 static if (isStaticArray!T)
5219 {
5220 result[i++] = unformatElement!(typeof(T.init[0]))(input, fmt);
5221 }
5222 else static if (isDynamicArray!T)
5223 {
5224 result ~= unformatElement!(ElementType!T)(input, fmt);
5225 }
5226 else static if (isAssociativeArray!T)
5227 {
5228 auto key = unformatElement!(typeof(T.init.keys[0]))(input, fmt);
5229 fmt.readUpToNextSpec(input); // eat key separator
5230
5231 result[key] = unformatElement!(typeof(T.init.values[0]))(input, fmt);
5232 }
5233 debug (unformatRange) {
5234 if (input.empty) printf("-> front = [empty] ");
5235 else printf("-> front = %c ", input.front);
5236 }
5237
5238 static if (isStaticArray!T)
5239 {
5240 debug (unformatRange) printf("i = %u < %u\n", i, T.length);
5241 enforce(i <= T.length, "Too many format specifiers for static array of length %d".format(T.length));
5242 }
5243
5244 if (spec.sep !is null)
5245 fmt.readUpToNextSpec(input);
5246 auto sep = spec.sep !is null ? spec.sep
5247 : fmt.trailing;
5248 debug (unformatRange) {
5249 if (!sep.empty && !input.empty) printf("-> %c, sep = %.*s\n", input.front, cast(int) sep.length, sep.ptr);
5250 else printf("\n");
5251 }
5252
5253 if (checkEnd())
5254 break;
5255
5256 if (!sep.empty && input.front == sep.front)
5257 {
5258 while (!sep.empty)
5259 {
5260 enforce(!input.empty, "Unexpected end of input when parsing range separator");
5261 enforce(input.front == sep.front, "Unexpected character when parsing range separator");
5262 input.popFront();
5263 sep.popFront();
5264 }
5265 debug (unformatRange) printf("input.front = %c\n", input.front);
5266 }
5267 }
5268 }
5269 static if (isStaticArray!T)
5270 {
5271 enforce(i == T.length, "Too few (%d) format specifiers for static array of length %d".format(i, T.length));
5272 }
5273 return result;
5274 }
5275
5276 // Undocumented
5277 T unformatElement(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec)
5278 if (isInputRange!Range)
5279 {
5280 import std.conv : parseElement;
5281 static if (isSomeString!T)
5282 {
5283 if (spec.spec == 's')
5284 {
5285 return parseElement!T(input);
5286 }
5287 }
5288 else static if (isSomeChar!T)
5289 {
5290 if (spec.spec == 's')
5291 {
5292 return parseElement!T(input);
5293 }
5294 }
5295
5296 return unformatValue!T(input, spec);
5297 }
5298
5299
5300 // Legacy implementation
5301
5302 enum Mangle : char
5303 {
5304 Tvoid = 'v',
5305 Tbool = 'b',
5306 Tbyte = 'g',
5307 Tubyte = 'h',
5308 Tshort = 's',
5309 Tushort = 't',
5310 Tint = 'i',
5311 Tuint = 'k',
5312 Tlong = 'l',
5313 Tulong = 'm',
5314 Tfloat = 'f',
5315 Tdouble = 'd',
5316 Treal = 'e',
5317
5318 Tifloat = 'o',
5319 Tidouble = 'p',
5320 Tireal = 'j',
5321 Tcfloat = 'q',
5322 Tcdouble = 'r',
5323 Tcreal = 'c',
5324
5325 Tchar = 'a',
5326 Twchar = 'u',
5327 Tdchar = 'w',
5328
5329 Tarray = 'A',
5330 Tsarray = 'G',
5331 Taarray = 'H',
5332 Tpointer = 'P',
5333 Tfunction = 'F',
5334 Tident = 'I',
5335 Tclass = 'C',
5336 Tstruct = 'S',
5337 Tenum = 'E',
5338 Ttypedef = 'T',
5339 Tdelegate = 'D',
5340
5341 Tconst = 'x',
5342 Timmutable = 'y',
5343 }
5344
5345 // return the TypeInfo for a primitive type and null otherwise. This
5346 // is required since for arrays of ints we only have the mangled char
5347 // to work from. If arrays always subclassed TypeInfo_Array this
5348 // routine could go away.
5349 private TypeInfo primitiveTypeInfo(Mangle m)
5350 {
5351 // BUG: should fix this in static this() to avoid double checked locking bug
5352 __gshared TypeInfo[Mangle] dic;
5353 if (!dic.length)
5354 {
5355 dic = [
5356 Mangle.Tvoid : typeid(void),
5357 Mangle.Tbool : typeid(bool),
5358 Mangle.Tbyte : typeid(byte),
5359 Mangle.Tubyte : typeid(ubyte),
5360 Mangle.Tshort : typeid(short),
5361 Mangle.Tushort : typeid(ushort),
5362 Mangle.Tint : typeid(int),
5363 Mangle.Tuint : typeid(uint),
5364 Mangle.Tlong : typeid(long),
5365 Mangle.Tulong : typeid(ulong),
5366 Mangle.Tfloat : typeid(float),
5367 Mangle.Tdouble : typeid(double),
5368 Mangle.Treal : typeid(real),
5369 Mangle.Tifloat : typeid(ifloat),
5370 Mangle.Tidouble : typeid(idouble),
5371 Mangle.Tireal : typeid(ireal),
5372 Mangle.Tcfloat : typeid(cfloat),
5373 Mangle.Tcdouble : typeid(cdouble),
5374 Mangle.Tcreal : typeid(creal),
5375 Mangle.Tchar : typeid(char),
5376 Mangle.Twchar : typeid(wchar),
5377 Mangle.Tdchar : typeid(dchar)
5378 ];
5379 }
5380 auto p = m in dic;
5381 return p ? *p : null;
5382 }
5383
5384 private bool needToSwapEndianess(Char)(const ref FormatSpec!Char f)
5385 {
5386 import std.system : endian, Endian;
5387
5388 return endian == Endian.littleEndian && f.flPlus
5389 || endian == Endian.bigEndian && f.flDash;
5390 }
5391
5392 /* ======================== Unit Tests ====================================== */
5393
5394 @system unittest
5395 {
5396 import std.conv : octal;
5397
5398 int i;
5399 string s;
5400
5401 debug(format) printf("std.format.format.unittest\n");
5402
5403 s = format("hello world! %s %s %s%s%s", true, 57, 1_000_000_000, 'x', " foo");
5404 assert(s == "hello world! true 57 1000000000x foo");
5405
5406 s = format("%s %A %s", 1.67, -1.28, float.nan);
5407 /* The host C library is used to format floats.
5408 * C99 doesn't specify what the hex digit before the decimal point
5409 * is for %A.
5410 */
5411 //version (linux)
5412 // assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan");
5413 //else version (OSX)
5414 // assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan", s);
5415 //else
5416 version (MinGW)
5417 assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan", s);
5418 else version (CRuntime_Microsoft)
5419 assert(s == "1.67 -0X1.47AE14P+0 nan"
5420 || s == "1.67 -0X1.47AE147AE147BP+0 nan", s); // MSVCRT 14+ (VS 2015)
5421 else
5422 assert(s == "1.67 -0X1.47AE147AE147BP+0 nan", s);
5423
5424 s = format("%x %X", 0x1234AF, 0xAFAFAFAF);
5425 assert(s == "1234af AFAFAFAF");
5426
5427 s = format("%b %o", 0x1234AF, 0xAFAFAFAF);
5428 assert(s == "100100011010010101111 25753727657");
5429
5430 s = format("%d %s", 0x1234AF, 0xAFAFAFAF);
5431 assert(s == "1193135 2947526575");
5432
5433 //version (X86_64)
5434 //{
5435 // pragma(msg, "several format tests disabled on x86_64 due to bug 5625");
5436 //}
5437 //else
5438 //{
5439 s = format("%s", 1.2 + 3.4i);
5440 assert(s == "1.2+3.4i", s);
5441
5442 //s = format("%x %X", 1.32, 6.78f);
5443 //assert(s == "3ff51eb851eb851f 40D8F5C3");
5444
5445 //}
5446
5447 s = format("%#06.*f",2,12.345);
5448 assert(s == "012.35");
5449
5450 s = format("%#0*.*f",6,2,12.345);
5451 assert(s == "012.35");
5452
5453 s = format("%7.4g:", 12.678);
5454 assert(s == " 12.68:");
5455
5456 s = format("%7.4g:", 12.678L);
5457 assert(s == " 12.68:");
5458
5459 s = format("%04f|%05d|%#05x|%#5x",-4.0,-10,1,1);
5460 assert(s == "-4.000000|-0010|0x001| 0x1");
5461
5462 i = -10;
5463 s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
5464 assert(s == "-10|-10|-10|-10|-10.0000");
5465
5466 i = -5;
5467 s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
5468 assert(s == "-5| -5|-05|-5|-5.0000");
5469
5470 i = 0;
5471 s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
5472 assert(s == "0| 0|000|0|0.0000");
5473
5474 i = 5;
5475 s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
5476 assert(s == "5| 5|005|5|5.0000");
5477
5478 i = 10;
5479 s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
5480 assert(s == "10| 10|010|10|10.0000");
5481
5482 s = format("%.0d", 0);
5483 assert(s == "");
5484
5485 s = format("%.g", .34);
5486 assert(s == "0.3");
5487
5488 s = format("%.0g", .34);
5489 assert(s == "0.3");
5490
5491 s = format("%.2g", .34);
5492 assert(s == "0.34");
5493
5494 s = format("%0.0008f", 1e-08);
5495 assert(s == "0.00000001");
5496
5497 s = format("%0.0008f", 1e-05);
5498 assert(s == "0.00001000");
5499
5500 s = "helloworld";
5501 string r;
5502 r = format("%.2s", s[0 .. 5]);
5503 assert(r == "he");
5504 r = format("%.20s", s[0 .. 5]);
5505 assert(r == "hello");
5506 r = format("%8s", s[0 .. 5]);
5507 assert(r == " hello");
5508
5509 byte[] arrbyte = new byte[4];
5510 arrbyte[0] = 100;
5511 arrbyte[1] = -99;
5512 arrbyte[3] = 0;
5513 r = format("%s", arrbyte);
5514 assert(r == "[100, -99, 0, 0]");
5515
5516 ubyte[] arrubyte = new ubyte[4];
5517 arrubyte[0] = 100;
5518 arrubyte[1] = 200;
5519 arrubyte[3] = 0;
5520 r = format("%s", arrubyte);
5521 assert(r == "[100, 200, 0, 0]");
5522
5523 short[] arrshort = new short[4];
5524 arrshort[0] = 100;
5525 arrshort[1] = -999;
5526 arrshort[3] = 0;
5527 r = format("%s", arrshort);
5528 assert(r == "[100, -999, 0, 0]");
5529
5530 ushort[] arrushort = new ushort[4];
5531 arrushort[0] = 100;
5532 arrushort[1] = 20_000;
5533 arrushort[3] = 0;
5534 r = format("%s", arrushort);
5535 assert(r == "[100, 20000, 0, 0]");
5536
5537 int[] arrint = new int[4];
5538 arrint[0] = 100;
5539 arrint[1] = -999;
5540 arrint[3] = 0;
5541 r = format("%s", arrint);
5542 assert(r == "[100, -999, 0, 0]");
5543
5544 long[] arrlong = new long[4];
5545 arrlong[0] = 100;
5546 arrlong[1] = -999;
5547 arrlong[3] = 0;
5548 r = format("%s", arrlong);
5549 assert(r == "[100, -999, 0, 0]");
5550
5551 ulong[] arrulong = new ulong[4];
5552 arrulong[0] = 100;
5553 arrulong[1] = 999;
5554 arrulong[3] = 0;
5555 r = format("%s", arrulong);
5556 assert(r == "[100, 999, 0, 0]");
5557
5558 string[] arr2 = new string[4];
5559 arr2[0] = "hello";
5560 arr2[1] = "world";
5561 arr2[3] = "foo";
5562 r = format("%s", arr2);
5563 assert(r == `["hello", "world", "", "foo"]`);
5564
5565 r = format("%.8d", 7);
5566 assert(r == "00000007");
5567 r = format("%.8x", 10);
5568 assert(r == "0000000a");
5569
5570 r = format("%-3d", 7);
5571 assert(r == "7 ");
5572
5573 r = format("%-1*d", 4, 3);
5574 assert(r == "3 ");
5575
5576 r = format("%*d", -3, 7);
5577 assert(r == "7 ");
5578
5579 r = format("%.*d", -3, 7);
5580 assert(r == "7");
5581
5582 r = format("%-1.*f", 2, 3.1415);
5583 assert(r == "3.14");
5584
5585 r = format("abc"c);
5586 assert(r == "abc");
5587
5588 //format() returns the same type as inputted.
5589 wstring wr;
5590 wr = format("def"w);
5591 assert(wr == "def"w);
5592
5593 dstring dr;
5594 dr = format("ghi"d);
5595 assert(dr == "ghi"d);
5596
5597 void* p = cast(void*) 0xDEADBEEF;
5598 r = format("%s", p);
5599 assert(r == "DEADBEEF");
5600
5601 r = format("%#x", 0xabcd);
5602 assert(r == "0xabcd");
5603 r = format("%#X", 0xABCD);
5604 assert(r == "0XABCD");
5605
5606 r = format("%#o", octal!12345);
5607 assert(r == "012345");
5608 r = format("%o", 9);
5609 assert(r == "11");
5610 r = format("%#o", 0); // issue 15663
5611 assert(r == "0");
5612
5613 r = format("%+d", 123);
5614 assert(r == "+123");
5615 r = format("%+d", -123);
5616 assert(r == "-123");
5617 r = format("% d", 123);
5618 assert(r == " 123");
5619 r = format("% d", -123);
5620 assert(r == "-123");
5621
5622 r = format("%%");
5623 assert(r == "%");
5624
5625 r = format("%d", true);
5626 assert(r == "1");
5627 r = format("%d", false);
5628 assert(r == "0");
5629
5630 r = format("%d", 'a');
5631 assert(r == "97");
5632 wchar wc = 'a';
5633 r = format("%d", wc);
5634 assert(r == "97");
5635 dchar dc = 'a';
5636 r = format("%d", dc);
5637 assert(r == "97");
5638
5639 byte b = byte.max;
5640 r = format("%x", b);
5641 assert(r == "7f");
5642 r = format("%x", ++b);
5643 assert(r == "80");
5644 r = format("%x", ++b);
5645 assert(r == "81");
5646
5647 short sh = short.max;
5648 r = format("%x", sh);
5649 assert(r == "7fff");
5650 r = format("%x", ++sh);
5651 assert(r == "8000");
5652 r = format("%x", ++sh);
5653 assert(r == "8001");
5654
5655 i = int.max;
5656 r = format("%x", i);
5657 assert(r == "7fffffff");
5658 r = format("%x", ++i);
5659 assert(r == "80000000");
5660 r = format("%x", ++i);
5661 assert(r == "80000001");
5662
5663 r = format("%x", 10);
5664 assert(r == "a");
5665 r = format("%X", 10);
5666 assert(r == "A");
5667 r = format("%x", 15);
5668 assert(r == "f");
5669 r = format("%X", 15);
5670 assert(r == "F");
5671
5672 Object c = null;
5673 r = format("%s", c);
5674 assert(r == "null");
5675
5676 enum TestEnum
5677 {
5678 Value1, Value2
5679 }
5680 r = format("%s", TestEnum.Value2);
5681 assert(r == "Value2");
5682
5683 immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]);
5684 r = format("%s", aa.values);
5685 assert(r == `["hello", "betty"]` || r == `["betty", "hello"]`);
5686 r = format("%s", aa);
5687 assert(r == `[3:"hello", 4:"betty"]` || r == `[4:"betty", 3:"hello"]`);
5688
5689 static const dchar[] ds = ['a','b'];
5690 for (int j = 0; j < ds.length; ++j)
5691 {
5692 r = format(" %d", ds[j]);
5693 if (j == 0)
5694 assert(r == " 97");
5695 else
5696 assert(r == " 98");
5697 }
5698
5699 r = format(">%14d<, %s", 15, [1,2,3]);
5700 assert(r == "> 15<, [1, 2, 3]");
5701
5702 assert(format("%8s", "bar") == " bar");
5703 assert(format("%8s", "b\u00e9ll\u00f4") == " b\u00e9ll\u00f4");
5704 }
5705
5706 @safe unittest
5707 {
5708 // bugzilla 3479
5709 import std.array;
5710 auto stream = appender!(char[])();
5711 formattedWrite(stream, "%2$.*1$d", 12, 10);
5712 assert(stream.data == "000000000010", stream.data);
5713 }
5714
5715 @safe unittest
5716 {
5717 // bug 6893
5718 import std.array;
5719 enum E : ulong { A, B, C }
5720 auto stream = appender!(char[])();
5721 formattedWrite(stream, "%s", E.C);
5722 assert(stream.data == "C");
5723 }
5724
5725 // Used to check format strings are compatible with argument types
5726 package static const checkFormatException(alias fmt, Args...) =
5727 {
5728 try
5729 .format(fmt, Args.init);
5730 catch (Exception e)
5731 return (e.msg == ctfpMessage) ? null : e;
5732 return null;
5733 }();
5734
5735 /*****************************************************
5736 * Format arguments into a string.
5737 *
5738 * Params: fmt = Format string. For detailed specification, see $(LREF formattedWrite).
5739 * args = Variadic list of arguments to _format into returned string.
5740 */
5741 typeof(fmt) format(alias fmt, Args...)(Args args)
5742 if (isSomeString!(typeof(fmt)))
5743 {
5744 alias e = checkFormatException!(fmt, Args);
5745 static assert(!e, e.msg);
5746 return .format(fmt, args);
5747 }
5748
5749 /// Type checking can be done when fmt is known at compile-time:
5750 @safe unittest
5751 {
5752 auto s = format!"%s is %s"("Pi", 3.14);
5753 assert(s == "Pi is 3.14");
5754
5755 static assert(!__traits(compiles, {s = format!"%l"();})); // missing arg
5756 static assert(!__traits(compiles, {s = format!""(404);})); // surplus arg
5757 static assert(!__traits(compiles, {s = format!"%d"(4.03);})); // incompatible arg
5758 }
5759
5760 /// ditto
5761 immutable(Char)[] format(Char, Args...)(in Char[] fmt, Args args)
5762 if (isSomeChar!Char)
5763 {
5764 import std.array : appender;
5765 import std.format : formattedWrite, FormatException;
5766 auto w = appender!(immutable(Char)[]);
5767 auto n = formattedWrite(w, fmt, args);
5768 version (all)
5769 {
5770 // In the future, this check will be removed to increase consistency
5771 // with formattedWrite
5772 import std.conv : text;
5773 import std.exception : enforce;
5774 enforce(n == args.length, new FormatException(
5775 text("Orphan format arguments: args[", n, "..", args.length, "]")));
5776 }
5777 return w.data;
5778 }
5779
5780 @safe pure unittest
5781 {
5782 import core.exception;
5783 import std.exception;
5784 import std.format;
5785 assertCTFEable!(
5786 {
5787 // assert(format(null) == "");
5788 assert(format("foo") == "foo");
5789 assert(format("foo%%") == "foo%");
5790 assert(format("foo%s", 'C') == "fooC");
5791 assert(format("%s foo", "bar") == "bar foo");
5792 assert(format("%s foo %s", "bar", "abc") == "bar foo abc");
5793 assert(format("foo %d", -123) == "foo -123");
5794 assert(format("foo %d", 123) == "foo 123");
5795
5796 assertThrown!FormatException(format("foo %s"));
5797 assertThrown!FormatException(format("foo %s", 123, 456));
5798
5799 assert(format("hel%slo%s%s%s", "world", -138, 'c', true) ==
5800 "helworldlo-138ctrue");
5801 });
5802
5803 assert(is(typeof(format("happy")) == string));
5804 assert(is(typeof(format("happy"w)) == wstring));
5805 assert(is(typeof(format("happy"d)) == dstring));
5806 }
5807
5808 // https://issues.dlang.org/show_bug.cgi?id=16661
5809 @safe unittest
5810 {
5811 assert(format("%.2f"d, 0.4) == "0.40");
5812 assert("%02d"d.format(1) == "01"d);
5813 }
5814
5815 /*****************************************************
5816 * Format arguments into buffer $(I buf) which must be large
5817 * enough to hold the result.
5818 *
5819 * Returns:
5820 * The slice of `buf` containing the formatted string.
5821 *
5822 * Throws:
5823 * A `RangeError` if `buf` isn't large enough to hold the
5824 * formatted string.
5825 *
5826 * A $(LREF FormatException) if the length of `args` is different
5827 * than the number of format specifiers in `fmt`.
5828 */
5829 char[] sformat(alias fmt, Args...)(char[] buf, Args args)
5830 if (isSomeString!(typeof(fmt)))
5831 {
5832 alias e = checkFormatException!(fmt, Args);
5833 static assert(!e, e.msg);
5834 return .sformat(buf, fmt, args);
5835 }
5836
5837 /// ditto
5838 char[] sformat(Char, Args...)(char[] buf, in Char[] fmt, Args args)
5839 {
5840 import core.exception : RangeError;
5841 import std.format : formattedWrite, FormatException;
5842 import std.utf : encode;
5843
5844 size_t i;
5845
5846 struct Sink
5847 {
5848 void put(dchar c)
5849 {
5850 char[4] enc;
5851 auto n = encode(enc, c);
5852
5853 if (buf.length < i + n)
5854 throw new RangeError(__FILE__, __LINE__);
5855
5856 buf[i .. i + n] = enc[0 .. n];
5857 i += n;
5858 }
5859 void put(const(char)[] s)
5860 {
5861 if (buf.length < i + s.length)
5862 throw new RangeError(__FILE__, __LINE__);
5863
5864 buf[i .. i + s.length] = s[];
5865 i += s.length;
5866 }
5867 void put(const(wchar)[] s)
5868 {
5869 for (; !s.empty; s.popFront())
5870 put(s.front);
5871 }
5872 void put(const(dchar)[] s)
5873 {
5874 for (; !s.empty; s.popFront())
5875 put(s.front);
5876 }
5877 }
5878 auto n = formattedWrite(Sink(), fmt, args);
5879 version (all)
5880 {
5881 // In the future, this check will be removed to increase consistency
5882 // with formattedWrite
5883 import std.conv : text;
5884 import std.exception : enforce;
5885 enforce!FormatException(
5886 n == args.length,
5887 text("Orphan format arguments: args[", n, " .. ", args.length, "]")
5888 );
5889 }
5890 return buf[0 .. i];
5891 }
5892
5893 /// The format string can be checked at compile-time (see $(LREF format) for details):
5894 @system unittest
5895 {
5896 char[10] buf;
5897
5898 assert(buf[].sformat!"foo%s"('C') == "fooC");
5899 assert(sformat(buf[], "%s foo", "bar") == "bar foo");
5900 }
5901
5902 @system unittest
5903 {
5904 import core.exception;
5905 import std.format;
5906
5907 debug(string) trustedPrintf("std.string.sformat.unittest\n");
5908
5909 import std.exception;
5910 assertCTFEable!(
5911 {
5912 char[10] buf;
5913
5914 assert(sformat(buf[], "foo") == "foo");
5915 assert(sformat(buf[], "foo%%") == "foo%");
5916 assert(sformat(buf[], "foo%s", 'C') == "fooC");
5917 assert(sformat(buf[], "%s foo", "bar") == "bar foo");
5918 assertThrown!RangeError(sformat(buf[], "%s foo %s", "bar", "abc"));
5919 assert(sformat(buf[], "foo %d", -123) == "foo -123");
5920 assert(sformat(buf[], "foo %d", 123) == "foo 123");
5921
5922 assertThrown!FormatException(sformat(buf[], "foo %s"));
5923 assertThrown!FormatException(sformat(buf[], "foo %s", 123, 456));
5924
5925 assert(sformat(buf[], "%s %s %s", "c"c, "w"w, "d"d) == "c w d");
5926 });
5927 }
5928
5929 /*****************************
5930 * The .ptr is unsafe because it could be dereferenced and the length of the array may be 0.
5931 * Returns:
5932 * the difference between the starts of the arrays
5933 */
5934 @trusted private pure nothrow @nogc
5935 ptrdiff_t arrayPtrDiff(T)(const T[] array1, const T[] array2)
5936 {
5937 return array1.ptr - array2.ptr;
5938 }
5939
5940 @safe unittest
5941 {
5942 assertCTFEable!({
5943 auto tmp = format("%,d", 1000);
5944 assert(tmp == "1,000", "'" ~ tmp ~ "'");
5945
5946 tmp = format("%,?d", 'z', 1234567);
5947 assert(tmp == "1z234z567", "'" ~ tmp ~ "'");
5948
5949 tmp = format("%10,?d", 'z', 1234567);
5950 assert(tmp == " 1z234z567", "'" ~ tmp ~ "'");
5951
5952 tmp = format("%11,2?d", 'z', 1234567);
5953 assert(tmp == " 1z23z45z67", "'" ~ tmp ~ "'");
5954
5955 tmp = format("%11,*?d", 2, 'z', 1234567);
5956 assert(tmp == " 1z23z45z67", "'" ~ tmp ~ "'");
5957
5958 tmp = format("%11,*d", 2, 1234567);
5959 assert(tmp == " 1,23,45,67", "'" ~ tmp ~ "'");
5960
5961 tmp = format("%11,2d", 1234567);
5962 assert(tmp == " 1,23,45,67", "'" ~ tmp ~ "'");
5963 });
5964 }
5965
5966 @safe unittest
5967 {
5968 auto tmp = format("%,f", 1000.0);
5969 assert(tmp == "1,000.000,000", "'" ~ tmp ~ "'");
5970
5971 tmp = format("%,f", 1234567.891011);
5972 assert(tmp == "1,234,567.891,011", "'" ~ tmp ~ "'");
5973
5974 tmp = format("%,f", -1234567.891011);
5975 assert(tmp == "-1,234,567.891,011", "'" ~ tmp ~ "'");
5976
5977 tmp = format("%,2f", 1234567.891011);
5978 assert(tmp == "1,23,45,67.89,10,11", "'" ~ tmp ~ "'");
5979
5980 tmp = format("%18,f", 1234567.891011);
5981 assert(tmp == " 1,234,567.891,011", "'" ~ tmp ~ "'");
5982
5983 tmp = format("%18,?f", '.', 1234567.891011);
5984 assert(tmp == " 1.234.567.891.011", "'" ~ tmp ~ "'");
5985
5986 tmp = format("%,?.3f", 'ä', 1234567.891011);
5987 assert(tmp == "1ä234ä567.891", "'" ~ tmp ~ "'");
5988
5989 tmp = format("%,*?.3f", 1, 'ä', 1234567.891011);
5990 assert(tmp == "1ä2ä3ä4ä5ä6ä7.8ä9ä1", "'" ~ tmp ~ "'");
5991
5992 tmp = format("%,4?.3f", '_', 1234567.891011);
5993 assert(tmp == "123_4567.891", "'" ~ tmp ~ "'");
5994
5995 tmp = format("%12,3.3f", 1234.5678);
5996 assert(tmp == " 1,234.568", "'" ~ tmp ~ "'");
5997
5998 tmp = format("%,e", 3.141592653589793238462);
5999 assert(tmp == "3.141,593e+00", "'" ~ tmp ~ "'");
6000
6001 tmp = format("%15,e", 3.141592653589793238462);
6002 assert(tmp == " 3.141,593e+00", "'" ~ tmp ~ "'");
6003
6004 tmp = format("%15,e", -3.141592653589793238462);
6005 assert(tmp == " -3.141,593e+00", "'" ~ tmp ~ "'");
6006
6007 tmp = format("%.4,*e", 2, 3.141592653589793238462);
6008 assert(tmp == "3.14,16e+00", "'" ~ tmp ~ "'");
6009
6010 tmp = format("%13.4,*e", 2, 3.141592653589793238462);
6011 assert(tmp == " 3.14,16e+00", "'" ~ tmp ~ "'");
6012
6013 tmp = format("%,.0f", 3.14);
6014 assert(tmp == "3", "'" ~ tmp ~ "'");
6015
6016 tmp = format("%3,g", 1_000_000.123456);
6017 assert(tmp == "1e+06", "'" ~ tmp ~ "'");
6018
6019 tmp = format("%19,?f", '.', -1234567.891011);
6020 assert(tmp == " -1.234.567.891.011", "'" ~ tmp ~ "'");
6021 }
6022
6023 // Test for multiple indexes
6024 @safe unittest
6025 {
6026 auto tmp = format("%2:5$s", 1, 2, 3, 4, 5);
6027 assert(tmp == "2345", tmp);
6028 }
6029