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 `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 The D Language Foundation 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 @nogc pure nothrow
73     this()
74     {
75         super("format error");
76     }
77 
78     @safe @nogc 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 ///
86 @safe unittest
87 {
88     import std.exception : assertThrown;
89     assertThrown!FormatException(format("%d", "foo"));
90 }
91 
92 private alias enforceFmt = enforce!FormatException;
93 
94 
95 /**********************************************************************
96    Interprets variadic argument list `args`, formats them according
97    to `fmt`, and sends the resulting characters to `w`. The
98    encoding of the output is the same as `Char`. The type `Writer`
99    must satisfy $(REF isOutputRange, std,range,primitives)!(Writer, Char).
100 
101    The variadic arguments are normally consumed in order. POSIX-style
102    $(HTTP opengroup.org/onlinepubs/009695399/functions/printf.html,
103    positional parameter syntax) is also supported. Each argument is
104    formatted into a sequence of chars according to the format
105    specification, and the characters are passed to `w`. As many
106    arguments as specified in the format string are consumed and
107    formatted. If there are fewer arguments than format specifiers, a
108    `FormatException` is thrown. If there are more remaining arguments
109    than needed by the format specification, they are ignored but only
110    if at least one argument was formatted.
111 
112    The format string supports the formatting of array and nested array elements
113    via the grouping format specifiers $(B %$(LPAREN)) and $(B %$(RPAREN)). Each
114    matching pair of $(B %$(LPAREN)) and $(B %$(RPAREN)) corresponds with a single array
115    argument. The enclosed sub-format string is applied to individual array
116    elements.  The trailing portion of the sub-format string following the
117    conversion specifier for the array element is interpreted as the array
118    delimiter, and is therefore omitted following the last array element. The
119    $(B %|) specifier may be used to explicitly indicate the start of the
120    delimiter, so that the preceding portion of the string will be included
121    following the last array element.  (See below for explicit examples.)
122 
123    Params:
124 
125    w = Output is sent to this writer. Typical output writers include
126    $(REF Appender!string, std,array) and $(REF LockingTextWriter, std,stdio).
127 
128    fmt = Format string.
129 
130    args = Variadic argument list.
131 
132    Returns: Formatted number of arguments.
133 
134    Throws: Mismatched arguments and formats result in a $(D
135    FormatException) being thrown.
136 
137    Format_String: $(I Format strings)
138    consist of characters interspersed with $(I format
139    specifications). Characters are simply copied to the output (such
140    as putc) after any necessary conversion to the corresponding UTF-8
141    sequence.
142 
143    The format string has the following grammar:
144 
145 $(PRE
146 $(I FormatString):
147     $(I FormatStringItem)*
148 $(I FormatStringItem):
149     $(B '%%')
150     $(B '%') $(I Position) $(I Flags) $(I Width) $(I Separator) $(I Precision) $(I FormatChar)
151     $(B '%$(LPAREN)') $(I FormatString) $(B '%$(RPAREN)')
152     $(B '%-$(LPAREN)') $(I FormatString) $(B '%$(RPAREN)')
153     $(I OtherCharacterExceptPercent)
154 $(I Position):
155     $(I empty)
156     $(I Integer) $(B '$')
157 $(I Flags):
158     $(I empty)
159     $(B '-') $(I Flags)
160     $(B '+') $(I Flags)
161     $(B '#') $(I Flags)
162     $(B '0') $(I Flags)
163     $(B ' ') $(I Flags)
164 $(I Width):
165     $(I empty)
166     $(I Integer)
167     $(B '*')
168 $(I Separator):
169     $(I empty)
170     $(B ',')
171     $(B ',') $(B '?')
172     $(B ',') $(B '*') $(B '?')
173     $(B ',') $(I Integer) $(B '?')
174     $(B ',') $(B '*')
175     $(B ',') $(I Integer)
176 $(I Precision):
177     $(I empty)
178     $(B '.')
179     $(B '.') $(I Integer)
180     $(B '.*')
181 $(I Integer):
182     $(I Digit)
183     $(I Digit) $(I Integer)
184 $(I Digit):
185     $(B '0')|$(B '1')|$(B '2')|$(B '3')|$(B '4')|$(B '5')|$(B '6')|$(B '7')|$(B '8')|$(B '9')
186 $(I FormatChar):
187     $(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 '|')
188 )
189 
190     $(BOOKTABLE Flags affect formatting depending on the specifier as
191     follows., $(TR $(TH Flag) $(TH Types affected) $(TH Semantics))
192 
193     $(TR $(TD $(B '-')) $(TD numeric, bool, null, char, string, enum, pointer) $(TD Left justify the result in
194         the field.  It overrides any $(B 0) flag.))
195 
196     $(TR $(TD $(B '+')) $(TD numeric) $(TD Prefix positive numbers in
197     a signed conversion with a $(B +).  It overrides any $(I space)
198     flag.))
199 
200     $(TR $(TD $(B '#')) $(TD integral ($(B 'o'))) $(TD Add to
201     precision as necessary so that the first digit of the octal
202     formatting is a '0', even if both the argument and the $(I
203     Precision) are zero.))
204 
205     $(TR $(TD $(B '#')) $(TD integral ($(B 'x'), $(B 'X'))) $(TD If
206        non-zero, prefix result with $(B 0x) ($(B 0X)).))
207 
208     $(TR $(TD $(B '#')) $(TD floating) $(TD Always insert the decimal
209        point and print trailing zeros.))
210 
211     $(TR $(TD $(B '0')) $(TD numeric) $(TD Use leading
212     zeros to pad rather than spaces (except for the floating point
213     values `nan` and `infinity`).  Ignore if there's a $(I
214     Precision).))
215 
216     $(TR $(TD $(B ' ')) $(TD numeric) $(TD Prefix positive
217     numbers in a signed conversion with a space.)))
218 
219     $(DL
220         $(DT $(I Width))
221         $(DD
222         Only used for numeric, bool, null, char, string, enum and pointer types.
223         Specifies the minimum field width.
224         If the width is a $(B *), an additional argument of type $(B int),
225         preceding the actual argument, is taken as the width.
226         If the width is negative, it is as if the $(B -) was given
227         as a $(I Flags) character.)
228 
229         $(DT $(I Precision))
230         $(DD Gives the precision for numeric conversions.
231         If the precision is a $(B *), an additional argument of type $(B int),
232         preceding the actual argument, is taken as the precision.
233         If it is negative, it is as if there was no $(I Precision) specifier.)
234 
235         $(DT $(I Separator))
236         $(DD Inserts the separator symbols ',' every $(I X) digits, from right
237         to left, into numeric values to increase readability.
238         The fractional part of floating point values inserts the separator
239         from left to right.
240         Entering an integer after the ',' allows to specify $(I X).
241         If a '*' is placed after the ',' then $(I X) is specified by an
242         additional parameter to the format function.
243         Adding a '?' after the ',' or $(I X) specifier allows to specify
244         the separator character as an additional parameter.
245         )
246 
247         $(DT $(I FormatChar))
248         $(DD
249         $(DL
250             $(DT $(B 's'))
251             $(DD The corresponding argument is formatted in a manner consistent
252             with its type:
253             $(DL
254                 $(DT $(B bool))
255                 $(DD The result is `"true"` or `"false"`.)
256                 $(DT integral types)
257                 $(DD The $(B %d) format is used.)
258                 $(DT floating point types)
259                 $(DD The $(B %g) format is used.)
260                 $(DT string types)
261                 $(DD The result is the string converted to UTF-8.
262                 A $(I Precision) specifies the maximum number of characters
263                 to use in the result.)
264                 $(DT structs)
265                 $(DD If the struct defines a $(B toString()) method the result is
266                 the string returned from this function. Otherwise the result is
267                 StructName(field$(SUB 0), field$(SUB 1), ...) where
268                 field$(SUB n) is the nth element formatted with the default
269                 format.)
270                 $(DT classes derived from $(B Object))
271                 $(DD The result is the string returned from the class instance's
272                 $(B .toString()) method.
273                 A $(I Precision) specifies the maximum number of characters
274                 to use in the result.)
275                 $(DT unions)
276                 $(DD If the union defines a $(B toString()) method the result is
277                 the string returned from this function. Otherwise the result is
278                 the name of the union, without its contents.)
279                 $(DT non-string static and dynamic arrays)
280                 $(DD The result is [s$(SUB 0), s$(SUB 1), ...]
281                 where s$(SUB n) is the nth element
282                 formatted with the default format.)
283                 $(DT associative arrays)
284                 $(DD The result is the equivalent of what the initializer
285                 would look like for the contents of the associative array,
286                 e.g.: ["red" : 10, "blue" : 20].)
287             ))
288 
289             $(DT $(B 'c'))
290             $(DD The corresponding argument must be a character type.)
291 
292             $(DT $(B 'b','d','o','x','X'))
293             $(DD The corresponding argument must be an integral type
294             and is formatted as an integer. If the argument is a signed type
295             and the $(I FormatChar) is $(B d) it is converted to
296             a signed string of characters, otherwise it is treated as
297             unsigned. An argument of type $(B bool) is formatted as '1'
298             or '0'. The base used is binary for $(B b), octal for $(B o),
299             decimal
300             for $(B d), and hexadecimal for $(B x) or $(B X).
301             $(B x) formats using lower case letters, $(B X) uppercase.
302             If there are fewer resulting digits than the $(I Precision),
303             leading zeros are used as necessary.
304             If the $(I Precision) is 0 and the number is 0, no digits
305             result.)
306 
307             $(DT $(B 'e','E'))
308             $(DD A floating point number is formatted as one digit before
309             the decimal point, $(I Precision) digits after, the $(I FormatChar),
310             ±, followed by at least a two digit exponent:
311             $(I d.dddddd)e$(I ±dd).
312             If there is no $(I Precision), six
313             digits are generated after the decimal point.
314             If the $(I Precision) is 0, no decimal point is generated.)
315 
316             $(DT $(B 'f','F'))
317             $(DD A floating point number is formatted in decimal notation.
318             The $(I Precision) specifies the number of digits generated
319             after the decimal point. It defaults to six. At least one digit
320             is generated before the decimal point. If the $(I Precision)
321             is zero, no decimal point is generated.)
322 
323             $(DT $(B 'g','G'))
324             $(DD A floating point number is formatted in either $(B e) or
325             $(B f) format for $(B g); $(B E) or $(B F) format for
326             $(B G).
327             The $(B f) format is used if the exponent for an $(B e) format
328             is greater than -5 and less than the $(I Precision).
329             The $(I Precision) specifies the number of significant
330             digits, and defaults to six.
331             Trailing zeros are elided after the decimal point, if the fractional
332             part is zero then no decimal point is generated.)
333 
334             $(DT $(B 'a','A'))
335             $(DD A floating point number is formatted in hexadecimal
336             exponential notation 0x$(I h.hhhhhh)p$(I ±d).
337             There is one hexadecimal digit before the decimal point, and as
338             many after as specified by the $(I Precision).
339             If the $(I Precision) is zero, no decimal point is generated.
340             If there is no $(I Precision), as many hexadecimal digits as
341             necessary to exactly represent the mantissa are generated.
342             The exponent is written in as few digits as possible,
343             but at least one, is in decimal, and represents a power of 2 as in
344             $(I h.hhhhhh)*2$(SUPERSCRIPT $(I ±d)).
345             The exponent for zero is zero.
346             The hexadecimal digits, x and p are in upper case if the
347             $(I FormatChar) is upper case.)
348         ))
349     )
350 
351     Floating point NaN's are formatted as $(B nan) if the
352     $(I FormatChar) is lower case, or $(B NAN) if upper.
353     Floating point infinities are formatted as $(B inf) or
354     $(B infinity) if the
355     $(I FormatChar) is lower case, or $(B INF) or $(B INFINITY) if upper.
356 
357     The positional and non-positional styles can be mixed in the same
358     format string. (POSIX leaves this behavior undefined.) The internal
359     counter for non-positional parameters tracks the next parameter after
360     the largest positional parameter already used.
361 
362     Example using array and nested array formatting:
363     -------------------------
364     import std.stdio;
365 
366     void main()
367     {
368         writefln("My items are %(%s %).", [1,2,3]);
369         writefln("My items are %(%s, %).", [1,2,3]);
370     }
371     -------------------------
372     The output is:
373 $(CONSOLE
374 My items are 1 2 3.
375 My items are 1, 2, 3.
376 )
377 
378     The trailing end of the sub-format string following the specifier for each
379     item is interpreted as the array delimiter, and is therefore omitted
380     following the last array item. The $(B %|) delimiter specifier may be used
381     to indicate where the delimiter begins, so that the portion of the format
382     string prior to it will be retained in the last array element:
383     -------------------------
384     import std.stdio;
385 
386     void main()
387     {
388         writefln("My items are %(-%s-%|, %).", [1,2,3]);
389     }
390     -------------------------
391     which gives the output:
392 $(CONSOLE
393 My items are -1-, -2-, -3-.
394 )
395 
396     These compound format specifiers may be nested in the case of a nested
397     array argument:
398     -------------------------
399     import std.stdio;
400     void main() {
401          auto mat = [[1, 2, 3],
402                      [4, 5, 6],
403                      [7, 8, 9]];
404 
405          writefln("%(%(%d %)\n%)", mat);
406          writeln();
407 
408          writefln("[%(%(%d %)\n %)]", mat);
409          writeln();
410 
411          writefln("[%([%(%d %)]%|\n %)]", mat);
412          writeln();
413     }
414     -------------------------
415     The output is:
416 $(CONSOLE
417 1 2 3
418 4 5 6
419 7 8 9
420 
421 [1 2 3
422  4 5 6
423  7 8 9]
424 
425 [[1 2 3]
426  [4 5 6]
427  [7 8 9]]
428 )
429 
430     Inside a compound format specifier, strings and characters are escaped
431     automatically. To avoid this behavior, add $(B '-') flag to
432     `"%$(LPAREN)"`.
433     -------------------------
434     import std.stdio;
435 
436     void main()
437     {
438         writefln("My friends are %s.", ["John", "Nancy"]);
439         writefln("My friends are %(%s, %).", ["John", "Nancy"]);
440         writefln("My friends are %-(%s, %).", ["John", "Nancy"]);
441     }
442     -------------------------
443    which gives the output:
444 $(CONSOLE
445 My friends are ["John", "Nancy"].
446 My friends are "John", "Nancy".
447 My friends are John, Nancy.
448 )
449  */
450 uint formattedWrite(alias fmt, Writer, A...)(auto ref Writer w, A args)
451 if (isSomeString!(typeof(fmt)))
452 {
453     alias e = checkFormatException!(fmt, A);
454     static assert(!e, e.msg);
455     return .formattedWrite(w, fmt, args);
456 }
457 
458 /// The format string can be checked at compile-time (see $(LREF format) for details):
459 @safe pure unittest
460 {
461     import std.array : appender;
462 
463     auto writer = appender!string();
464     writer.formattedWrite!"%s is the ultimate %s."(42, "answer");
465     assert(writer.data == "42 is the ultimate answer.");
466 
467     // Clear the writer
468     writer = appender!string();
469     formattedWrite(writer, "Date: %2$s %1$s", "October", 5);
470     assert(writer.data == "Date: 5 October");
471 }
472 
473 /// ditto
474 uint formattedWrite(Writer, Char, A...)(auto ref Writer w, const scope Char[] fmt, A args)
475 {
476     import std.conv : text;
477 
478     auto spec = FormatSpec!Char(fmt);
479 
480     // Are we already done with formats? Then just dump each parameter in turn
481     uint currentArg = 0;
482     while (spec.writeUpToNextSpec(w))
483     {
484         if (currentArg == A.length && !spec.indexStart)
485         {
486             // leftover spec?
487             enforceFmt(fmt.length == 0,
488                 text("Orphan format specifier: %", spec.spec));
489             break;
490         }
491 
492         if (spec.width == spec.DYNAMIC)
493         {
494             auto width = getNthInt!"integer width"(currentArg, args);
495             if (width < 0)
496             {
497                 spec.flDash = true;
498                 width = -width;
499             }
500             spec.width = width;
501             ++currentArg;
502         }
503         else if (spec.width < 0)
504         {
505             // means: get width as a positional parameter
506             auto index = cast(uint) -spec.width;
507             assert(index > 0, "The index must be greater than zero");
508             auto width = getNthInt!"integer width"(index - 1, args);
509             if (currentArg < index) currentArg = index;
510             if (width < 0)
511             {
512                 spec.flDash = true;
513                 width = -width;
514             }
515             spec.width = width;
516         }
517 
518         if (spec.precision == spec.DYNAMIC)
519         {
520             auto precision = getNthInt!"integer precision"(currentArg, args);
521             if (precision >= 0) spec.precision = precision;
522             // else negative precision is same as no precision
523             else spec.precision = spec.UNSPECIFIED;
524             ++currentArg;
525         }
526         else if (spec.precision < 0)
527         {
528             // means: get precision as a positional parameter
529             auto index = cast(uint) -spec.precision;
530             assert(index > 0, "The precision must be greater than zero");
531             auto precision = getNthInt!"integer precision"(index- 1, args);
532             if (currentArg < index) currentArg = index;
533             if (precision >= 0) spec.precision = precision;
534             // else negative precision is same as no precision
535             else spec.precision = spec.UNSPECIFIED;
536         }
537 
538         if (spec.separators == spec.DYNAMIC)
539         {
540             auto separators = getNthInt!"separator digit width"(currentArg, args);
541             spec.separators = separators;
542             ++currentArg;
543         }
544 
545         if (spec.separatorCharPos == spec.DYNAMIC)
546         {
547             auto separatorChar =
548                 getNth!("separator character", isSomeChar, dchar)(currentArg, args);
549             spec.separatorChar = separatorChar;
550             ++currentArg;
551         }
552 
553         if (currentArg == A.length && !spec.indexStart)
554         {
555             // leftover spec?
556             enforceFmt(fmt.length == 0,
557                 text("Orphan format specifier: %", spec.spec));
558             break;
559         }
560 
561         // Format an argument
562         // This switch uses a static foreach to generate a jump table.
563         // Currently `spec.indexStart` use the special value '0' to signal
564         // we should use the current argument. An enhancement would be to
565         // always store the index.
566         size_t index = currentArg;
567         if (spec.indexStart != 0)
568             index = spec.indexStart - 1;
569         else
570             ++currentArg;
571     SWITCH: switch (index)
572         {
573             foreach (i, Tunused; A)
574             {
575             case i:
576                 formatValue(w, args[i], spec);
577                 if (currentArg < spec.indexEnd)
578                     currentArg = spec.indexEnd;
579                 // A little know feature of format is to format a range
580                 // of arguments, e.g. `%1:3$` will format the first 3
581                 // arguments. Since they have to be consecutive we can
582                 // just use explicit fallthrough to cover that case.
583                 if (i + 1 < spec.indexEnd)
584                 {
585                     // You cannot goto case if the next case is the default
586                     static if (i + 1 < A.length)
587                         goto case;
588                     else
589                         goto default;
590                 }
591                 else
592                     break SWITCH;
593             }
594         default:
595             throw new FormatException(
596                 text("Positional specifier %", spec.indexStart, '$', spec.spec,
597                      " index exceeds ", A.length));
598         }
599     }
600     return currentArg;
601 }
602 
603 ///
604 @safe unittest
605 {
606     assert(format("%,d", 1000) == "1,000");
607     assert(format("%,f", 1234567.891011) == "1,234,567.891011");
608     assert(format("%,?d", '?', 1000) == "1?000");
609     assert(format("%,1d", 1000) == "1,0,0,0", format("%,1d", 1000));
610     assert(format("%,*d", 4, -12345) == "-1,2345");
611     assert(format("%,*?d", 4, '_', -12345) == "-1_2345");
612     assert(format("%,6?d", '_', -12345678) == "-12_345678");
613     assert(format("%12,3.3f", 1234.5678) == "   1,234.568", "'" ~
614             format("%12,3.3f", 1234.5678) ~ "'");
615 }
616 
617 @safe pure unittest
618 {
619     import std.array;
620     auto w = appender!string();
621     formattedWrite(w, "%s %d", "@safe/pure", 42);
622     assert(w.data == "@safe/pure 42");
623 }
624 
625 @safe pure unittest
626 {
627     char[20] buf;
628     auto w = buf[];
629     formattedWrite(w, "%s %d", "@safe/pure", 42);
630     assert(buf[0 .. $ - w.length] == "@safe/pure 42");
631 }
632 
633 /**
634 Reads characters from $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
635 `r`, converts them according to `fmt`, and writes them to `args`.
636 
637 Params:
638     r = The range to read from.
639     fmt = The format of the data to read.
640     args = The drain of the data read.
641 
642 Returns:
643 
644 On success, the function returns the number of variables filled. This count
645 can match the expected number of readings or fewer, even zero, if a
646 matching failure happens.
647 
648 Throws:
649     A `FormatException` if `S.length == 0` and `fmt` has format specifiers.
650  */
651 uint formattedRead(alias fmt, R, S...)(auto ref R r, auto ref S args)
652 if (isSomeString!(typeof(fmt)))
653 {
654     alias e = checkFormatException!(fmt, S);
655     static assert(!e, e.msg);
656     return .formattedRead(r, fmt, args);
657 }
658 
659 /// ditto
660 uint formattedRead(R, Char, S...)(auto ref R r, const(Char)[] fmt, auto ref S args)
661 {
662     import std.typecons : isTuple;
663 
664     auto spec = FormatSpec!Char(fmt);
665     static if (!S.length)
666     {
667         spec.readUpToNextSpec(r);
668         enforceFmt(spec.trailing.empty, "Trailing characters in formattedRead format string");
669         return 0;
670     }
671     else
672     {
673         enum hasPointer = isPointer!(typeof(args[0]));
674 
675         // The function below accounts for '*' == fields meant to be
676         // read and skipped
677         void skipUnstoredFields()
678         {
679             for (;;)
680             {
681                 spec.readUpToNextSpec(r);
682                 if (spec.width != spec.DYNAMIC) break;
683                 // must skip this field
684                 skipData(r, spec);
685             }
686         }
687 
688         skipUnstoredFields();
689         if (r.empty)
690         {
691             // Input is empty, nothing to read
692             return 0;
693         }
694         static if (hasPointer)
695             alias A = typeof(*args[0]);
696         else
697             alias A = typeof(args[0]);
698 
699         static if (isTuple!A)
700         {
701             foreach (i, T; A.Types)
702             {
703                 static if (hasPointer)
704                     (*args[0])[i] = unformatValue!(T)(r, spec);
705                 else
706                     args[0][i] = unformatValue!(T)(r, spec);
707                 skipUnstoredFields();
708             }
709         }
710         else
711         {
712             static if (hasPointer)
713                 *args[0] = unformatValue!(A)(r, spec);
714             else
715                 args[0] = unformatValue!(A)(r, spec);
716         }
717         return 1 + formattedRead(r, spec.trailing, args[1 .. $]);
718     }
719 }
720 
721 /// The format string can be checked at compile-time (see $(LREF format) for details):
722 @safe pure unittest
723 {
724     string s = "hello!124:34.5";
725     string a;
726     int b;
727     double c;
728     s.formattedRead!"%s!%s:%s"(a, b, c);
729     assert(a == "hello" && b == 124 && c == 34.5);
730 }
731 
732 @safe unittest
733 {
734     import std.math;
735     string s = " 1.2 3.4 ";
736     double x, y, z;
737     assert(formattedRead(s, " %s %s %s ", x, y, z) == 2);
738     assert(s.empty);
739     assert(approxEqual(x, 1.2));
740     assert(approxEqual(y, 3.4));
741     assert(isNaN(z));
742 }
743 
744 // for backwards compatibility
745 @system pure unittest
746 {
747     string s = "hello!124:34.5";
748     string a;
749     int b;
750     double c;
751     formattedRead(s, "%s!%s:%s", &a, &b, &c);
752     assert(a == "hello" && b == 124 && c == 34.5);
753 
754     // mix pointers and auto-ref
755     s = "world!200:42.25";
756     formattedRead(s, "%s!%s:%s", a, &b, &c);
757     assert(a == "world" && b == 200 && c == 42.25);
758 
759     s = "world1!201:42.5";
760     formattedRead(s, "%s!%s:%s", &a, &b, c);
761     assert(a == "world1" && b == 201 && c == 42.5);
762 
763     s = "world2!202:42.75";
764     formattedRead(s, "%s!%s:%s", a, b, &c);
765     assert(a == "world2" && b == 202 && c == 42.75);
766 }
767 
768 // for backwards compatibility
769 @system pure unittest
770 {
771     import std.math;
772     string s = " 1.2 3.4 ";
773     double x, y, z;
774     assert(formattedRead(s, " %s %s %s ", &x, &y, &z) == 2);
775     assert(s.empty);
776     assert(approxEqual(x, 1.2));
777     assert(approxEqual(y, 3.4));
778     assert(isNaN(z));
779 }
780 
781 @system pure unittest
782 {
783     string line;
784 
785     bool f1;
786 
787     line = "true";
788     formattedRead(line, "%s", &f1);
789     assert(f1);
790 
791     line = "TrUE";
792     formattedRead(line, "%s", &f1);
793     assert(f1);
794 
795     line = "false";
796     formattedRead(line, "%s", &f1);
797     assert(!f1);
798 
799     line = "fALsE";
800     formattedRead(line, "%s", &f1);
801     assert(!f1);
802 
803     line = "1";
804     formattedRead(line, "%d", &f1);
805     assert(f1);
806 
807     line = "-1";
808     formattedRead(line, "%d", &f1);
809     assert(f1);
810 
811     line = "0";
812     formattedRead(line, "%d", &f1);
813     assert(!f1);
814 
815     line = "-0";
816     formattedRead(line, "%d", &f1);
817     assert(!f1);
818 }
819 
820 @system pure unittest
821 {
822      union B
823      {
824          char[int.sizeof] untyped;
825          int typed;
826      }
827      B b;
828      b.typed = 5;
829      char[] input = b.untyped[];
830      int witness;
831      formattedRead(input, "%r", &witness);
832      assert(witness == b.typed);
833 }
834 
835 @system pure unittest
836 {
837     union A
838     {
839         char[float.sizeof] untyped;
840         float typed;
841     }
842     A a;
843     a.typed = 5.5;
844     char[] input = a.untyped[];
845     float witness;
846     formattedRead(input, "%r", &witness);
847     assert(witness == a.typed);
848 }
849 
850 @system pure unittest
851 {
852     import std.typecons;
853     char[] line = "1 2".dup;
854     int a, b;
855     formattedRead(line, "%s %s", &a, &b);
856     assert(a == 1 && b == 2);
857 
858     line = "10 2 3".dup;
859     formattedRead(line, "%d ", &a);
860     assert(a == 10);
861     assert(line == "2 3");
862 
863     Tuple!(int, float) t;
864     line = "1 2.125".dup;
865     formattedRead(line, "%d %g", &t);
866     assert(t[0] == 1 && t[1] == 2.125);
867 
868     line = "1 7643 2.125".dup;
869     formattedRead(line, "%s %*u %s", &t);
870     assert(t[0] == 1 && t[1] == 2.125);
871 }
872 
873 @system pure unittest
874 {
875     string line;
876 
877     char c1, c2;
878 
879     line = "abc";
880     formattedRead(line, "%s%c", &c1, &c2);
881     assert(c1 == 'a' && c2 == 'b');
882     assert(line == "c");
883 }
884 
885 @system pure unittest
886 {
887     string line;
888 
889     line = "[1,2,3]";
890     int[] s1;
891     formattedRead(line, "%s", &s1);
892     assert(s1 == [1,2,3]);
893 }
894 
895 @system pure unittest
896 {
897     string line;
898 
899     line = "[1,2,3]";
900     int[] s1;
901     formattedRead(line, "[%(%s,%)]", &s1);
902     assert(s1 == [1,2,3]);
903 
904     line = `["hello", "world"]`;
905     string[] s2;
906     formattedRead(line, "[%(%s, %)]", &s2);
907     assert(s2 == ["hello", "world"]);
908 
909     line = "123 456";
910     int[] s3;
911     formattedRead(line, "%(%s %)", &s3);
912     assert(s3 == [123, 456]);
913 
914     line = "h,e,l,l,o; w,o,r,l,d";
915     string[] s4;
916     formattedRead(line, "%(%(%c,%); %)", &s4);
917     assert(s4 == ["hello", "world"]);
918 }
919 
920 @system pure unittest
921 {
922     string line;
923 
924     int[4] sa1;
925     line = `[1,2,3,4]`;
926     formattedRead(line, "%s", &sa1);
927     assert(sa1 == [1,2,3,4]);
928 
929     int[4] sa2;
930     line = `[1,2,3]`;
931     assertThrown(formattedRead(line, "%s", &sa2));
932 
933     int[4] sa3;
934     line = `[1,2,3,4,5]`;
935     assertThrown(formattedRead(line, "%s", &sa3));
936 }
937 
938 @system pure unittest
939 {
940     string input;
941 
942     int[4] sa1;
943     input = `[1,2,3,4]`;
944     formattedRead(input, "[%(%s,%)]", &sa1);
945     assert(sa1 == [1,2,3,4]);
946 
947     int[4] sa2;
948     input = `[1,2,3]`;
949     assertThrown!FormatException(formattedRead(input, "[%(%s,%)]", &sa2));
950 }
951 
952 @system pure unittest
953 {
954     string line;
955 
956     string s1, s2;
957 
958     line = "hello, world";
959     formattedRead(line, "%s", &s1);
960     assert(s1 == "hello, world", s1);
961 
962     line = "hello, world;yah";
963     formattedRead(line, "%s;%s", &s1, &s2);
964     assert(s1 == "hello, world", s1);
965     assert(s2 == "yah", s2);
966 
967     line = `['h','e','l','l','o']`;
968     string s3;
969     formattedRead(line, "[%(%s,%)]", &s3);
970     assert(s3 == "hello");
971 
972     line = `"hello"`;
973     string s4;
974     formattedRead(line, "\"%(%c%)\"", &s4);
975     assert(s4 == "hello");
976 }
977 
978 @system pure unittest
979 {
980     string line;
981 
982     string[int] aa1;
983     line = `[1:"hello", 2:"world"]`;
984     formattedRead(line, "%s", &aa1);
985     assert(aa1 == [1:"hello", 2:"world"]);
986 
987     int[string] aa2;
988     line = `{"hello"=1; "world"=2}`;
989     formattedRead(line, "{%(%s=%s; %)}", &aa2);
990     assert(aa2 == ["hello":1, "world":2]);
991 
992     int[string] aa3;
993     line = `{[hello=1]; [world=2]}`;
994     formattedRead(line, "{%([%(%c%)=%s]%|; %)}", &aa3);
995     assert(aa3 == ["hello":1, "world":2]);
996 }
997 
998 // test rvalue using
999 @system pure unittest
1000 {
1001     string[int] aa1;
1002     formattedRead!("%s")(`[1:"hello", 2:"world"]`, aa1);
1003     assert(aa1 == [1:"hello", 2:"world"]);
1004 
1005     int[string] aa2;
1006     formattedRead(`{"hello"=1; "world"=2}`, "{%(%s=%s; %)}", aa2);
1007     assert(aa2 == ["hello":1, "world":2]);
1008 }
1009 
1010 template FormatSpec(Char)
1011 if (!is(Unqual!Char == Char))
1012 {
1013     alias FormatSpec = FormatSpec!(Unqual!Char);
1014 }
1015 
1016 /**
1017  * A General handler for `printf` style format specifiers. Used for building more
1018  * specific formatting functions.
1019  */
1020 struct FormatSpec(Char)
1021 if (is(Unqual!Char == Char))
1022 {
1023     import std.algorithm.searching : startsWith;
1024     import std.ascii : isDigit, isPunctuation, isAlpha;
1025     import std.conv : parse, text, to;
1026 
1027     /**
1028        Minimum _width, default `0`.
1029      */
1030     int width = 0;
1031 
1032     /**
1033        Precision. Its semantics depends on the argument type. For
1034        floating point numbers, _precision dictates the number of
1035        decimals printed.
1036      */
1037     int precision = UNSPECIFIED;
1038 
1039     /**
1040        Number of digits printed between _separators.
1041     */
1042     int separators = UNSPECIFIED;
1043 
1044     /**
1045        Set to `DYNAMIC` when the separator character is supplied at runtime.
1046     */
1047     int separatorCharPos = UNSPECIFIED;
1048 
1049     /**
1050        Character to insert between digits.
1051     */
1052     dchar separatorChar = ',';
1053 
1054     /**
1055        Special value for width and precision. `DYNAMIC` width or
1056        precision means that they were specified with `'*'` in the
1057        format string and are passed at runtime through the varargs.
1058      */
1059     enum int DYNAMIC = int.max;
1060 
1061     /**
1062        Special value for precision, meaning the format specifier
1063        contained no explicit precision.
1064      */
1065     enum int UNSPECIFIED = DYNAMIC - 1;
1066 
1067     /**
1068        The actual format specifier, `'s'` by default.
1069     */
1070     char spec = 's';
1071 
1072     /**
1073        Index of the argument for positional parameters, from `1` to
1074        `ubyte.max`. (`0` means not used).
1075     */
1076     ubyte indexStart;
1077 
1078     /**
1079        Index of the last argument for positional parameter range, from
1080        `1` to `ubyte.max`. (`0` means not used).
1081     */
1082     ubyte indexEnd;
1083 
1084     version (StdDdoc)
1085     {
1086         /**
1087          The format specifier contained a `'-'` (`printf`
1088          compatibility).
1089          */
1090         bool flDash;
1091 
1092         /**
1093          The format specifier contained a `'0'` (`printf`
1094          compatibility).
1095          */
1096         bool flZero;
1097 
1098         /**
1099          The format specifier contained a $(D ' ') (`printf`
1100          compatibility).
1101          */
1102         bool flSpace;
1103 
1104         /**
1105          The format specifier contained a `'+'` (`printf`
1106          compatibility).
1107          */
1108         bool flPlus;
1109 
1110         /**
1111          The format specifier contained a `'#'` (`printf`
1112          compatibility).
1113          */
1114         bool flHash;
1115 
1116         /**
1117          The format specifier contained a `','`
1118          */
1119         bool flSeparator;
1120 
1121         // Fake field to allow compilation
1122         ubyte allFlags;
1123     }
1124     else
1125     {
1126         union
1127         {
1128             import std.bitmanip : bitfields;
1129             mixin(bitfields!(
1130                         bool, "flDash", 1,
1131                         bool, "flZero", 1,
1132                         bool, "flSpace", 1,
1133                         bool, "flPlus", 1,
1134                         bool, "flHash", 1,
1135                         bool, "flSeparator", 1,
1136                         ubyte, "", 2));
1137             ubyte allFlags;
1138         }
1139     }
1140 
1141     /**
1142        In case of a compound format specifier starting with $(D
1143        "%$(LPAREN)") and ending with `"%$(RPAREN)"`, `_nested`
1144        contains the string contained within the two separators.
1145      */
1146     const(Char)[] nested;
1147 
1148     /**
1149        In case of a compound format specifier, `_sep` contains the
1150        string positioning after `"%|"`.
1151        `sep is null` means no separator else `sep.empty` means 0 length
1152         separator.
1153      */
1154     const(Char)[] sep;
1155 
1156     /**
1157        `_trailing` contains the rest of the format string.
1158      */
1159     const(Char)[] trailing;
1160 
1161     /*
1162        This string is inserted before each sequence (e.g. array)
1163        formatted (by default `"["`).
1164      */
1165     enum immutable(Char)[] seqBefore = "[";
1166 
1167     /*
1168        This string is inserted after each sequence formatted (by
1169        default `"]"`).
1170      */
1171     enum immutable(Char)[] seqAfter = "]";
1172 
1173     /*
1174        This string is inserted after each element keys of a sequence (by
1175        default `":"`).
1176      */
1177     enum immutable(Char)[] keySeparator = ":";
1178 
1179     /*
1180        This string is inserted in between elements of a sequence (by
1181        default $(D ", ")).
1182      */
1183     enum immutable(Char)[] seqSeparator = ", ";
1184 
1185     /**
1186        Construct a new `FormatSpec` using the format string `fmt`, no
1187        processing is done until needed.
1188      */
1189     this(in Char[] fmt) @safe pure
1190     {
1191         trailing = fmt;
1192     }
1193 
1194     /**
1195        Write the format string to an output range until the next format
1196        specifier is found and parse that format specifier.
1197 
1198        See $(LREF FormatSpec) for an example, how to use `writeUpToNextSpec`.
1199 
1200        Params:
1201            writer = the $(REF_ALTTEXT output range, isOutputRange, std, range, primitives)
1202 
1203        Returns:
1204            True, when a format specifier is found.
1205 
1206        Throws:
1207            A $(LREF FormatException) when the found format specifier
1208            could not be parsed.
1209      */
1210     bool writeUpToNextSpec(OutputRange)(ref OutputRange writer) scope
1211     {
1212         if (trailing.empty)
1213             return false;
1214         for (size_t i = 0; i < trailing.length; ++i)
1215         {
1216             if (trailing[i] != '%') continue;
1217             put(writer, trailing[0 .. i]);
1218             trailing = trailing[i .. $];
1219             enforceFmt(trailing.length >= 2, `Unterminated format specifier: "%"`);
1220             trailing = trailing[1 .. $];
1221 
1222             if (trailing[0] != '%')
1223             {
1224                 // Spec found. Fill up the spec, and bailout
1225                 fillUp();
1226                 return true;
1227             }
1228             // Doubled! Reset and Keep going
1229             i = 0;
1230         }
1231         // no format spec found
1232         put(writer, trailing);
1233         trailing = null;
1234         return false;
1235     }
1236 
1237     private void fillUp() scope
1238     {
1239         // Reset content
1240         if (__ctfe)
1241         {
1242             flDash = false;
1243             flZero = false;
1244             flSpace = false;
1245             flPlus = false;
1246             flHash = false;
1247             flSeparator = false;
1248         }
1249         else
1250         {
1251             allFlags = 0;
1252         }
1253 
1254         width = 0;
1255         precision = UNSPECIFIED;
1256         nested = null;
1257         // Parse the spec (we assume we're past '%' already)
1258         for (size_t i = 0; i < trailing.length; )
1259         {
1260             switch (trailing[i])
1261             {
1262             case '(':
1263                 // Embedded format specifier.
1264                 auto j = i + 1;
1265                 // Get the matching balanced paren
1266                 for (uint innerParens;;)
1267                 {
1268                     enforceFmt(j + 1 < trailing.length,
1269                         text("Incorrect format specifier: %", trailing[i .. $]));
1270                     if (trailing[j++] != '%')
1271                     {
1272                         // skip, we're waiting for %( and %)
1273                         continue;
1274                     }
1275                     if (trailing[j] == '-') // for %-(
1276                     {
1277                         ++j;    // skip
1278                         enforceFmt(j < trailing.length,
1279                             text("Incorrect format specifier: %", trailing[i .. $]));
1280                     }
1281                     if (trailing[j] == ')')
1282                     {
1283                         if (innerParens-- == 0) break;
1284                     }
1285                     else if (trailing[j] == '|')
1286                     {
1287                         if (innerParens == 0) break;
1288                     }
1289                     else if (trailing[j] == '(')
1290                     {
1291                         ++innerParens;
1292                     }
1293                 }
1294                 if (trailing[j] == '|')
1295                 {
1296                     auto k = j;
1297                     for (++j;;)
1298                     {
1299                         if (trailing[j++] != '%')
1300                             continue;
1301                         if (trailing[j] == '%')
1302                             ++j;
1303                         else if (trailing[j] == ')')
1304                             break;
1305                         else
1306                             throw new FormatException(
1307                                 text("Incorrect format specifier: %",
1308                                         trailing[j .. $]));
1309                     }
1310                     nested = trailing[i + 1 .. k - 1];
1311                     sep = trailing[k + 1 .. j - 1];
1312                 }
1313                 else
1314                 {
1315                     nested = trailing[i + 1 .. j - 1];
1316                     sep = null; // no separator
1317                 }
1318                 //this = FormatSpec(innerTrailingSpec);
1319                 spec = '(';
1320                 // We practically found the format specifier
1321                 trailing = trailing[j + 1 .. $];
1322                 return;
1323             case '-': flDash = true; ++i; break;
1324             case '+': flPlus = true; ++i; break;
1325             case '#': flHash = true; ++i; break;
1326             case '0': flZero = true; ++i; break;
1327             case ' ': flSpace = true; ++i; break;
1328             case '*':
1329                 if (isDigit(trailing[++i]))
1330                 {
1331                     // a '*' followed by digits and '$' is a
1332                     // positional format
1333                     trailing = trailing[1 .. $];
1334                     width = -parse!(typeof(width))(trailing);
1335                     i = 0;
1336                     enforceFmt(trailing[i++] == '$',
1337                         "$ expected");
1338                 }
1339                 else
1340                 {
1341                     // read result
1342                     width = DYNAMIC;
1343                 }
1344                 break;
1345             case '1': .. case '9':
1346                 auto tmp = trailing[i .. $];
1347                 const widthOrArgIndex = parse!uint(tmp);
1348                 enforceFmt(tmp.length,
1349                     text("Incorrect format specifier %", trailing[i .. $]));
1350                 i = arrayPtrDiff(tmp, trailing);
1351                 if (tmp.startsWith('$'))
1352                 {
1353                     // index of the form %n$
1354                     indexEnd = indexStart = to!ubyte(widthOrArgIndex);
1355                     ++i;
1356                 }
1357                 else if (tmp.startsWith(':'))
1358                 {
1359                     // two indexes of the form %m:n$, or one index of the form %m:$
1360                     indexStart = to!ubyte(widthOrArgIndex);
1361                     tmp = tmp[1 .. $];
1362                     if (tmp.startsWith('$'))
1363                     {
1364                         indexEnd = indexEnd.max;
1365                     }
1366                     else
1367                     {
1368                         indexEnd = parse!(typeof(indexEnd))(tmp);
1369                     }
1370                     i = arrayPtrDiff(tmp, trailing);
1371                     enforceFmt(trailing[i++] == '$',
1372                         "$ expected");
1373                 }
1374                 else
1375                 {
1376                     // width
1377                     width = to!int(widthOrArgIndex);
1378                 }
1379                 break;
1380             case ',':
1381                 // Precision
1382                 ++i;
1383                 flSeparator = true;
1384 
1385                 if (trailing[i] == '*')
1386                 {
1387                     ++i;
1388                     // read result
1389                     separators = DYNAMIC;
1390                 }
1391                 else if (isDigit(trailing[i]))
1392                 {
1393                     auto tmp = trailing[i .. $];
1394                     separators = parse!int(tmp);
1395                     i = arrayPtrDiff(tmp, trailing);
1396                 }
1397                 else
1398                 {
1399                     // "," was specified, but nothing after it
1400                     separators = 3;
1401                 }
1402 
1403                 if (trailing[i] == '?')
1404                 {
1405                     separatorCharPos = DYNAMIC;
1406                     ++i;
1407                 }
1408 
1409                 break;
1410             case '.':
1411                 // Precision
1412                 if (trailing[++i] == '*')
1413                 {
1414                     if (isDigit(trailing[++i]))
1415                     {
1416                         // a '.*' followed by digits and '$' is a
1417                         // positional precision
1418                         trailing = trailing[i .. $];
1419                         i = 0;
1420                         precision = -parse!int(trailing);
1421                         enforceFmt(trailing[i++] == '$',
1422                             "$ expected");
1423                     }
1424                     else
1425                     {
1426                         // read result
1427                         precision = DYNAMIC;
1428                     }
1429                 }
1430                 else if (trailing[i] == '-')
1431                 {
1432                     // negative precision, as good as 0
1433                     precision = 0;
1434                     auto tmp = trailing[i .. $];
1435                     parse!int(tmp); // skip digits
1436                     i = arrayPtrDiff(tmp, trailing);
1437                 }
1438                 else if (isDigit(trailing[i]))
1439                 {
1440                     auto tmp = trailing[i .. $];
1441                     precision = parse!int(tmp);
1442                     i = arrayPtrDiff(tmp, trailing);
1443                 }
1444                 else
1445                 {
1446                     // "." was specified, but nothing after it
1447                     precision = 0;
1448                 }
1449                 break;
1450             default:
1451                 // this is the format char
1452                 spec = cast(char) trailing[i++];
1453                 trailing = trailing[i .. $];
1454                 return;
1455             } // end switch
1456         } // end for
1457         throw new FormatException(text("Incorrect format specifier: ", trailing));
1458     }
1459 
1460     //--------------------------------------------------------------------------
1461     private bool readUpToNextSpec(R)(ref R r) scope
1462     {
1463         import std.ascii : isLower, isWhite;
1464         import std.utf : stride;
1465 
1466         // Reset content
1467         if (__ctfe)
1468         {
1469             flDash = false;
1470             flZero = false;
1471             flSpace = false;
1472             flPlus = false;
1473             flHash = false;
1474             flSeparator = false;
1475         }
1476         else
1477         {
1478             allFlags = 0;
1479         }
1480         width = 0;
1481         precision = UNSPECIFIED;
1482         nested = null;
1483         // Parse the spec
1484         while (trailing.length)
1485         {
1486             const c = trailing[0];
1487             if (c == '%' && trailing.length > 1)
1488             {
1489                 const c2 = trailing[1];
1490                 if (c2 == '%')
1491                 {
1492                     assert(!r.empty, "Required at least one more input");
1493                     // Require a '%'
1494                     if (r.front != '%') break;
1495                     trailing = trailing[2 .. $];
1496                     r.popFront();
1497                 }
1498                 else
1499                 {
1500                     enforceFmt(isLower(c2) || c2 == '*' ||
1501                             c2 == '(',
1502                             text("'%", c2,
1503                                     "' not supported with formatted read"));
1504                     trailing = trailing[1 .. $];
1505                     fillUp();
1506                     return true;
1507                 }
1508             }
1509             else
1510             {
1511                 if (c == ' ')
1512                 {
1513                     while (!r.empty && isWhite(r.front)) r.popFront();
1514                     //r = std.algorithm.find!(not!(isWhite))(r);
1515                 }
1516                 else
1517                 {
1518                     enforceFmt(!r.empty,
1519                             text("parseToFormatSpec: Cannot find character '",
1520                                     c, "' in the input string."));
1521                     if (r.front != trailing.front) break;
1522                     r.popFront();
1523                 }
1524                 trailing = trailing[stride(trailing, 0) .. $];
1525             }
1526         }
1527         return false;
1528     }
1529 
1530     private string getCurFmtStr() const
1531     {
1532         import std.array : appender;
1533         auto w = appender!string();
1534         auto f = FormatSpec!Char("%s"); // for stringnize
1535 
1536         put(w, '%');
1537         if (indexStart != 0)
1538         {
1539             formatValue(w, indexStart, f);
1540             put(w, '$');
1541         }
1542         if (flDash)  put(w, '-');
1543         if (flZero)  put(w, '0');
1544         if (flSpace) put(w, ' ');
1545         if (flPlus)  put(w, '+');
1546         if (flHash)  put(w, '#');
1547         if (flSeparator)  put(w, ',');
1548         if (width != 0)
1549             formatValue(w, width, f);
1550         if (precision != FormatSpec!Char.UNSPECIFIED)
1551         {
1552             put(w, '.');
1553             formatValue(w, precision, f);
1554         }
1555         put(w, spec);
1556         return w.data;
1557     }
1558 
1559     private const(Char)[] headUpToNextSpec()
1560     {
1561         import std.array : appender;
1562         auto w = appender!(typeof(return))();
1563         auto tr = trailing;
1564 
1565         while (tr.length)
1566         {
1567             if (tr[0] == '%')
1568             {
1569                 if (tr.length > 1 && tr[1] == '%')
1570                 {
1571                     tr = tr[2 .. $];
1572                     w.put('%');
1573                 }
1574                 else
1575                     break;
1576             }
1577             else
1578             {
1579                 w.put(tr.front);
1580                 tr.popFront();
1581             }
1582         }
1583         return w.data;
1584     }
1585 
1586     /**
1587      * Gives a string containing all of the member variables on their own
1588      * line.
1589      *
1590      * Params:
1591      *     writer = A `char` accepting
1592      *     $(REF_ALTTEXT output range, isOutputRange, std, range, primitives)
1593      * Returns:
1594      *     A `string` when not using an output range; `void` otherwise.
1595      */
1596     string toString() const @safe pure
1597     {
1598         import std.array : appender;
1599         auto app = appender!string();
1600         app.reserve(200 + trailing.length);
1601         toString(app);
1602         return app.data;
1603     }
1604 
1605     /// ditto
1606     void toString(OutputRange)(ref OutputRange writer) const
1607     if (isOutputRange!(OutputRange, char))
1608     {
1609         auto s = singleSpec("%s");
1610 
1611         put(writer, "address = ");
1612         formatValue(writer, &this, s);
1613         put(writer, "\nwidth = ");
1614         formatValue(writer, width, s);
1615         put(writer, "\nprecision = ");
1616         formatValue(writer, precision, s);
1617         put(writer, "\nspec = ");
1618         formatValue(writer, spec, s);
1619         put(writer, "\nindexStart = ");
1620         formatValue(writer, indexStart, s);
1621         put(writer, "\nindexEnd = ");
1622         formatValue(writer, indexEnd, s);
1623         put(writer, "\nflDash = ");
1624         formatValue(writer, flDash, s);
1625         put(writer, "\nflZero = ");
1626         formatValue(writer, flZero, s);
1627         put(writer, "\nflSpace = ");
1628         formatValue(writer, flSpace, s);
1629         put(writer, "\nflPlus = ");
1630         formatValue(writer, flPlus, s);
1631         put(writer, "\nflHash = ");
1632         formatValue(writer, flHash, s);
1633         put(writer, "\nflSeparator = ");
1634         formatValue(writer, flSeparator, s);
1635         put(writer, "\nnested = ");
1636         formatValue(writer, nested, s);
1637         put(writer, "\ntrailing = ");
1638         formatValue(writer, trailing, s);
1639         put(writer, '\n');
1640     }
1641 }
1642 
1643 @safe unittest
1644 {
1645     import std.array;
1646     import std.conv : text;
1647     auto w = appender!(char[])();
1648     auto f = FormatSpec!char("abc%sdef%sghi");
1649     f.writeUpToNextSpec(w);
1650     assert(w.data == "abc", w.data);
1651     assert(f.trailing == "def%sghi", text(f.trailing));
1652     f.writeUpToNextSpec(w);
1653     assert(w.data == "abcdef", w.data);
1654     assert(f.trailing == "ghi");
1655     // test with embedded %%s
1656     f = FormatSpec!char("ab%%cd%%ef%sg%%h%sij");
1657     w.clear();
1658     f.writeUpToNextSpec(w);
1659     assert(w.data == "ab%cd%ef" && f.trailing == "g%%h%sij", w.data);
1660     f.writeUpToNextSpec(w);
1661     assert(w.data == "ab%cd%efg%h" && f.trailing == "ij");
1662     // https://issues.dlang.org/show_bug.cgi?id=4775
1663     f = FormatSpec!char("%%%s");
1664     w.clear();
1665     f.writeUpToNextSpec(w);
1666     assert(w.data == "%" && f.trailing == "");
1667     f = FormatSpec!char("%%%%%s%%");
1668     w.clear();
1669     while (f.writeUpToNextSpec(w)) continue;
1670     assert(w.data == "%%%");
1671 
1672     f = FormatSpec!char("a%%b%%c%");
1673     w.clear();
1674     assertThrown!FormatException(f.writeUpToNextSpec(w));
1675     assert(w.data == "a%b%c" && f.trailing == "%");
1676 }
1677 
1678 // https://issues.dlang.org/show_bug.cgi?id=5237
1679 @safe unittest
1680 {
1681     import std.array;
1682     auto w = appender!string();
1683     auto f = FormatSpec!char("%.16f");
1684     f.writeUpToNextSpec(w); // dummy eating
1685     assert(f.spec == 'f');
1686     auto fmt = f.getCurFmtStr();
1687     assert(fmt == "%.16f");
1688 }
1689 
1690 ///
1691 @safe pure unittest
1692 {
1693     import std.array;
1694     auto a = appender!(string)();
1695     auto fmt = "Number: %6.4e\nString: %s";
1696     auto f = FormatSpec!char(fmt);
1697 
1698     assert(f.writeUpToNextSpec(a) == true);
1699 
1700     assert(a.data == "Number: ");
1701     assert(f.trailing == "\nString: %s");
1702     assert(f.spec == 'e');
1703     assert(f.width == 6);
1704     assert(f.precision == 4);
1705 
1706     assert(f.writeUpToNextSpec(a) == true);
1707 
1708     assert(a.data == "Number: \nString: ");
1709     assert(f.trailing == "");
1710     assert(f.spec == 's');
1711 
1712     assert(f.writeUpToNextSpec(a) == false);
1713     assert(a.data == "Number: \nString: ");
1714 }
1715 
1716 // https://issues.dlang.org/show_bug.cgi?id=14059
1717 @safe unittest
1718 {
1719     import std.array : appender;
1720     auto a = appender!(string)();
1721 
1722     auto f = FormatSpec!char("%-(%s%"); // %)")
1723     assertThrown!FormatException(f.writeUpToNextSpec(a));
1724 
1725     f = FormatSpec!char("%(%-"); // %)")
1726     assertThrown!FormatException(f.writeUpToNextSpec(a));
1727 }
1728 
1729 @safe unittest
1730 {
1731     import std.array : appender;
1732     auto a = appender!(string)();
1733 
1734     auto f = FormatSpec!char("%,d");
1735     f.writeUpToNextSpec(a);
1736 
1737     assert(f.spec == 'd', format("%s", f.spec));
1738     assert(f.precision == FormatSpec!char.UNSPECIFIED);
1739     assert(f.separators == 3);
1740 
1741     f = FormatSpec!char("%5,10f");
1742     f.writeUpToNextSpec(a);
1743     assert(f.spec == 'f', format("%s", f.spec));
1744     assert(f.separators == 10);
1745     assert(f.width == 5);
1746 
1747     f = FormatSpec!char("%5,10.4f");
1748     f.writeUpToNextSpec(a);
1749     assert(f.spec == 'f', format("%s", f.spec));
1750     assert(f.separators == 10);
1751     assert(f.width == 5);
1752     assert(f.precision == 4);
1753 }
1754 
1755 @safe pure unittest
1756 {
1757     import std.algorithm.searching : canFind, findSplitBefore;
1758     auto expected = "width = 2" ~
1759         "\nprecision = 5" ~
1760         "\nspec = f" ~
1761         "\nindexStart = 0" ~
1762         "\nindexEnd = 0" ~
1763         "\nflDash = false" ~
1764         "\nflZero = false" ~
1765         "\nflSpace = false" ~
1766         "\nflPlus = false" ~
1767         "\nflHash = false" ~
1768         "\nflSeparator = false" ~
1769         "\nnested = " ~
1770         "\ntrailing = \n";
1771     auto spec = singleSpec("%2.5f");
1772     auto res = spec.toString();
1773     // make sure the address exists, then skip it
1774     assert(res.canFind("address"));
1775     assert(res.findSplitBefore("width")[1] == expected);
1776 }
1777 
1778 /**
1779 Helper function that returns a `FormatSpec` for a single specifier given
1780 in `fmt`.
1781 
1782 Params:
1783     fmt = A format specifier.
1784 
1785 Returns:
1786     A `FormatSpec` with the specifier parsed.
1787 Throws:
1788     A `FormatException` when more than one specifier is given or the specifier
1789     is malformed.
1790   */
1791 FormatSpec!Char singleSpec(Char)(Char[] fmt)
1792 {
1793     import std.conv : text;
1794     enforceFmt(fmt.length >= 2, "fmt must be at least 2 characters long");
1795     enforceFmt(fmt.front == '%', "fmt must start with a '%' character");
1796 
1797     static struct DummyOutputRange {
1798         void put(C)(scope const C[] buf) {} // eat elements
1799     }
1800     auto a = DummyOutputRange();
1801     auto spec = FormatSpec!Char(fmt);
1802     //dummy write
1803     spec.writeUpToNextSpec(a);
1804 
1805     enforceFmt(spec.trailing.empty,
1806             text("Trailing characters in fmt string: '", spec.trailing));
1807 
1808     return spec;
1809 }
1810 
1811 ///
1812 @safe pure unittest
1813 {
1814     import std.exception : assertThrown;
1815     auto spec = singleSpec("%2.3e");
1816 
1817     assert(spec.trailing == "");
1818     assert(spec.spec == 'e');
1819     assert(spec.width == 2);
1820     assert(spec.precision == 3);
1821 
1822     assertThrown!FormatException(singleSpec(""));
1823     assertThrown!FormatException(singleSpec("2.3e"));
1824     assertThrown!FormatException(singleSpec("%2.3eTest"));
1825 }
1826 
1827 /**
1828  * Formats any value into `Char` accepting `OutputRange`, using the given `FormatSpec`.
1829  *
1830  * Aggregates:
1831  * `struct`, `union`, `class`, and `interface` are formatted by calling `toString`.
1832  *
1833  * `toString` should have one of the following signatures:
1834  *
1835  * ---
1836  * void toString(W)(ref W w, scope const ref FormatSpec fmt)
1837  * void toString(W)(ref W w)
1838  * string toString();
1839  * ---
1840  *
1841  * Where `W` is an $(REF_ALTTEXT output range, isOutputRange, std,range,primitives)
1842  * which accepts characters. The template type does not have to be called `W`.
1843  *
1844  * The following overloads are also accepted for legacy reasons or for use in virtual
1845  * functions. It's recommended that any new code forgo these overloads if possible for
1846  * speed and attribute acceptance reasons.
1847  *
1848  * ---
1849  * void toString(scope void delegate(const(char)[]) sink, const ref FormatSpec fmt);
1850  * void toString(scope void delegate(const(char)[]) sink, string fmt);
1851  * void toString(scope void delegate(const(char)[]) sink);
1852  * ---
1853  *
1854  * For the class objects which have input range interface,
1855  * $(UL
1856  *     $(LI If the instance `toString` has overridden `Object.toString`, it is used.)
1857  *     $(LI Otherwise, the objects are formatted as input range.)
1858  * )
1859  *
1860  * For the `struct` and `union` objects which does not have `toString`,
1861  * $(UL
1862  *     $(LI If they have range interface, formatted as input range.)
1863  *     $(LI Otherwise, they are formatted like `Type(field1, filed2, ...)`.)
1864  * )
1865  *
1866  * Otherwise, are formatted just as their type name.
1867  *
1868  * Params:
1869  *     w = The $(REF_ALTTEXT output range, isOutputRange, std,range,primitives) to write to.
1870  *     val = The value to write.
1871  *     f = The $(REF FormatSpec, std, format) defining how to write the value.
1872  */
1873 void formatValue(Writer, T, Char)(auto ref Writer w, auto ref T val, scope const ref FormatSpec!Char f)
1874 {
1875     formatValueImpl(w, val, f);
1876 }
1877 
1878 /++
1879    The following code compares the use of `formatValue` and `formattedWrite`.
1880  +/
1881 @safe pure unittest
1882 {
1883    import std.array : appender;
1884 
1885    auto writer1 = appender!string();
1886    writer1.formattedWrite("%08b", 42);
1887 
1888    auto writer2 = appender!string();
1889    auto f = singleSpec("%08b");
1890    writer2.formatValue(42, f);
1891 
1892    assert(writer1.data == writer2.data && writer1.data == "00101010");
1893 }
1894 
1895 /**
1896  * `bool`s are formatted as `"true"` or `"false"` with `%s` and as `1` or
1897  * `0` with integral-specific format specs.
1898  */
1899 @safe pure unittest
1900 {
1901     import std.array : appender;
1902     auto w = appender!string();
1903     auto spec = singleSpec("%s");
1904     formatValue(w, true, spec);
1905 
1906     assert(w.data == "true");
1907 }
1908 
1909 /// `null` literal is formatted as `"null"`.
1910 @safe pure unittest
1911 {
1912     import std.array : appender;
1913     auto w = appender!string();
1914     auto spec = singleSpec("%s");
1915     formatValue(w, null, spec);
1916 
1917     assert(w.data == "null");
1918 }
1919 
1920 /// Integrals are formatted like $(REF printf, core, stdc, stdio).
1921 @safe pure unittest
1922 {
1923     import std.array : appender;
1924     auto w = appender!string();
1925     auto spec = singleSpec("%d");
1926     formatValue(w, 1337, spec);
1927 
1928     assert(w.data == "1337");
1929 }
1930 
1931 /// Floating-point values are formatted like $(REF printf, core, stdc, stdio)
1932 @safe unittest
1933 {
1934     import std.array : appender;
1935     auto w = appender!string();
1936     auto spec = singleSpec("%.1f");
1937     formatValue(w, 1337.7, spec);
1938 
1939     assert(w.data == "1337.7");
1940 }
1941 
1942 /**
1943  * Individual characters (`char, `wchar`, or `dchar`) are formatted as
1944  * Unicode characters with `%s` and as integers with integral-specific format
1945  * specs.
1946  */
1947 @safe pure unittest
1948 {
1949     import std.array : appender;
1950     auto w = appender!string();
1951     auto spec = singleSpec("%c");
1952     formatValue(w, 'a', spec);
1953 
1954     assert(w.data == "a");
1955 }
1956 
1957 /// Strings are formatted like $(REF printf, core, stdc, stdio)
1958 @safe pure unittest
1959 {
1960     import std.array : appender;
1961     auto w = appender!string();
1962     auto spec = singleSpec("%s");
1963     formatValue(w, "hello", spec);
1964 
1965     assert(w.data == "hello");
1966 }
1967 
1968 /// Static-size arrays are formatted as dynamic arrays.
1969 @safe pure unittest
1970 {
1971     import std.array : appender;
1972     auto w = appender!string();
1973     auto spec = singleSpec("%s");
1974     char[2] two = ['a', 'b'];
1975     formatValue(w, two, spec);
1976 
1977     assert(w.data == "ab");
1978 }
1979 
1980 /**
1981  * Dynamic arrays are formatted as input ranges.
1982  *
1983  * Specializations:
1984  *   $(UL
1985  *      $(LI `void[]` is formatted like `ubyte[]`.)
1986  *      $(LI Const array is converted to input range by removing its qualifier.)
1987  *   )
1988  */
1989 @safe pure unittest
1990 {
1991     import std.array : appender;
1992     auto w = appender!string();
1993     auto spec = singleSpec("%s");
1994     auto two = [1, 2];
1995     formatValue(w, two, spec);
1996 
1997     assert(w.data == "[1, 2]");
1998 }
1999 
2000 /**
2001  * Associative arrays are formatted by using `':'` and `", "` as
2002  * separators, and enclosed by `'['` and `']'`.
2003  */
2004 @safe pure unittest
2005 {
2006     import std.array : appender;
2007     auto w = appender!string();
2008     auto spec = singleSpec("%s");
2009     auto aa = ["H":"W"];
2010     formatValue(w, aa, spec);
2011 
2012     assert(w.data == "[\"H\":\"W\"]", w.data);
2013 }
2014 
2015 /// `enum`s are formatted like their base value
2016 @safe pure unittest
2017 {
2018     import std.array : appender;
2019     auto w = appender!string();
2020     auto spec = singleSpec("%s");
2021 
2022     enum A { first, second, third }
2023 
2024     formatValue(w, A.second, spec);
2025 
2026     assert(w.data == "second");
2027 }
2028 
2029 /**
2030  * Formatting a struct by defining a method `toString`, which takes an output
2031  * range.
2032  *
2033  * It's recommended that any `toString` using $(REF_ALTTEXT output ranges, isOutputRange, std,range,primitives)
2034  * use $(REF put, std,range,primitives) rather than use the `put` method of the range
2035  * directly.
2036  */
2037 @safe unittest
2038 {
2039     import std.array : appender;
2040     import std.range.primitives;
2041 
2042     static struct Point
2043     {
2044         int x, y;
2045 
2046         void toString(W)(ref W writer, scope const ref FormatSpec!char f)
2047         if (isOutputRange!(W, char))
2048         {
2049             // std.range.primitives.put
2050             put(writer, "(");
2051             formatValue(writer, x, f);
2052             put(writer, ",");
2053             formatValue(writer, y, f);
2054             put(writer, ")");
2055         }
2056     }
2057 
2058     auto w = appender!string();
2059     auto spec = singleSpec("%s");
2060     auto p = Point(16, 11);
2061 
2062     formatValue(w, p, spec);
2063     assert(w.data == "(16,11)");
2064 }
2065 
2066 /**
2067  * Another example of formatting a `struct` with a defined `toString`,
2068  * this time using the `scope delegate` method.
2069  *
2070  * $(RED This method is now discouraged for non-virtual functions).
2071  * If possible, please use the output range method instead.
2072  */
2073 @safe unittest
2074 {
2075    static struct Point
2076    {
2077        int x, y;
2078 
2079        void toString(scope void delegate(scope const(char)[]) @safe sink,
2080                      scope const FormatSpec!char fmt) const
2081        {
2082            sink("(");
2083            sink.formatValue(x, fmt);
2084            sink(",");
2085            sink.formatValue(y, fmt);
2086            sink(")");
2087        }
2088    }
2089 
2090    auto p = Point(16,11);
2091    assert(format("%03d", p) == "(016,011)");
2092    assert(format("%02x", p) == "(10,0b)");
2093 }
2094 
2095 /// Pointers are formatted as hex integers.
2096 @system pure unittest
2097 {
2098     import std.array : appender;
2099     auto w = appender!string();
2100     auto spec = singleSpec("%s");
2101 
2102     auto q = cast(void*) 0xFFEECCAA;
2103     formatValue(w, q, spec);
2104 
2105     assert(w.data == "FFEECCAA");
2106 }
2107 
2108 /// SIMD vectors are formatted as arrays.
2109 @safe unittest
2110 {
2111     import core.simd;
2112     import std.array : appender;
2113     auto w = appender!string();
2114     auto spec = singleSpec("%s");
2115 
2116     static if (is(float4))
2117     {
2118         version (X86) {}
2119         else
2120         {
2121             float4 f4;
2122             f4.array[0] = 1;
2123             f4.array[1] = 2;
2124             f4.array[2] = 3;
2125             f4.array[3] = 4;
2126 
2127             formatValue(w, f4, spec);
2128             assert(w.data == "[1, 2, 3, 4]");
2129         }
2130     }
2131 }
2132 
2133 /// Delegates are formatted by `ReturnType delegate(Parameters) FunctionAttributes`
2134 @safe unittest
2135 {
2136     import std.conv : to;
2137 
2138     int i;
2139 
2140     int foo(short k) @nogc
2141     {
2142         return i + k;
2143     }
2144 
2145     @system int delegate(short) @nogc bar() nothrow pure
2146     {
2147         int* p = new int(1);
2148         i = *p;
2149         return &foo;
2150     }
2151 
2152     assert(to!string(&bar) == "int delegate(short) @nogc delegate() pure nothrow @system");
2153     assert(() @trusted { return bar()(3); }() == 4);
2154 }
2155 
2156 /*
2157     `bool`s are formatted as `"true"` or `"false"` with `%s` and as `1` or
2158     `0` with integral-specific format specs.
2159  */
2160 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
2161 if (is(BooleanTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
2162 {
2163     BooleanTypeOf!T val = obj;
2164 
2165     if (f.spec == 's')
2166         writeAligned(w, val ? "true" : "false", f);
2167     else
2168         formatValueImpl(w, cast(int) val, f);
2169 }
2170 
2171 @safe pure unittest
2172 {
2173     assertCTFEable!(
2174     {
2175         formatTest( false, "false" );
2176         formatTest( true,  "true"  );
2177     });
2178 }
2179 @system unittest
2180 {
2181     class C1 { bool val; alias val this; this(bool v){ val = v; } }
2182     class C2 { bool val; alias val this; this(bool v){ val = v; }
2183                override string toString() const { return "C"; } }
2184     formatTest( new C1(false), "false" );
2185     formatTest( new C1(true),  "true" );
2186     formatTest( new C2(false), "C" );
2187     formatTest( new C2(true),  "C" );
2188 
2189     struct S1 { bool val; alias val this; }
2190     struct S2 { bool val; alias val this;
2191                 string toString() const { return "S"; } }
2192     formatTest( S1(false), "false" );
2193     formatTest( S1(true),  "true"  );
2194     formatTest( S2(false), "S" );
2195     formatTest( S2(true),  "S" );
2196 }
2197 
2198 @safe pure unittest
2199 {
2200     string t1 = format("[%6s] [%6s] [%-6s]", true, false, true);
2201     assert(t1 == "[  true] [ false] [true  ]");
2202 
2203     string t2 = format("[%3s] [%-2s]", true, false);
2204     assert(t2 == "[true] [false]");
2205 }
2206 
2207 /*
2208     `null` literal is formatted as `"null"`
2209 */
2210 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
2211 if (is(immutable T == immutable typeof(null)) && !is(T == enum) && !hasToString!(T, Char))
2212 {
2213     const spec = f.spec;
2214     enforceFmt(spec == 's',
2215         "null literal cannot match %" ~ spec);
2216 
2217     writeAligned(w, "null", f);
2218 }
2219 
2220 @safe pure unittest
2221 {
2222     assert(collectExceptionMsg!FormatException(format("%p", null)).back == 'p');
2223 
2224     assertCTFEable!(
2225     {
2226         formatTest( null, "null" );
2227     });
2228 }
2229 
2230 @safe pure unittest
2231 {
2232     string t = format("[%6s] [%-6s]", null, null);
2233     assert(t == "[  null] [null  ]");
2234 }
2235 
2236 /*
2237     Integrals are formatted like $(REF printf, core, stdc, stdio).
2238 */
2239 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
2240 if (is(IntegralTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
2241 {
2242     alias U = IntegralTypeOf!T;
2243     U val = obj;    // Extracting alias this may be impure/system/may-throw
2244 
2245     const spec = f.spec;
2246     if (spec == 'r')
2247     {
2248         // raw write, skip all else and write the thing
2249         auto raw = (ref val)@trusted{
2250             return (cast(const char*) &val)[0 .. val.sizeof];
2251         }(val);
2252         if (needToSwapEndianess(f))
2253         {
2254             foreach_reverse (c; raw)
2255                 put(w, c);
2256         }
2257         else
2258         {
2259             foreach (c; raw)
2260                 put(w, c);
2261         }
2262         return;
2263     }
2264 
2265     immutable uint base =
2266         spec == 'x' || spec == 'X' ? 16 :
2267         spec == 'o' ? 8 :
2268         spec == 'b' ? 2 :
2269         spec == 's' || spec == 'd' || spec == 'u' ? 10 :
2270         0;
2271     enforceFmt(base > 0,
2272         "incompatible format character for integral argument: %" ~ spec);
2273 
2274     // Forward on to formatIntegral to handle both U and const(U)
2275     // Saves duplication of code for both versions.
2276     static if (is(ucent) && (is(U == cent) || is(U == ucent)))
2277         alias C = U;
2278     else static if (isSigned!U)
2279         alias C = long;
2280     else
2281         alias C = ulong;
2282     formatIntegral(w, cast(C) val, f, base, Unsigned!U.max);
2283 }
2284 
2285 private void formatIntegral(Writer, T, Char)(ref Writer w, const(T) val, scope const ref FormatSpec!Char fs,
2286     uint base, ulong mask)
2287 {
2288     T arg = val;
2289 
2290     immutable negative = (base == 10 && arg < 0);
2291     if (negative)
2292     {
2293         arg = -arg;
2294     }
2295 
2296     // All unsigned integral types should fit in ulong.
2297     static if (is(ucent) && is(typeof(arg) == ucent))
2298         formatUnsigned(w, (cast(ucent) arg) & mask, fs, base, negative);
2299     else
2300         formatUnsigned(w, (cast(ulong) arg) & mask, fs, base, negative);
2301 }
2302 
2303 private void formatUnsigned(Writer, T, Char)
2304 (ref Writer w, T arg, scope const ref FormatSpec!Char fs, uint base, bool negative)
2305 {
2306     /* Write string:
2307      *    leftpad prefix1 prefix2 zerofill digits rightpad
2308      */
2309 
2310     /* Convert arg to digits[].
2311      * Note that 0 becomes an empty digits[]
2312      */
2313     char[64] buffer = void; // 64 bits in base 2 at most
2314     char[] digits;
2315     if (arg < base && base <= 10 && arg)
2316     {
2317         // Most numbers are a single digit - avoid expensive divide
2318         buffer[0] = cast(char)(arg + '0');
2319         digits = buffer[0 .. 1];
2320     }
2321     else
2322     {
2323         size_t i = buffer.length;
2324         while (arg)
2325         {
2326             --i;
2327             char c = cast(char) (arg % base);
2328             arg /= base;
2329             if (c < 10)
2330                 buffer[i] = cast(char)(c + '0');
2331             else
2332                 buffer[i] = cast(char)(c + (fs.spec == 'x' ? 'a' - 10 : 'A' - 10));
2333         }
2334         digits = buffer[i .. $]; // got the digits without the sign
2335     }
2336 
2337 
2338     immutable precision = (fs.precision == fs.UNSPECIFIED) ? 1 : fs.precision;
2339 
2340     char padChar = 0;
2341     if (!fs.flDash)
2342     {
2343         padChar = (fs.flZero && fs.precision == fs.UNSPECIFIED) ? '0' : ' ';
2344     }
2345 
2346     // Compute prefix1 and prefix2
2347     char prefix1 = 0;
2348     char prefix2 = 0;
2349     if (base == 10)
2350     {
2351         if (negative)
2352             prefix1 = '-';
2353         else if (fs.flPlus)
2354             prefix1 = '+';
2355         else if (fs.flSpace)
2356             prefix1 = ' ';
2357     }
2358     else if (base == 16 && fs.flHash && digits.length)
2359     {
2360         prefix1 = '0';
2361         prefix2 = fs.spec == 'x' ? 'x' : 'X';
2362     }
2363     // adjust precision to print a '0' for octal if alternate format is on
2364     else if (base == 8 && fs.flHash &&
2365              (precision <= 1 || precision <= digits.length) && // too low precision
2366              digits.length > 0)
2367         prefix1 = '0';
2368 
2369     size_t zerofill = precision > digits.length ? precision - digits.length : 0;
2370     size_t leftpad = 0;
2371     size_t rightpad = 0;
2372 
2373     immutable prefixWidth = (prefix1 != 0) + (prefix2 != 0);
2374     size_t finalWidth, separatorsCount;
2375     if (fs.flSeparator != 0)
2376     {
2377         finalWidth = prefixWidth + digits.length + ((digits.length > 0) ? (digits.length - 1) / fs.separators : 0);
2378         if (finalWidth < fs.width)
2379             finalWidth = fs.width + (padChar == '0') * (((fs.width - prefixWidth) % (fs.separators + 1) == 0) ? 1 : 0);
2380 
2381         separatorsCount = (padChar == '0') ? (finalWidth - prefixWidth - 1) / (fs.separators + 1) :
2382                          ((digits.length > 0) ? (digits.length - 1) / fs.separators : 0);
2383     }
2384     else
2385     {
2386         import std.algorithm.comparison : max;
2387         finalWidth = max(fs.width, prefixWidth + digits.length);
2388     }
2389 
2390     immutable ptrdiff_t spacesToPrint =
2391         finalWidth - (
2392             + prefixWidth
2393             + zerofill
2394             + digits.length
2395             + separatorsCount
2396         );
2397     if (spacesToPrint > 0) // need to do some padding
2398     {
2399         if (padChar == '0')
2400             zerofill += spacesToPrint;
2401         else if (padChar)
2402             leftpad = spacesToPrint;
2403         else
2404             rightpad = spacesToPrint;
2405     }
2406 
2407     // Print
2408     foreach (i ; 0 .. leftpad)
2409         put(w, ' ');
2410 
2411     if (prefix1) put(w, prefix1);
2412     if (prefix2) put(w, prefix2);
2413 
2414     if (fs.flSeparator)
2415     {
2416         if (zerofill > 0)
2417         {
2418             put(w, '0');
2419             --zerofill;
2420         }
2421 
2422         int j = cast(int) (finalWidth - prefixWidth - separatorsCount - 1);
2423         for (size_t i = 0; i < zerofill; ++i, --j)
2424         {
2425             if (j % fs.separators == 0)
2426             {
2427                 put(w, fs.separatorChar);
2428             }
2429             put(w, '0');
2430         }
2431     }
2432     else
2433     {
2434         foreach (i ; 0 .. zerofill)
2435             put(w, '0');
2436     }
2437 
2438     if (fs.flSeparator)
2439     {
2440         for (size_t j = 0; j < digits.length; ++j)
2441         {
2442             if (((j != 0) || ((spacesToPrint > 0) && (padChar == '0'))) && (digits.length - j) % fs.separators == 0)
2443             {
2444                 put(w, fs.separatorChar);
2445             }
2446             put(w, digits[j]);
2447         }
2448     }
2449     else
2450     {
2451         put(w, digits);
2452     }
2453 
2454     foreach (i ; 0 .. rightpad)
2455         put(w, ' ');
2456 }
2457 
2458 // https://issues.dlang.org/show_bug.cgi?id=18838
2459 @safe pure unittest
2460 {
2461     assert("%12,d".format(0) == "           0");
2462 }
2463 
2464 @safe pure unittest
2465 {
2466     assert(collectExceptionMsg!FormatException(format("%c", 5)).back == 'c');
2467 
2468     assertCTFEable!(
2469     {
2470         formatTest(9, "9");
2471         formatTest( 10, "10" );
2472     });
2473 }
2474 
2475 @system unittest
2476 {
2477     class C1 { long val; alias val this; this(long v){ val = v; } }
2478     class C2 { long val; alias val this; this(long v){ val = v; }
2479                override string toString() const { return "C"; } }
2480     formatTest( new C1(10), "10" );
2481     formatTest( new C2(10), "C" );
2482 
2483     struct S1 { long val; alias val this; }
2484     struct S2 { long val; alias val this;
2485                 string toString() const { return "S"; } }
2486     formatTest( S1(10), "10" );
2487     formatTest( S2(10), "S" );
2488 }
2489 
2490 // https://issues.dlang.org/show_bug.cgi?id=9117
2491 @safe unittest
2492 {
2493     static struct Frop {}
2494 
2495     static struct Foo
2496     {
2497         int n = 0;
2498         alias n this;
2499         T opCast(T) () if (is(T == Frop))
2500         {
2501             return Frop();
2502         }
2503         string toString()
2504         {
2505             return "Foo";
2506         }
2507     }
2508 
2509     static struct Bar
2510     {
2511         Foo foo;
2512         alias foo this;
2513         string toString()
2514         {
2515             return "Bar";
2516         }
2517     }
2518 
2519     const(char)[] result;
2520     void put(scope const char[] s){ result ~= s; }
2521 
2522     Foo foo;
2523     formattedWrite(&put, "%s", foo);    // OK
2524     assert(result == "Foo");
2525 
2526     result = null;
2527 
2528     Bar bar;
2529     formattedWrite(&put, "%s", bar);    // NG
2530     assert(result == "Bar");
2531 
2532     result = null;
2533 
2534     int i = 9;
2535     formattedWrite(&put, "%s", 9);
2536     assert(result == "9");
2537 }
2538 
2539 // https://issues.dlang.org/show_bug.cgi?id=20064
2540 @safe unittest
2541 {
2542     assert(format( "%03,d",  1234) ==              "1,234");
2543     assert(format( "%04,d",  1234) ==              "1,234");
2544     assert(format( "%05,d",  1234) ==              "1,234");
2545     assert(format( "%06,d",  1234) ==             "01,234");
2546     assert(format( "%07,d",  1234) ==            "001,234");
2547     assert(format( "%08,d",  1234) ==          "0,001,234");
2548     assert(format( "%09,d",  1234) ==          "0,001,234");
2549     assert(format("%010,d",  1234) ==         "00,001,234");
2550     assert(format("%011,d",  1234) ==        "000,001,234");
2551     assert(format("%012,d",  1234) ==      "0,000,001,234");
2552     assert(format("%013,d",  1234) ==      "0,000,001,234");
2553     assert(format("%014,d",  1234) ==     "00,000,001,234");
2554     assert(format("%015,d",  1234) ==    "000,000,001,234");
2555     assert(format("%016,d",  1234) ==  "0,000,000,001,234");
2556     assert(format("%017,d",  1234) ==  "0,000,000,001,234");
2557 
2558     assert(format( "%03,d", -1234) ==             "-1,234");
2559     assert(format( "%04,d", -1234) ==             "-1,234");
2560     assert(format( "%05,d", -1234) ==             "-1,234");
2561     assert(format( "%06,d", -1234) ==             "-1,234");
2562     assert(format( "%07,d", -1234) ==            "-01,234");
2563     assert(format( "%08,d", -1234) ==           "-001,234");
2564     assert(format( "%09,d", -1234) ==         "-0,001,234");
2565     assert(format("%010,d", -1234) ==         "-0,001,234");
2566     assert(format("%011,d", -1234) ==        "-00,001,234");
2567     assert(format("%012,d", -1234) ==       "-000,001,234");
2568     assert(format("%013,d", -1234) ==     "-0,000,001,234");
2569     assert(format("%014,d", -1234) ==     "-0,000,001,234");
2570     assert(format("%015,d", -1234) ==    "-00,000,001,234");
2571     assert(format("%016,d", -1234) ==   "-000,000,001,234");
2572     assert(format("%017,d", -1234) == "-0,000,000,001,234");
2573 }
2574 
2575 @safe pure unittest
2576 {
2577     string t1 = format("[%6s] [%-6s]", 123, 123);
2578     assert(t1 == "[   123] [123   ]");
2579 
2580     string t2 = format("[%6s] [%-6s]", -123, -123);
2581     assert(t2 == "[  -123] [-123  ]");
2582 }
2583 
2584 private enum ctfpMessage = "Cannot format floating point types at compile-time";
2585 
2586 private enum RoundingMode { up, down, toZero, toNearestTiesToEven, toNearestTiesAwayFromZero }
2587 
2588 /*
2589     Floating-point values are formatted like $(REF printf, core, stdc, stdio)
2590  */
2591 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
2592 if (is(FloatingPointTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
2593 {
2594     import std.algorithm.comparison : min;
2595     import std.algorithm.searching : find;
2596     import std..string : indexOf, indexOfAny, indexOfNeither;
2597     import std.math : isInfinity, isNaN, signbit;
2598     import std.ascii : isUpper;
2599 
2600     string nanInfStr(scope const ref FormatSpec!Char f, const bool nan,
2601             const bool inf, const int sb, const bool up) @safe pure nothrow
2602     {
2603         return nan
2604             ? up
2605                 ? sb ? "-NAN" : f.flPlus ? "+NAN" : (f.flSpace ? " NAN" : "NAN")
2606                 : sb ? "-nan" : f.flPlus ? "+nan" : (f.flSpace ? " nan" : "nan")
2607             : inf
2608                 ? up
2609                     ? sb ? "-INF" : f.flPlus ? "+INF" : (f.flSpace ? " INF" : "INF")
2610                     : sb ? "-inf" : f.flPlus ? "+inf" : (f.flSpace ? " inf" : "inf")
2611                 : "";
2612     }
2613 
2614     FloatingPointTypeOf!T val = obj;
2615     const char spec = f.spec;
2616 
2617     if (spec == 'r')
2618     {
2619         // raw write, skip all else and write the thing
2620         auto raw = (ref val)@trusted{
2621             return (cast(const char*) &val)[0 .. val.sizeof];
2622         }(val);
2623         if (needToSwapEndianess(f))
2624         {
2625             foreach_reverse (c; raw)
2626                 put(w, c);
2627         }
2628         else
2629         {
2630             foreach (c; raw)
2631                 put(w, c);
2632         }
2633         return;
2634     }
2635     enforceFmt(find("fgFGaAeEs", spec).length,
2636         "incompatible format character for floating point argument: %" ~ spec);
2637     enforceFmt(!__ctfe, ctfpMessage);
2638 
2639     FormatSpec!Char fs = f; // fs is copy for change its values.
2640     const spec2 = spec == 's' ? 'g' : spec;
2641 
2642     version (CRuntime_Microsoft)
2643     {
2644         // convert early to get "inf" in case of overflow
2645         // windows handels inf and nan strange
2646         // https://devblogs.microsoft.com/oldnewthing/20130228-01/?p=5103
2647         immutable double tval = val;
2648     }
2649     else
2650     {
2651         alias tval = val;
2652     }
2653 
2654     const nan = isNaN(tval);
2655     const inf = isInfinity(tval);
2656 
2657     char[512] buf2 = void;
2658     size_t len;
2659     char[] buf;
2660     if (fs.spec=='a' || fs.spec=='A' || fs.spec=='e' || fs.spec=='E')
2661     {
2662         static if (is(T == float) || is(T == double) || (is(T == real) && T.mant_dig == double.mant_dig))
2663         {
2664             import std.math;
2665 
2666             auto mode = RoundingMode.toNearestTiesToEven;
2667 
2668             // std.math's FloatingPointControl isn't available on all target platforms
2669             static if (is(FloatingPointControl))
2670             {
2671                 switch (FloatingPointControl.rounding)
2672                 {
2673                 case FloatingPointControl.roundUp:
2674                     mode = RoundingMode.up;
2675                     break;
2676                 case FloatingPointControl.roundDown:
2677                     mode = RoundingMode.down;
2678                     break;
2679                 case FloatingPointControl.roundToZero:
2680                     mode = RoundingMode.toZero;
2681                     break;
2682                 case FloatingPointControl.roundToNearest:
2683                     mode = RoundingMode.toNearestTiesToEven;
2684                     break;
2685                 default: assert(false);
2686                 }
2687             }
2688 
2689             buf = printFloat(buf2[], val, fs, mode);
2690             len = buf.length;
2691         }
2692         else
2693             goto useSnprintf;
2694     }
2695     else
2696     {
2697 useSnprintf:
2698         if (nan || inf)
2699         {
2700             const sb = signbit(tval);
2701             const up = isUpper(spec);
2702             string ns = nanInfStr(f, nan, inf, sb, up);
2703             FormatSpec!Char co;
2704             co.spec = 's';
2705             co.width = f.width;
2706             co.flDash = f.flDash;
2707             formatValue(w, ns, co);
2708             return;
2709         }
2710 
2711         char[1 /*%*/ + 5 /*flags*/ + 3 /*width.prec*/ + 2 /*format*/
2712              + 1 /*\0*/] sprintfSpec = void;
2713         sprintfSpec[0] = '%';
2714         uint i = 1;
2715         if (fs.flDash) sprintfSpec[i++] = '-';
2716         if (fs.flPlus) sprintfSpec[i++] = '+';
2717         if (fs.flZero) sprintfSpec[i++] = '0';
2718         if (fs.flSpace) sprintfSpec[i++] = ' ';
2719         if (fs.flHash) sprintfSpec[i++] = '#';
2720         sprintfSpec[i .. i + 3] = "*.*";
2721         i += 3;
2722         if (is(immutable typeof(val) == immutable real)) sprintfSpec[i++] = 'L';
2723         sprintfSpec[i++] = spec2;
2724         sprintfSpec[i] = 0;
2725         //printf("format: '%s'; geeba: %g\n", sprintfSpec.ptr, val);
2726 
2727         //writefln("'%s'", sprintfSpec[0 .. i]);
2728 
2729         immutable n = ()@trusted{
2730             import core.stdc.stdio : snprintf;
2731             return snprintf(buf2.ptr, buf2.length,
2732                             sprintfSpec.ptr,
2733                             fs.width,
2734                             // negative precision is same as no precision specified
2735                             fs.precision == fs.UNSPECIFIED ? -1 : fs.precision,
2736                             tval);
2737         }();
2738 
2739         enforceFmt(n >= 0,
2740                    "floating point formatting failure");
2741 
2742         len = min(n, buf2.length-1);
2743         buf = buf2;
2744     }
2745 
2746     if (fs.flSeparator && !inf && !nan)
2747     {
2748         ptrdiff_t indexOfRemovable()
2749         {
2750             if (len < 2)
2751                 return -1;
2752 
2753             size_t start = (buf[0 .. 1].indexOfAny(" 0123456789") == -1) ? 1 : 0;
2754             if (len < 2 + start)
2755                 return -1;
2756             if ((buf[start] == ' ') || (buf[start] == '0' && buf[start + 1] != '.'))
2757                 return start;
2758 
2759             return -1;
2760         }
2761 
2762         ptrdiff_t dot, firstDigit, ePos, dotIdx, firstLen;
2763         size_t separatorScoreCnt;
2764 
2765         while (true)
2766         {
2767             dot = buf[0 .. len].indexOf('.');
2768             firstDigit = buf[0 .. len].indexOfAny("0123456789");
2769             ePos = buf[0 .. len].indexOf('e');
2770             dotIdx = dot == -1 ? ePos == -1 ? len : ePos : dot;
2771 
2772             firstLen = dotIdx - firstDigit;
2773             separatorScoreCnt = (firstLen > 0) ? (firstLen - 1) / fs.separators : 0;
2774 
2775             ptrdiff_t removableIdx = (len + separatorScoreCnt > fs.width) ? indexOfRemovable() : -1;
2776             if ((removableIdx != -1) &&
2777                 ((firstLen - (buf[removableIdx] == '0' ? 2 : 1)) / fs.separators + len - 1 >= fs.width))
2778             {
2779                 buf[removableIdx .. $ - 1] = buf.dup[removableIdx + 1 .. $];
2780                 len--;
2781             }
2782             else
2783                 break;
2784         }
2785 
2786         immutable afterDotIdx = (ePos != -1) ? ePos : len;
2787 
2788         // plus, minus, prefix
2789         if (firstDigit > 0)
2790         {
2791             put(w, buf[0 .. firstDigit]);
2792         }
2793 
2794         // digits until dot with separator
2795         for (auto j = 0; j < firstLen; ++j)
2796         {
2797             if (j > 0 && (firstLen - j) % fs.separators == 0)
2798             {
2799                 put(w, fs.separatorChar);
2800             }
2801             put(w, buf[j + firstDigit]);
2802         }
2803 
2804         // print dot for decimal numbers only or with '#' format specifier
2805         if (dot != -1 || fs.flHash)
2806         {
2807             put(w, '.');
2808         }
2809 
2810         // digits after dot
2811         for (auto j = dotIdx + 1; j < afterDotIdx; ++j)
2812         {
2813             put(w, buf[j]);
2814         }
2815 
2816         // rest
2817         if (ePos != -1)
2818         {
2819             put(w, buf[afterDotIdx .. len]);
2820         }
2821     }
2822     else
2823     {
2824         put(w, buf[0 .. len]);
2825     }
2826 }
2827 
2828 @safe unittest
2829 {
2830     assert(format("%.1f", 1337.7) == "1337.7");
2831     assert(format("%,3.2f", 1331.982) == "1,331.98");
2832     assert(format("%,3.0f", 1303.1982) == "1,303");
2833     assert(format("%#,3.4f", 1303.1982) == "1,303.1982");
2834     assert(format("%#,3.0f", 1303.1982) == "1,303.");
2835 }
2836 
2837 @safe /*pure*/ unittest     // formatting floating point values is now impure
2838 {
2839     import std.conv : to;
2840 
2841     assert(collectExceptionMsg!FormatException(format("%d", 5.1)).back == 'd');
2842 
2843     static foreach (T; AliasSeq!(float, double, real))
2844     {
2845         formatTest( to!(          T)(5.5), "5.5" );
2846         formatTest( to!(    const T)(5.5), "5.5" );
2847         formatTest( to!(immutable T)(5.5), "5.5" );
2848 
2849         formatTest( T.nan, "nan" );
2850     }
2851 }
2852 
2853 @system unittest
2854 {
2855     formatTest( 2.25, "2.25" );
2856 
2857     class C1 { double val; alias val this; this(double v){ val = v; } }
2858     class C2 { double val; alias val this; this(double v){ val = v; }
2859                override string toString() const { return "C"; } }
2860     formatTest( new C1(2.25), "2.25" );
2861     formatTest( new C2(2.25), "C" );
2862 
2863     struct S1 { double val; alias val this; }
2864     struct S2 { double val; alias val this;
2865                 string toString() const { return "S"; } }
2866     formatTest( S1(2.25), "2.25" );
2867     formatTest( S2(2.25), "S" );
2868 }
2869 
2870 // https://issues.dlang.org/show_bug.cgi?id=19939
2871 @safe unittest
2872 {
2873     assert(format("^%13,3.2f$",          1.00) == "^         1.00$");
2874     assert(format("^%13,3.2f$",         10.00) == "^        10.00$");
2875     assert(format("^%13,3.2f$",        100.00) == "^       100.00$");
2876     assert(format("^%13,3.2f$",      1_000.00) == "^     1,000.00$");
2877     assert(format("^%13,3.2f$",     10_000.00) == "^    10,000.00$");
2878     assert(format("^%13,3.2f$",    100_000.00) == "^   100,000.00$");
2879     assert(format("^%13,3.2f$",  1_000_000.00) == "^ 1,000,000.00$");
2880     assert(format("^%13,3.2f$", 10_000_000.00) == "^10,000,000.00$");
2881 }
2882 
2883 // https://issues.dlang.org/show_bug.cgi?id=20069
2884 @safe unittest
2885 {
2886     assert(format("%012,f",   -1234.0) ==    "-1,234.000000");
2887     assert(format("%013,f",   -1234.0) ==    "-1,234.000000");
2888     assert(format("%014,f",   -1234.0) ==   "-01,234.000000");
2889     assert(format("%011,f",    1234.0) ==     "1,234.000000");
2890     assert(format("%012,f",    1234.0) ==     "1,234.000000");
2891     assert(format("%013,f",    1234.0) ==    "01,234.000000");
2892     assert(format("%014,f",    1234.0) ==   "001,234.000000");
2893     assert(format("%015,f",    1234.0) == "0,001,234.000000");
2894     assert(format("%016,f",    1234.0) == "0,001,234.000000");
2895 
2896     assert(format( "%08,.2f", -1234.0) ==        "-1,234.00");
2897     assert(format( "%09,.2f", -1234.0) ==        "-1,234.00");
2898     assert(format("%010,.2f", -1234.0) ==       "-01,234.00");
2899     assert(format("%011,.2f", -1234.0) ==      "-001,234.00");
2900     assert(format("%012,.2f", -1234.0) ==    "-0,001,234.00");
2901     assert(format("%013,.2f", -1234.0) ==    "-0,001,234.00");
2902     assert(format("%014,.2f", -1234.0) ==   "-00,001,234.00");
2903     assert(format( "%08,.2f",  1234.0) ==         "1,234.00");
2904     assert(format( "%09,.2f",  1234.0) ==        "01,234.00");
2905     assert(format("%010,.2f",  1234.0) ==       "001,234.00");
2906     assert(format("%011,.2f",  1234.0) ==     "0,001,234.00");
2907     assert(format("%012,.2f",  1234.0) ==     "0,001,234.00");
2908     assert(format("%013,.2f",  1234.0) ==    "00,001,234.00");
2909     assert(format("%014,.2f",  1234.0) ==   "000,001,234.00");
2910     assert(format("%015,.2f",  1234.0) == "0,000,001,234.00");
2911     assert(format("%016,.2f",  1234.0) == "0,000,001,234.00");
2912 }
2913 
2914 @safe unittest
2915 {
2916     string t1 = format("[%6s] [%-6s]", 12.3, 12.3);
2917     assert(t1 == "[  12.3] [12.3  ]");
2918 
2919     string t2 = format("[%6s] [%-6s]", -12.3, -12.3);
2920     assert(t2 == "[ -12.3] [-12.3 ]");
2921 }
2922 
2923 // https://issues.dlang.org/show_bug.cgi?id=20396
2924 @safe unittest
2925 {
2926     import std.math : nextUp;
2927 
2928     assert(format!"%a"(nextUp(0.0f)) == "0x0.000002p-126");
2929     assert(format!"%a"(nextUp(0.0)) == "0x0.0000000000001p-1022");
2930 }
2931 
2932 // https://issues.dlang.org/show_bug.cgi?id=20371
2933 @safe unittest
2934 {
2935     assert(format!"%.1000a"(1.0) ==
2936            "0x1.000000000000000000000000000000000000000000000000000000000000000000000000000"
2937            ~"000000000000000000000000000000000000000000000000000000000000000000000000000000"
2938            ~"000000000000000000000000000000000000000000000000000000000000000000000000000000"
2939            ~"000000000000000000000000000000000000000000000000000000000000000000000000000000"
2940            ~"000000000000000000000000000000000000000000000000000000000000000000000000000000"
2941            ~"000000000000000000000000000000000000000000000000000000000000000000000000000000"
2942            ~"000000000000000000000000000000000000000000000000000000000000000000000000000000"
2943            ~"000000000000000000000000000000000000000000000000000000000000000000000000000000"
2944            ~"000000000000000000000000000000000000000000000000000000000000000000000000000000"
2945            ~"000000000000000000000000000000000000000000000000000000000000000000000000000000"
2946            ~"000000000000000000000000000000000000000000000000000000000000000000000000000000"
2947            ~"000000000000000000000000000000000000000000000000000000000000000000000000000000"
2948            ~"0000000000000000000000000000000000000000000000000000000000000000000p+0");
2949 }
2950 
2951 /*
2952     Formatting a `creal` is deprecated but still kept around for a while.
2953  */
2954 deprecated("Use of complex types is deprecated. Use std.complex")
2955 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
2956 if (is(immutable T : immutable creal) && !is(T == enum) && !hasToString!(T, Char))
2957 {
2958     immutable creal val = obj;
2959 
2960     formatValueImpl(w, val.re, f);
2961     if (val.im >= 0)
2962     {
2963         put(w, '+');
2964     }
2965     formatValueImpl(w, val.im, f);
2966     put(w, 'i');
2967 }
2968 
2969 version (TestComplex)
2970 deprecated
2971 @safe /*pure*/ unittest     // formatting floating point values is now impure
2972 {
2973     import std.conv : to;
2974     static foreach (T; AliasSeq!(cfloat, cdouble, creal))
2975     {
2976         formatTest( to!(          T)(1 + 1i), "1+1i" );
2977         formatTest( to!(    const T)(1 + 1i), "1+1i" );
2978         formatTest( to!(immutable T)(1 + 1i), "1+1i" );
2979     }
2980     static foreach (T; AliasSeq!(cfloat, cdouble, creal))
2981     {
2982         formatTest( to!(          T)(0 - 3i), "0-3i" );
2983         formatTest( to!(    const T)(0 - 3i), "0-3i" );
2984         formatTest( to!(immutable T)(0 - 3i), "0-3i" );
2985     }
2986 }
2987 
2988 version (TestComplex)
2989 deprecated
2990 @system unittest
2991 {
2992     formatTest( 3+2.25i, "3+2.25i" );
2993 
2994     class C1 { cdouble val; alias val this; this(cdouble v){ val = v; } }
2995     class C2 { cdouble val; alias val this; this(cdouble v){ val = v; }
2996                override string toString() const { return "C"; } }
2997     formatTest( new C1(3+2.25i), "3+2.25i" );
2998     formatTest( new C2(3+2.25i), "C" );
2999 
3000     struct S1 { cdouble val; alias val this; }
3001     struct S2 { cdouble val; alias val this;
3002                 string toString() const { return "S"; } }
3003     formatTest( S1(3+2.25i), "3+2.25i" );
3004     formatTest( S2(3+2.25i), "S" );
3005 }
3006 
3007 /*
3008     Formatting an `ireal` is deprecated but still kept around for a while.
3009  */
3010 deprecated("Use of imaginary types is deprecated. Use std.complex")
3011 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
3012 if (is(immutable T : immutable ireal) && !is(T == enum) && !hasToString!(T, Char))
3013 {
3014     immutable ireal val = obj;
3015 
3016     formatValueImpl(w, val.im, f);
3017     put(w, 'i');
3018 }
3019 
3020 version (TestComplex)
3021 deprecated
3022 @safe /*pure*/ unittest     // formatting floating point values is now impure
3023 {
3024     import std.conv : to;
3025     static foreach (T; AliasSeq!(ifloat, idouble, ireal))
3026     {
3027         formatTest( to!(          T)(1i), "1i" );
3028         formatTest( to!(    const T)(1i), "1i" );
3029         formatTest( to!(immutable T)(1i), "1i" );
3030     }
3031 }
3032 
3033 version (TestComplex)
3034 deprecated
3035 @system unittest
3036 {
3037     formatTest( 2.25i, "2.25i" );
3038 
3039     class C1 { idouble val; alias val this; this(idouble v){ val = v; } }
3040     class C2 { idouble val; alias val this; this(idouble v){ val = v; }
3041                override string toString() const { return "C"; } }
3042     formatTest( new C1(2.25i), "2.25i" );
3043     formatTest( new C2(2.25i), "C" );
3044 
3045     struct S1 { idouble val; alias val this; }
3046     struct S2 { idouble val; alias val this;
3047                 string toString() const { return "S"; } }
3048     formatTest( S1(2.25i), "2.25i" );
3049     formatTest( S2(2.25i), "S" );
3050 }
3051 
3052 /*
3053     Individual characters are formatted as Unicode characters with `%s`
3054     and as integers with integral-specific format specs
3055  */
3056 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
3057 if (is(CharTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
3058 {
3059     CharTypeOf!T[1] val = obj;
3060 
3061     if (f.spec == 's' || f.spec == 'c')
3062         writeAligned(w, val[], f);
3063     else
3064     {
3065         alias U = AliasSeq!(ubyte, ushort, uint)[CharTypeOf!T.sizeof/2];
3066         formatValueImpl(w, cast(U) val[0], f);
3067     }
3068 }
3069 
3070 @safe pure unittest
3071 {
3072     assertCTFEable!(
3073     {
3074         formatTest( 'c', "c" );
3075     });
3076 }
3077 
3078 @system unittest
3079 {
3080     class C1 { char val; alias val this; this(char v){ val = v; } }
3081     class C2 { char val; alias val this; this(char v){ val = v; }
3082                override string toString() const { return "C"; } }
3083     formatTest( new C1('c'), "c" );
3084     formatTest( new C2('c'), "C" );
3085 
3086     struct S1 { char val; alias val this; }
3087     struct S2 { char val; alias val this;
3088                 string toString() const { return "S"; } }
3089     formatTest( S1('c'), "c" );
3090     formatTest( S2('c'), "S" );
3091 }
3092 
3093 @safe unittest
3094 {
3095     //Little Endian
3096     formatTest( "%-r", cast( char)'c', ['c'         ] );
3097     formatTest( "%-r", cast(wchar)'c', ['c', 0      ] );
3098     formatTest( "%-r", cast(dchar)'c', ['c', 0, 0, 0] );
3099     formatTest( "%-r", '本', ['\x2c', '\x67'] );
3100 
3101     //Big Endian
3102     formatTest( "%+r", cast( char)'c', [         'c'] );
3103     formatTest( "%+r", cast(wchar)'c', [0,       'c'] );
3104     formatTest( "%+r", cast(dchar)'c', [0, 0, 0, 'c'] );
3105     formatTest( "%+r", '本', ['\x67', '\x2c'] );
3106 }
3107 
3108 
3109 @safe pure unittest
3110 {
3111     string t1 = format("[%6s] [%-6s]", 'A', 'A');
3112     assert(t1 == "[     A] [A     ]");
3113     string t2 = format("[%6s] [%-6s]", '本', '本');
3114     assert(t2 == "[     本] [本     ]");
3115 }
3116 
3117 /*
3118     Strings are formatted like $(REF printf, core, stdc, stdio)
3119  */
3120 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope T obj, scope const ref FormatSpec!Char f)
3121 if (is(StringTypeOf!T) && !is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
3122 {
3123     Unqual!(StringTypeOf!T) val = obj;  // for `alias this`, see bug5371
3124     formatRange(w, val, f);
3125 }
3126 
3127 @safe unittest
3128 {
3129     formatTest( "abc", "abc" );
3130 }
3131 
3132 @system unittest
3133 {
3134     // Test for bug 5371 for classes
3135     class C1 { const string var; alias var this; this(string s){ var = s; } }
3136     class C2 {       string var; alias var this; this(string s){ var = s; } }
3137     formatTest( new C1("c1"), "c1" );
3138     formatTest( new C2("c2"), "c2" );
3139 
3140     // Test for bug 5371 for structs
3141     struct S1 { const string var; alias var this; }
3142     struct S2 {       string var; alias var this; }
3143     formatTest( S1("s1"), "s1" );
3144     formatTest( S2("s2"), "s2" );
3145 }
3146 
3147 @system unittest
3148 {
3149     class  C3 { string val; alias val this; this(string s){ val = s; }
3150                 override string toString() const { return "C"; } }
3151     formatTest( new C3("c3"), "C" );
3152 
3153     struct S3 { string val; alias val this;
3154                 string toString() const { return "S"; } }
3155     formatTest( S3("s3"), "S" );
3156 }
3157 
3158 @safe pure unittest
3159 {
3160     //Little Endian
3161     formatTest( "%-r", "ab"c, ['a'         , 'b'         ] );
3162     formatTest( "%-r", "ab"w, ['a', 0      , 'b', 0      ] );
3163     formatTest( "%-r", "ab"d, ['a', 0, 0, 0, 'b', 0, 0, 0] );
3164     formatTest( "%-r", "日本語"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac', '\xe8', '\xaa', '\x9e'] );
3165     formatTest( "%-r", "日本語"w, ['\xe5', '\x65', '\x2c', '\x67', '\x9e', '\x8a']);
3166     formatTest( "%-r", "日本語"d, ['\xe5', '\x65', '\x00', '\x00', '\x2c', '\x67',
3167         '\x00', '\x00', '\x9e', '\x8a', '\x00', '\x00'] );
3168 
3169     //Big Endian
3170     formatTest( "%+r", "ab"c, [         'a',          'b'] );
3171     formatTest( "%+r", "ab"w, [      0, 'a',       0, 'b'] );
3172     formatTest( "%+r", "ab"d, [0, 0, 0, 'a', 0, 0, 0, 'b'] );
3173     formatTest( "%+r", "日本語"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac', '\xe8', '\xaa', '\x9e'] );
3174     formatTest( "%+r", "日本語"w, ['\x65', '\xe5', '\x67', '\x2c', '\x8a', '\x9e'] );
3175     formatTest( "%+r", "日本語"d, ['\x00', '\x00', '\x65', '\xe5', '\x00', '\x00',
3176         '\x67', '\x2c', '\x00', '\x00', '\x8a', '\x9e'] );
3177 }
3178 
3179 @safe pure unittest
3180 {
3181     string t1 = format("[%6s] [%-6s]", "AB", "AB");
3182     assert(t1 == "[    AB] [AB    ]");
3183     string t2 = format("[%6s] [%-6s]", "本Ä", "本Ä");
3184     assert(t2 == "[    本Ä] [本Ä    ]");
3185 }
3186 
3187 /*
3188     Static-size arrays are formatted as dynamic arrays.
3189  */
3190 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, auto ref T obj, scope const ref FormatSpec!Char f)
3191 if (is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
3192 {
3193     formatValueImpl(w, obj[], f);
3194 }
3195 
3196 // Test for https://issues.dlang.org/show_bug.cgi?id=8310
3197 @safe unittest
3198 {
3199     import std.array : appender;
3200     FormatSpec!char f;
3201     auto w = appender!string();
3202 
3203     char[2] two = ['a', 'b'];
3204     formatValue(w, two, f);
3205 
3206     char[2] getTwo(){ return two; }
3207     formatValue(w, getTwo(), f);
3208 }
3209 
3210 /*
3211     Dynamic arrays are formatted as input ranges.
3212  */
3213 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
3214 if (is(DynamicArrayTypeOf!T) && !is(StringTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
3215 {
3216     static if (is(const(ArrayTypeOf!T) == const(void[])))
3217     {
3218         formatValueImpl(w, cast(const ubyte[]) obj, f);
3219     }
3220     else static if (!isInputRange!T)
3221     {
3222         alias U = Unqual!(ArrayTypeOf!T);
3223         static assert(isInputRange!U, U.stringof ~ " must be an InputRange");
3224         U val = obj;
3225         formatValueImpl(w, val, f);
3226     }
3227     else
3228     {
3229         formatRange(w, obj, f);
3230     }
3231 }
3232 
3233 // alias this, input range I/F, and toString()
3234 @system unittest
3235 {
3236     struct S(int flags)
3237     {
3238         int[] arr;
3239       static if (flags & 1)
3240         alias arr this;
3241 
3242       static if (flags & 2)
3243       {
3244         @property bool empty() const { return arr.length == 0; }
3245         @property int front() const { return arr[0] * 2; }
3246         void popFront() { arr = arr[1..$]; }
3247       }
3248 
3249       static if (flags & 4)
3250         string toString() const { return "S"; }
3251     }
3252     formatTest(S!0b000([0, 1, 2]), "S!0([0, 1, 2])");
3253     formatTest(S!0b001([0, 1, 2]), "[0, 1, 2]");        // Test for bug 7628
3254     formatTest(S!0b010([0, 1, 2]), "[0, 2, 4]");
3255     formatTest(S!0b011([0, 1, 2]), "[0, 2, 4]");
3256     formatTest(S!0b100([0, 1, 2]), "S");
3257     formatTest(S!0b101([0, 1, 2]), "S");                // Test for bug 7628
3258     formatTest(S!0b110([0, 1, 2]), "S");
3259     formatTest(S!0b111([0, 1, 2]), "S");
3260 
3261     class C(uint flags)
3262     {
3263         int[] arr;
3264       static if (flags & 1)
3265         alias arr this;
3266 
3267         this(int[] a) { arr = a; }
3268 
3269       static if (flags & 2)
3270       {
3271         @property bool empty() const { return arr.length == 0; }
3272         @property int front() const { return arr[0] * 2; }
3273         void popFront() { arr = arr[1..$]; }
3274       }
3275 
3276       static if (flags & 4)
3277         override string toString() const { return "C"; }
3278     }
3279     formatTest(new C!0b000([0, 1, 2]), (new C!0b000([])).toString());
3280     formatTest(new C!0b001([0, 1, 2]), "[0, 1, 2]");    // Test for bug 7628
3281     formatTest(new C!0b010([0, 1, 2]), "[0, 2, 4]");
3282     formatTest(new C!0b011([0, 1, 2]), "[0, 2, 4]");
3283     formatTest(new C!0b100([0, 1, 2]), "C");
3284     formatTest(new C!0b101([0, 1, 2]), "C");            // Test for bug 7628
3285     formatTest(new C!0b110([0, 1, 2]), "C");
3286     formatTest(new C!0b111([0, 1, 2]), "C");
3287 }
3288 
3289 @system unittest
3290 {
3291     // void[]
3292     void[] val0;
3293     formatTest( val0, "[]" );
3294 
3295     void[] val = cast(void[]) cast(ubyte[])[1, 2, 3];
3296     formatTest( val, "[1, 2, 3]" );
3297 
3298     void[0] sval0 = [];
3299     formatTest( sval0, "[]");
3300 
3301     void[3] sval = cast(void[3]) cast(ubyte[3])[1, 2, 3];
3302     formatTest( sval, "[1, 2, 3]" );
3303 }
3304 
3305 @safe unittest
3306 {
3307     // const(T[]) -> const(T)[]
3308     const short[] a = [1, 2, 3];
3309     formatTest( a, "[1, 2, 3]" );
3310 
3311     struct S { const(int[]) arr; alias arr this; }
3312     auto s = S([1,2,3]);
3313     formatTest( s, "[1, 2, 3]" );
3314 }
3315 
3316 // https://issues.dlang.org/show_bug.cgi?id=6640
3317 @safe unittest
3318 {
3319     struct Range
3320     {
3321       @safe:
3322         string value;
3323         @property bool empty() const { return !value.length; }
3324         @property dchar front() const { return value.front; }
3325         void popFront() { value.popFront(); }
3326 
3327         @property size_t length() const { return value.length; }
3328     }
3329     immutable table =
3330     [
3331         ["[%s]", "[string]"],
3332         ["[%10s]", "[    string]"],
3333         ["[%-10s]", "[string    ]"],
3334         ["[%(%02x %)]", "[73 74 72 69 6e 67]"],
3335         ["[%(%c %)]", "[s t r i n g]"],
3336     ];
3337     foreach (e; table)
3338     {
3339         formatTest(e[0], "string", e[1]);
3340         formatTest(e[0], Range("string"), e[1]);
3341     }
3342 }
3343 
3344 @system unittest
3345 {
3346     // string literal from valid UTF sequence is encoding free.
3347     static foreach (StrType; AliasSeq!(string, wstring, dstring))
3348     {
3349         // Valid and printable (ASCII)
3350         formatTest( [cast(StrType)"hello"],
3351                     `["hello"]` );
3352 
3353         // 1 character escape sequences (' is not escaped in strings)
3354         formatTest( [cast(StrType)"\"'\0\\\a\b\f\n\r\t\v"],
3355                     `["\"'\0\\\a\b\f\n\r\t\v"]` );
3356 
3357         // 1 character optional escape sequences
3358         formatTest( [cast(StrType)"\'\?"],
3359                     `["'?"]` );
3360 
3361         // Valid and non-printable code point (<= U+FF)
3362         formatTest( [cast(StrType)"\x10\x1F\x20test"],
3363                     `["\x10\x1F test"]` );
3364 
3365         // Valid and non-printable code point (<= U+FFFF)
3366         formatTest( [cast(StrType)"\u200B..\u200F"],
3367                     `["\u200B..\u200F"]` );
3368 
3369         // Valid and non-printable code point (<= U+10FFFF)
3370         formatTest( [cast(StrType)"\U000E0020..\U000E007F"],
3371                     `["\U000E0020..\U000E007F"]` );
3372     }
3373 
3374     // invalid UTF sequence needs hex-string literal postfix (c/w/d)
3375     {
3376         // U+FFFF with UTF-8 (Invalid code point for interchange)
3377         formatTest( [cast(string)[0xEF, 0xBF, 0xBF]],
3378                     `[x"EF BF BF"c]` );
3379 
3380         // U+FFFF with UTF-16 (Invalid code point for interchange)
3381         formatTest( [cast(wstring)[0xFFFF]],
3382                     `[x"FFFF"w]` );
3383 
3384         // U+FFFF with UTF-32 (Invalid code point for interchange)
3385         formatTest( [cast(dstring)[0xFFFF]],
3386                     `[x"FFFF"d]` );
3387     }
3388 }
3389 
3390 @safe unittest
3391 {
3392     // nested range formatting with array of string
3393     formatTest( "%({%(%02x %)}%| %)", ["test", "msg"],
3394                 `{74 65 73 74} {6d 73 67}` );
3395 }
3396 
3397 @safe unittest
3398 {
3399     // stop auto escaping inside range formatting
3400     auto arr = ["hello", "world"];
3401     formatTest( "%(%s, %)",  arr, `"hello", "world"` );
3402     formatTest( "%-(%s, %)", arr, `hello, world` );
3403 
3404     auto aa1 = [1:"hello", 2:"world"];
3405     formatTest( "%(%s:%s, %)",  aa1, [`1:"hello", 2:"world"`, `2:"world", 1:"hello"`] );
3406     formatTest( "%-(%s:%s, %)", aa1, [`1:hello, 2:world`, `2:world, 1:hello`] );
3407 
3408     auto aa2 = [1:["ab", "cd"], 2:["ef", "gh"]];
3409     formatTest( "%-(%s:%s, %)",        aa2, [`1:["ab", "cd"], 2:["ef", "gh"]`, `2:["ef", "gh"], 1:["ab", "cd"]`] );
3410     formatTest( "%-(%s:%(%s%), %)",    aa2, [`1:"ab""cd", 2:"ef""gh"`, `2:"ef""gh", 1:"ab""cd"`] );
3411     formatTest( "%-(%s:%-(%s%)%|, %)", aa2, [`1:abcd, 2:efgh`, `2:efgh, 1:abcd`] );
3412 }
3413 
3414 // input range formatting
3415 private void formatRange(Writer, T, Char)(ref Writer w, ref T val, scope const ref FormatSpec!Char f)
3416 if (isInputRange!T)
3417 {
3418     // in this mode, we just want to do a representative print to discover if the format spec is valid
3419     enum formatTestMode = is(Writer == NoOpSink);
3420 
3421     import std.conv : text;
3422 
3423     // Formatting character ranges like string
3424     if (f.spec == 's')
3425     {
3426         alias E = ElementType!T;
3427 
3428         static if (!is(E == enum) && is(CharTypeOf!E))
3429         {
3430             static if (is(StringTypeOf!T))
3431                 writeAligned(w, val[0 .. f.precision < $ ? f.precision : $], f);
3432             else
3433             {
3434                 if (!f.flDash)
3435                 {
3436                     static if (hasLength!T)
3437                     {
3438                         // right align
3439                         auto len = val.length;
3440                     }
3441                     else static if (isForwardRange!T && !isInfinite!T)
3442                     {
3443                         auto len = walkLength(val.save);
3444                     }
3445                     else
3446                     {
3447                         enforceFmt(f.width == 0, "Cannot right-align a range without length");
3448                         size_t len = 0;
3449                     }
3450                     if (f.precision != f.UNSPECIFIED && len > f.precision)
3451                         len = f.precision;
3452 
3453                     if (f.width > len)
3454                         foreach (i ; 0 .. f.width - len)
3455                             put(w, ' ');
3456                     if (f.precision == f.UNSPECIFIED)
3457                         put(w, val);
3458                     else
3459                     {
3460                         size_t printed = 0;
3461                         for (; !val.empty && printed < f.precision; val.popFront(), ++printed)
3462                             put(w, val.front);
3463                     }
3464                 }
3465                 else
3466                 {
3467                     size_t printed = void;
3468 
3469                     // left align
3470                     if (f.precision == f.UNSPECIFIED)
3471                     {
3472                         static if (hasLength!T)
3473                         {
3474                             printed = val.length;
3475                             put(w, val);
3476                         }
3477                         else
3478                         {
3479                             printed = 0;
3480                             for (; !val.empty; val.popFront(), ++printed)
3481                             {
3482                                 put(w, val.front);
3483                                 static if (formatTestMode) break; // one is enough to test
3484                             }
3485                         }
3486                     }
3487                     else
3488                     {
3489                         printed = 0;
3490                         for (; !val.empty && printed < f.precision; val.popFront(), ++printed)
3491                             put(w, val.front);
3492                     }
3493 
3494                     if (f.width > printed)
3495                         foreach (i ; 0 .. f.width - printed)
3496                             put(w, ' ');
3497                 }
3498             }
3499         }
3500         else
3501         {
3502             put(w, f.seqBefore);
3503             if (!val.empty)
3504             {
3505                 formatElement(w, val.front, f);
3506                 val.popFront();
3507                 for (size_t i; !val.empty; val.popFront(), ++i)
3508                 {
3509                     put(w, f.seqSeparator);
3510                     formatElement(w, val.front, f);
3511                     static if (formatTestMode) break; // one is enough to test
3512                 }
3513             }
3514             static if (!isInfinite!T) put(w, f.seqAfter);
3515         }
3516     }
3517     else if (f.spec == 'r')
3518     {
3519         static if (is(DynamicArrayTypeOf!T))
3520         {
3521             alias ARR = DynamicArrayTypeOf!T;
3522             scope a = cast(ARR) val;
3523             foreach (e ; a)
3524             {
3525                 formatValue(w, e, f);
3526                 static if (formatTestMode) break; // one is enough to test
3527             }
3528         }
3529         else
3530         {
3531             for (size_t i; !val.empty; val.popFront(), ++i)
3532             {
3533                 formatValue(w, val.front, f);
3534                 static if (formatTestMode) break; // one is enough to test
3535             }
3536         }
3537     }
3538     else if (f.spec == '(')
3539     {
3540         if (val.empty)
3541             return;
3542         // Nested specifier is to be used
3543         for (;;)
3544         {
3545             auto fmt = FormatSpec!Char(f.nested);
3546             w: while (true)
3547             {
3548                 immutable r = fmt.writeUpToNextSpec(w);
3549                 // There was no format specifier, so break
3550                 if (!r)
3551                     break;
3552                 if (f.flDash)
3553                     formatValue(w, val.front, fmt);
3554                 else
3555                     formatElement(w, val.front, fmt);
3556                 // Check if there will be a format specifier farther on in the
3557                 // string. If so, continue the loop, otherwise break. This
3558                 // prevents extra copies of the `sep` from showing up.
3559                 foreach (size_t i; 0 .. fmt.trailing.length)
3560                     if (fmt.trailing[i] == '%')
3561                         continue w;
3562                 break w;
3563             }
3564             static if (formatTestMode)
3565             {
3566                 break; // one is enough to test
3567             }
3568             else
3569             {
3570                 if (f.sep !is null)
3571                 {
3572                     put(w, fmt.trailing);
3573                     val.popFront();
3574                     if (val.empty)
3575                         break;
3576                     put(w, f.sep);
3577                 }
3578                 else
3579                 {
3580                     val.popFront();
3581                     if (val.empty)
3582                         break;
3583                     put(w, fmt.trailing);
3584                 }
3585             }
3586         }
3587     }
3588     else
3589         throw new FormatException(text("Incorrect format specifier for range: %", f.spec));
3590 }
3591 
3592 // https://issues.dlang.org/show_bug.cgi?id=18778
3593 @safe pure unittest
3594 {
3595     assert(format("%-(%1$s - %1$s, %)", ["A", "B", "C"]) == "A - A, B - B, C - C");
3596 }
3597 
3598 @safe pure unittest
3599 {
3600     assert(collectExceptionMsg(format("%d", "hi")).back == 'd');
3601 }
3602 
3603 // character formatting with ecaping
3604 private void formatChar(Writer)(ref Writer w, in dchar c, in char quote)
3605 {
3606     import std.uni : isGraphical;
3607 
3608     string fmt;
3609     if (isGraphical(c))
3610     {
3611         if (c == quote || c == '\\')
3612             put(w, '\\');
3613         put(w, c);
3614         return;
3615     }
3616     else if (c <= 0xFF)
3617     {
3618         if (c < 0x20)
3619         {
3620             foreach (i, k; "\n\r\t\a\b\f\v\0")
3621             {
3622                 if (c == k)
3623                 {
3624                     put(w, '\\');
3625                     put(w, "nrtabfv0"[i]);
3626                     return;
3627                 }
3628             }
3629         }
3630         fmt = "\\x%02X";
3631     }
3632     else if (c <= 0xFFFF)
3633         fmt = "\\u%04X";
3634     else
3635         fmt = "\\U%08X";
3636 
3637     formattedWrite(w, fmt, cast(uint) c);
3638 }
3639 
3640 // undocumented because of deprecation
3641 // string elements are formatted like UTF-8 string literals.
3642 void formatElement(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f)
3643 if (is(StringTypeOf!T) && !is(T == enum))
3644 {
3645     import std.array : appender;
3646     import std.utf : decode, UTFException;
3647 
3648     StringTypeOf!T str = val;   // https://issues.dlang.org/show_bug.cgi?id=8015
3649 
3650     if (f.spec == 's')
3651     {
3652         try
3653         {
3654             // ignore other specifications and quote
3655             for (size_t i = 0; i < str.length; )
3656             {
3657                 auto c = decode(str, i);
3658                 // \uFFFE and \uFFFF are considered valid by isValidDchar,
3659                 // so need checking for interchange.
3660                 if (c == 0xFFFE || c == 0xFFFF)
3661                     goto LinvalidSeq;
3662             }
3663             put(w, '\"');
3664             for (size_t i = 0; i < str.length; )
3665             {
3666                 auto c = decode(str, i);
3667                 formatChar(w, c, '"');
3668             }
3669             put(w, '\"');
3670             return;
3671         }
3672         catch (UTFException)
3673         {
3674         }
3675 
3676         // If val contains invalid UTF sequence, formatted like HexString literal
3677     LinvalidSeq:
3678         static if (is(typeof(str[0]) : const(char)))
3679         {
3680             enum postfix = 'c';
3681             alias IntArr = const(ubyte)[];
3682         }
3683         else static if (is(typeof(str[0]) : const(wchar)))
3684         {
3685             enum postfix = 'w';
3686             alias IntArr = const(ushort)[];
3687         }
3688         else static if (is(typeof(str[0]) : const(dchar)))
3689         {
3690             enum postfix = 'd';
3691             alias IntArr = const(uint)[];
3692         }
3693         formattedWrite(w, "x\"%(%02X %)\"%s", cast(IntArr) str, postfix);
3694     }
3695     else
3696         formatValue(w, str, f);
3697 }
3698 
3699 @safe pure unittest
3700 {
3701     import std.array : appender;
3702     auto w = appender!string();
3703     auto spec = singleSpec("%s");
3704     formatElement(w, "Hello World", spec);
3705 
3706     assert(w.data == "\"Hello World\"");
3707 }
3708 
3709 @safe unittest
3710 {
3711     // Test for bug 8015
3712     import std.typecons;
3713 
3714     struct MyStruct {
3715         string str;
3716         @property string toStr() {
3717             return str;
3718         }
3719         alias toStr this;
3720     }
3721 
3722     Tuple!(MyStruct) t;
3723 }
3724 
3725 // undocumented because of deprecation
3726 // Character elements are formatted like UTF-8 character literals.
3727 void formatElement(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f)
3728 if (is(CharTypeOf!T) && !is(T == enum))
3729 {
3730     if (f.spec == 's')
3731     {
3732         put(w, '\'');
3733         formatChar(w, val, '\'');
3734         put(w, '\'');
3735     }
3736     else
3737         formatValue(w, val, f);
3738 }
3739 
3740 ///
3741 @safe unittest
3742 {
3743     import std.array : appender;
3744     auto w = appender!string();
3745     auto spec = singleSpec("%s");
3746     formatElement(w, "H", spec);
3747 
3748     assert(w.data == "\"H\"", w.data);
3749 }
3750 
3751 // undocumented
3752 // Maybe T is noncopyable struct, so receive it by 'auto ref'.
3753 void formatElement(Writer, T, Char)(auto ref Writer w, auto ref T val, scope const ref FormatSpec!Char f)
3754 if (!is(StringTypeOf!T) && !is(CharTypeOf!T) || is(T == enum))
3755 {
3756     formatValue(w, val, f);
3757 }
3758 
3759 /*
3760    Associative arrays are formatted by using `':'` and $(D ", ") as
3761    separators, and enclosed by `'['` and `']'`.
3762  */
3763 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
3764 if (is(AssocArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
3765 {
3766     AssocArrayTypeOf!T val = obj;
3767     const spec = f.spec;
3768 
3769     enforceFmt(spec == 's' || spec == '(',
3770         "incompatible format character for associative array argument: %" ~ spec);
3771 
3772     enum const(Char)[] defSpec = "%s" ~ f.keySeparator ~ "%s" ~ f.seqSeparator;
3773     auto fmtSpec = spec == '(' ? f.nested : defSpec;
3774 
3775     size_t i = 0;
3776     immutable end = val.length;
3777 
3778     if (spec == 's')
3779         put(w, f.seqBefore);
3780     foreach (k, ref v; val)
3781     {
3782         auto fmt = FormatSpec!Char(fmtSpec);
3783         fmt.writeUpToNextSpec(w);
3784         if (f.flDash)
3785         {
3786             formatValue(w, k, fmt);
3787             fmt.writeUpToNextSpec(w);
3788             formatValue(w, v, fmt);
3789         }
3790         else
3791         {
3792             formatElement(w, k, fmt);
3793             fmt.writeUpToNextSpec(w);
3794             formatElement(w, v, fmt);
3795         }
3796         if (f.sep !is null)
3797         {
3798             fmt.writeUpToNextSpec(w);
3799             if (++i != end)
3800                 put(w, f.sep);
3801         }
3802         else
3803         {
3804             if (++i != end)
3805                 fmt.writeUpToNextSpec(w);
3806         }
3807     }
3808     if (spec == 's')
3809         put(w, f.seqAfter);
3810 }
3811 
3812 @safe unittest
3813 {
3814     assert(collectExceptionMsg!FormatException(format("%d", [0:1])).back == 'd');
3815 
3816     int[string] aa0;
3817     formatTest( aa0, `[]` );
3818 
3819     // elements escaping
3820     formatTest(  ["aaa":1, "bbb":2],
3821                [`["aaa":1, "bbb":2]`, `["bbb":2, "aaa":1]`] );
3822     formatTest(  ['c':"str"],
3823                 `['c':"str"]` );
3824     formatTest(  ['"':"\"", '\'':"'"],
3825                [`['"':"\"", '\'':"'"]`, `['\'':"'", '"':"\""]`] );
3826 
3827     // range formatting for AA
3828     auto aa3 = [1:"hello", 2:"world"];
3829     // escape
3830     formatTest( "{%(%s:%s $ %)}", aa3,
3831                [`{1:"hello" $ 2:"world"}`, `{2:"world" $ 1:"hello"}`]);
3832     // use range formatting for key and value, and use %|
3833     formatTest( "{%([%04d->%(%c.%)]%| $ %)}", aa3,
3834                [`{[0001->h.e.l.l.o] $ [0002->w.o.r.l.d]}`, `{[0002->w.o.r.l.d] $ [0001->h.e.l.l.o]}`] );
3835 
3836     // https://issues.dlang.org/show_bug.cgi?id=12135
3837     formatTest("%(%s:<%s>%|,%)", [1:2], "1:<2>");
3838     formatTest("%(%s:<%s>%|%)" , [1:2], "1:<2>");
3839 }
3840 
3841 @system unittest
3842 {
3843     class C1 { int[char] val; alias val this; this(int[char] v){ val = v; } }
3844     class C2 { int[char] val; alias val this; this(int[char] v){ val = v; }
3845                override string toString() const { return "C"; } }
3846     formatTest( new C1(['c':1, 'd':2]), [`['c':1, 'd':2]`, `['d':2, 'c':1]`] );
3847     formatTest( new C2(['c':1, 'd':2]), "C" );
3848 
3849     struct S1 { int[char] val; alias val this; }
3850     struct S2 { int[char] val; alias val this;
3851                 string toString() const { return "S"; } }
3852     formatTest( S1(['c':1, 'd':2]), [`['c':1, 'd':2]`, `['d':2, 'c':1]`] );
3853     formatTest( S2(['c':1, 'd':2]), "S" );
3854 }
3855 
3856 // https://issues.dlang.org/show_bug.cgi?id=8921
3857 @safe unittest
3858 {
3859     enum E : char { A = 'a', B = 'b', C = 'c' }
3860     E[3] e = [E.A, E.B, E.C];
3861     formatTest(e, "[A, B, C]");
3862 
3863     E[] e2 = [E.A, E.B, E.C];
3864     formatTest(e2, "[A, B, C]");
3865 }
3866 
3867 private enum HasToStringResult
3868 {
3869     none,
3870     hasSomeToString,
3871     constCharSink,
3872     constCharSinkFormatString,
3873     constCharSinkFormatSpec,
3874     customPutWriter,
3875     customPutWriterFormatSpec,
3876 }
3877 
3878 private template hasToString(T, Char)
3879 {
3880     static if (isPointer!T)
3881     {
3882         // X* does not have toString, even if X is aggregate type has toString.
3883         enum hasToString = HasToStringResult.none;
3884     }
3885     else static if (is(typeof(
3886         {T val = void;
3887         const FormatSpec!Char f;
3888         static struct S {void put(scope Char s){}}
3889         S s;
3890         val.toString(s, f);
3891         // force toString to take parameters by ref
3892         static assert(!__traits(compiles, val.toString(s, FormatSpec!Char())));
3893         static assert(!__traits(compiles, val.toString(S(), f)));}
3894     )))
3895     {
3896         enum hasToString = HasToStringResult.customPutWriterFormatSpec;
3897     }
3898     else static if (is(typeof(
3899         {T val = void;
3900         static struct S {void put(scope Char s){}}
3901         S s;
3902         val.toString(s);
3903         // force toString to take parameters by ref
3904         static assert(!__traits(compiles, val.toString(S())));}
3905     )))
3906     {
3907         enum hasToString = HasToStringResult.customPutWriter;
3908     }
3909     else static if (is(typeof({ T val = void; FormatSpec!Char f; val.toString((scope const(char)[] s){}, f); })))
3910     {
3911         enum hasToString = HasToStringResult.constCharSinkFormatSpec;
3912     }
3913     else static if (is(typeof({ T val = void; val.toString((scope const(char)[] s){}, "%s"); })))
3914     {
3915         enum hasToString = HasToStringResult.constCharSinkFormatString;
3916     }
3917     else static if (is(typeof({ T val = void; val.toString((scope const(char)[] s){}); })))
3918     {
3919         enum hasToString = HasToStringResult.constCharSink;
3920     }
3921     else static if (is(typeof({ T val = void; return val.toString(); }()) S) && isSomeString!S)
3922     {
3923         enum hasToString = HasToStringResult.hasSomeToString;
3924     }
3925     else
3926     {
3927         enum hasToString = HasToStringResult.none;
3928     }
3929 }
3930 
3931 @safe unittest
3932 {
3933     static struct A
3934     {
3935         void toString(Writer)(ref Writer w)
3936         if (isOutputRange!(Writer, string))
3937         {}
3938     }
3939     static struct B
3940     {
3941         void toString(scope void delegate(scope const(char)[]) sink, scope FormatSpec!char fmt) {}
3942     }
3943     static struct C
3944     {
3945         void toString(scope void delegate(scope const(char)[]) sink, string fmt) {}
3946     }
3947     static struct D
3948     {
3949         void toString(scope void delegate(scope const(char)[]) sink) {}
3950     }
3951     static struct E
3952     {
3953         string toString() {return "";}
3954     }
3955     static struct F
3956     {
3957         void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt)
3958         if (isOutputRange!(Writer, string))
3959         {}
3960     }
3961     static struct G
3962     {
3963         string toString() {return "";}
3964         void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, string)) {}
3965     }
3966     static struct H
3967     {
3968         string toString() {return "";}
3969         void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt)
3970         if (isOutputRange!(Writer, string))
3971         {}
3972     }
3973     static struct I
3974     {
3975         void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, string)) {}
3976         void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt)
3977         if (isOutputRange!(Writer, string))
3978         {}
3979     }
3980     static struct J
3981     {
3982         string toString() {return "";}
3983         void toString(Writer)(ref Writer w, scope ref FormatSpec!char fmt)
3984         if (isOutputRange!(Writer, string))
3985         {}
3986     }
3987     static struct K
3988     {
3989         void toString(Writer)(Writer w, scope const ref FormatSpec!char fmt)
3990         if (isOutputRange!(Writer, string))
3991         {}
3992     }
3993     static struct L
3994     {
3995         void toString(Writer)(ref Writer w, scope const FormatSpec!char fmt)
3996         if (isOutputRange!(Writer, string))
3997         {}
3998     }
3999 
4000     with(HasToStringResult)
4001     {
4002         static assert(hasToString!(A, char) == customPutWriter);
4003         static assert(hasToString!(B, char) == constCharSinkFormatSpec);
4004         static assert(hasToString!(C, char) == constCharSinkFormatString);
4005         static assert(hasToString!(D, char) == constCharSink);
4006         static assert(hasToString!(E, char) == hasSomeToString);
4007         static assert(hasToString!(F, char) == customPutWriterFormatSpec);
4008         static assert(hasToString!(G, char) == customPutWriter);
4009         static assert(hasToString!(H, char) == customPutWriterFormatSpec);
4010         static assert(hasToString!(I, char) == customPutWriterFormatSpec);
4011         static assert(hasToString!(J, char) == hasSomeToString);
4012         static assert(hasToString!(K, char) == constCharSinkFormatSpec);
4013         static assert(hasToString!(L, char) == none);
4014     }
4015 }
4016 
4017 // Like NullSink, but toString() isn't even called at all. Used to test the format string.
4018 private struct NoOpSink
4019 {
4020     void put(E)(scope const E) pure @safe @nogc nothrow {}
4021 }
4022 
4023 // object formatting with toString
4024 private void formatObject(Writer, T, Char)(ref Writer w, ref T val, scope const ref FormatSpec!Char f)
4025 if (hasToString!(T, Char))
4026 {
4027     enum overload = hasToString!(T, Char);
4028 
4029     enum noop = is(Writer == NoOpSink);
4030 
4031     static if (overload == HasToStringResult.customPutWriterFormatSpec)
4032     {
4033         static if (!noop) val.toString(w, f);
4034     }
4035     else static if (overload == HasToStringResult.customPutWriter)
4036     {
4037         static if (!noop) val.toString(w);
4038     }
4039     else static if (overload == HasToStringResult.constCharSinkFormatSpec)
4040     {
4041         static if (!noop) val.toString((scope const(char)[] s) { put(w, s); }, f);
4042     }
4043     else static if (overload == HasToStringResult.constCharSinkFormatString)
4044     {
4045         static if (!noop) val.toString((scope const(char)[] s) { put(w, s); }, f.getCurFmtStr());
4046     }
4047     else static if (overload == HasToStringResult.constCharSink)
4048     {
4049         static if (!noop) val.toString((scope const(char)[] s) { put(w, s); });
4050     }
4051     else static if (overload == HasToStringResult.hasSomeToString)
4052     {
4053         static if (!noop) put(w, val.toString());
4054     }
4055     else
4056     {
4057         static assert(0, "No way found to format " ~ T.stringof ~ " as string");
4058     }
4059 }
4060 
4061 void enforceValidFormatSpec(T, Char)(scope const ref FormatSpec!Char f)
4062 {
4063     enum overload = hasToString!(T, Char);
4064     static if (
4065             overload != HasToStringResult.constCharSinkFormatSpec &&
4066             overload != HasToStringResult.customPutWriterFormatSpec &&
4067             !isInputRange!T)
4068     {
4069         enforceFmt(f.spec == 's',
4070             "Expected '%s' format specifier for type '" ~ T.stringof ~ "'");
4071     }
4072 }
4073 
4074 @system unittest
4075 {
4076     static interface IF1 { }
4077     class CIF1 : IF1 { }
4078     static struct SF1 { }
4079     static union UF1 { }
4080     static class CF1 { }
4081 
4082     static interface IF2 { string toString(); }
4083     static class CIF2 : IF2 { override string toString() { return ""; } }
4084     static struct SF2 { string toString() { return ""; } }
4085     static union UF2 { string toString() { return ""; } }
4086     static class CF2 { override string toString() { return ""; } }
4087 
4088     static interface IK1 { void toString(scope void delegate(scope const(char)[]) sink,
4089                            FormatSpec!char) const; }
4090     static class CIK1 : IK1 { override void toString(scope void delegate(scope const(char)[]) sink,
4091                               FormatSpec!char) const { sink("CIK1"); } }
4092     static struct KS1 { void toString(scope void delegate(scope const(char)[]) sink,
4093                         FormatSpec!char) const { sink("KS1"); } }
4094 
4095     static union KU1 { void toString(scope void delegate(scope const(char)[]) sink,
4096                        FormatSpec!char) const { sink("KU1"); } }
4097 
4098     static class KC1 { void toString(scope void delegate(scope const(char)[]) sink,
4099                        FormatSpec!char) const { sink("KC1"); } }
4100 
4101     IF1 cif1 = new CIF1;
4102     assertThrown!FormatException(format("%f", cif1));
4103     assertThrown!FormatException(format("%f", SF1()));
4104     assertThrown!FormatException(format("%f", UF1()));
4105     assertThrown!FormatException(format("%f", new CF1()));
4106 
4107     IF2 cif2 = new CIF2;
4108     assertThrown!FormatException(format("%f", cif2));
4109     assertThrown!FormatException(format("%f", SF2()));
4110     assertThrown!FormatException(format("%f", UF2()));
4111     assertThrown!FormatException(format("%f", new CF2()));
4112 
4113     IK1 cik1 = new CIK1;
4114     assert(format("%f", cik1) == "CIK1");
4115     assert(format("%f", KS1()) == "KS1");
4116     assert(format("%f", KU1()) == "KU1");
4117     assert(format("%f", new KC1()) == "KC1");
4118 }
4119 
4120 /*
4121    Aggregates
4122  */
4123 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f)
4124 if (is(T == class) && !is(T == enum))
4125 {
4126     enforceValidFormatSpec!(T, Char)(f);
4127 
4128     // TODO: remove this check once `@disable override` deprecation cycle is finished
4129     static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString))
4130         static assert(!__traits(isDisabled, T.toString), T.stringof ~
4131             " cannot be formatted because its `toString` is marked with `@disable`");
4132 
4133     if (val is null)
4134         put(w, "null");
4135     else
4136     {
4137         import std.algorithm.comparison : among;
4138         enum overload = hasToString!(T, Char);
4139         with(HasToStringResult)
4140         static if ((is(T == immutable) || is(T == const) || is(T == shared)) && overload == none)
4141         {
4142             // Remove this when Object gets const toString
4143             // https://issues.dlang.org/show_bug.cgi?id=7879
4144             static if (is(T == immutable))
4145                 put(w, "immutable(");
4146             else static if (is(T == const))
4147                 put(w, "const(");
4148             else static if (is(T == shared))
4149                 put(w, "shared(");
4150 
4151             put(w, typeid(Unqual!T).name);
4152             put(w, ')');
4153         }
4154         else static if (overload.among(constCharSink, constCharSinkFormatString, constCharSinkFormatSpec) ||
4155                        (!isInputRange!T && !is(BuiltinTypeOf!T)))
4156         {
4157             formatObject!(Writer, T, Char)(w, val, f);
4158         }
4159         else
4160         {
4161           //string delegate() dg = &val.toString;
4162             Object o = val;     // workaround
4163             string delegate() dg = &o.toString;
4164             scope Object object = new Object();
4165             if (dg.funcptr != (&object.toString).funcptr) // toString is overridden
4166             {
4167                 formatObject(w, val, f);
4168             }
4169             else static if (isInputRange!T)
4170             {
4171                 formatRange(w, val, f);
4172             }
4173             else static if (is(BuiltinTypeOf!T X))
4174             {
4175                 X x = val;
4176                 formatValueImpl(w, x, f);
4177             }
4178             else
4179             {
4180                 formatObject(w, val, f);
4181             }
4182         }
4183     }
4184 }
4185 
4186 @system unittest
4187 {
4188     import std.array : appender;
4189     import std.range.interfaces;
4190     // class range (https://issues.dlang.org/show_bug.cgi?id=5154)
4191     auto c = inputRangeObject([1,2,3,4]);
4192     formatTest( c, "[1, 2, 3, 4]" );
4193     assert(c.empty);
4194     c = null;
4195     formatTest( c, "null" );
4196 }
4197 
4198 @system unittest
4199 {
4200     // https://issues.dlang.org/show_bug.cgi?id=5354
4201     // If the class has both range I/F and custom toString, the use of custom
4202     // toString routine is prioritized.
4203 
4204     // Enable the use of custom toString that gets a sink delegate
4205     // for class formatting.
4206 
4207     enum inputRangeCode =
4208     q{
4209         int[] arr;
4210         this(int[] a){ arr = a; }
4211         @property int front() const { return arr[0]; }
4212         @property bool empty() const { return arr.length == 0; }
4213         void popFront(){ arr = arr[1..$]; }
4214     };
4215 
4216     class C1
4217     {
4218         mixin(inputRangeCode);
4219         void toString(scope void delegate(scope const(char)[]) dg,
4220                       scope const ref FormatSpec!char f) const
4221         {
4222             dg("[012]");
4223         }
4224     }
4225     class C2
4226     {
4227         mixin(inputRangeCode);
4228         void toString(scope void delegate(const(char)[]) dg, string f) const { dg("[012]"); }
4229     }
4230     class C3
4231     {
4232         mixin(inputRangeCode);
4233         void toString(scope void delegate(const(char)[]) dg) const { dg("[012]"); }
4234     }
4235     class C4
4236     {
4237         mixin(inputRangeCode);
4238         override string toString() const { return "[012]"; }
4239     }
4240     class C5
4241     {
4242         mixin(inputRangeCode);
4243     }
4244 
4245     formatTest( new C1([0, 1, 2]), "[012]" );
4246     formatTest( new C2([0, 1, 2]), "[012]" );
4247     formatTest( new C3([0, 1, 2]), "[012]" );
4248     formatTest( new C4([0, 1, 2]), "[012]" );
4249     formatTest( new C5([0, 1, 2]), "[0, 1, 2]" );
4250 }
4251 
4252 // outside the unittest block, otherwise the FQN of the
4253 // class contains the line number of the unittest
4254 version (StdUnittest)
4255 {
4256     private class C {}
4257 }
4258 
4259 // https://issues.dlang.org/show_bug.cgi?id=7879
4260 @safe unittest
4261 {
4262     const(C) c;
4263     auto s = format("%s", c);
4264     assert(s == "null");
4265 
4266     immutable(C) c2 = new C();
4267     s = format("%s", c2);
4268     assert(s == "immutable(std.format.C)");
4269 
4270     const(C) c3 = new C();
4271     s = format("%s", c3);
4272     assert(s == "const(std.format.C)");
4273 
4274     shared(C) c4 = new C();
4275     s = format("%s", c4);
4276     assert(s == "shared(std.format.C)");
4277 }
4278 
4279 // https://issues.dlang.org/show_bug.cgi?id=19003
4280 @safe unittest
4281 {
4282     struct S
4283     {
4284         int i;
4285 
4286         @disable this();
4287 
4288         invariant { assert(this.i); }
4289 
4290         this(int i) @safe in { assert(i); } do { this.i = i; }
4291 
4292         string toString() { return "S"; }
4293     }
4294 
4295     S s = S(1);
4296 
4297     format!"%s"(s);
4298 }
4299 
4300 // https://issues.dlang.org/show_bug.cgi?id=20218
4301 @safe pure unittest
4302 {
4303     void notCalled()
4304     {
4305         import std.range : repeat;
4306 
4307         auto value = 1.repeat;
4308 
4309         // test that range is not evaluated to completion at compiletime
4310         format!"%s"(value);
4311     }
4312 }
4313 
4314 // https://issues.dlang.org/show_bug.cgi?id=7879
4315 @safe unittest
4316 {
4317     class F
4318     {
4319         override string toString() const @safe
4320         {
4321             return "Foo";
4322         }
4323     }
4324 
4325     const(F) c;
4326     auto s = format("%s", c);
4327     assert(s == "null");
4328 
4329     const(F) c2 = new F();
4330     s = format("%s", c2);
4331     assert(s == "Foo", s);
4332 }
4333 
4334 // ditto
4335 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f)
4336 if (is(T == interface) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is(T == enum))
4337 {
4338     enforceValidFormatSpec!(T, Char)(f);
4339     if (val is null)
4340         put(w, "null");
4341     else
4342     {
4343         static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString))
4344             static assert(!__traits(isDisabled, T.toString), T.stringof ~
4345                 " cannot be formatted because its `toString` is marked with `@disable`");
4346 
4347         static if (hasToString!(T, Char) != HasToStringResult.none)
4348         {
4349             formatObject(w, val, f);
4350         }
4351         else static if (isInputRange!T)
4352         {
4353             formatRange(w, val, f);
4354         }
4355         else
4356         {
4357             version (Windows)
4358             {
4359                 import core.sys.windows.com : IUnknown;
4360                 static if (is(T : IUnknown))
4361                 {
4362                     formatValueImpl(w, *cast(void**)&val, f);
4363                 }
4364                 else
4365                 {
4366                     formatValueImpl(w, cast(Object) val, f);
4367                 }
4368             }
4369             else
4370             {
4371                 formatValueImpl(w, cast(Object) val, f);
4372             }
4373         }
4374     }
4375 }
4376 
4377 @system unittest
4378 {
4379     // interface
4380     import std.range.interfaces;
4381     InputRange!int i = inputRangeObject([1,2,3,4]);
4382     formatTest( i, "[1, 2, 3, 4]" );
4383     assert(i.empty);
4384     i = null;
4385     formatTest( i, "null" );
4386 
4387     // interface (downcast to Object)
4388     interface Whatever {}
4389     class C : Whatever
4390     {
4391         override @property string toString() const { return "ab"; }
4392     }
4393     Whatever val = new C;
4394     formatTest( val, "ab" );
4395 
4396     // https://issues.dlang.org/show_bug.cgi?id=11175
4397     version (Windows)
4398     {
4399         import core.sys.windows.com : IUnknown, IID;
4400         import core.sys.windows.windef : HRESULT;
4401 
4402         interface IUnknown2 : IUnknown { }
4403 
4404         class D : IUnknown2
4405         {
4406             extern(Windows) HRESULT QueryInterface(const(IID)* riid, void** pvObject) { return typeof(return).init; }
4407             extern(Windows) uint AddRef() { return 0; }
4408             extern(Windows) uint Release() { return 0; }
4409         }
4410 
4411         IUnknown2 d = new D;
4412         string expected = format("%X", cast(void*) d);
4413         formatTest(d, expected);
4414     }
4415 }
4416 
4417 /// ditto
4418 // Maybe T is noncopyable struct, so receive it by 'auto ref'.
4419 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, auto ref T val, scope const ref FormatSpec!Char f)
4420 if ((is(T == struct) || is(T == union)) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is(T == enum))
4421 {
4422 
4423     static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString))
4424         static assert(!__traits(isDisabled, T.toString), T.stringof ~
4425             " cannot be formatted because its `toString` is marked with `@disable`");
4426 
4427     enforceValidFormatSpec!(T, Char)(f);
4428     static if (hasToString!(T, Char))
4429     {
4430         formatObject(w, val, f);
4431     }
4432     else static if (isInputRange!T)
4433     {
4434         formatRange(w, val, f);
4435     }
4436     else static if (is(T == struct))
4437     {
4438         enum left = T.stringof~"(";
4439         enum separator = ", ";
4440         enum right = ")";
4441 
4442         put(w, left);
4443         foreach (i, e; val.tupleof)
4444         {
4445             static if (__traits(identifier, val.tupleof[i]) == "this")
4446                 continue;
4447             else static if (0 < i && val.tupleof[i-1].offsetof == val.tupleof[i].offsetof)
4448             {
4449                 static if (i == val.tupleof.length - 1 || val.tupleof[i].offsetof != val.tupleof[i+1].offsetof)
4450                     put(w, separator~val.tupleof[i].stringof[4..$]~"}");
4451                 else
4452                     put(w, separator~val.tupleof[i].stringof[4..$]);
4453             }
4454             else static if (i+1 < val.tupleof.length && val.tupleof[i].offsetof == val.tupleof[i+1].offsetof)
4455                 put(w, (i > 0 ? separator : "")~"#{overlap "~val.tupleof[i].stringof[4..$]);
4456             else
4457             {
4458                 static if (i > 0)
4459                     put(w, separator);
4460                 formatElement(w, e, f);
4461             }
4462         }
4463         put(w, right);
4464     }
4465     else
4466     {
4467         put(w, T.stringof);
4468     }
4469 }
4470 
4471 // https://issues.dlang.org/show_bug.cgi?id=9588
4472 @safe pure unittest
4473 {
4474     struct S { int x; bool empty() { return false; } }
4475     formatTest( S(), "S(0)" );
4476 }
4477 
4478 // https://issues.dlang.org/show_bug.cgi?id=4638
4479 @safe unittest
4480 {
4481     struct U8  {  string toString() const { return "blah"; } }
4482     struct U16 { wstring toString() const { return "blah"; } }
4483     struct U32 { dstring toString() const { return "blah"; } }
4484     formatTest( U8(), "blah" );
4485     formatTest( U16(), "blah" );
4486     formatTest( U32(), "blah" );
4487 }
4488 
4489 // https://issues.dlang.org/show_bug.cgi?id=3890
4490 @safe unittest
4491 {
4492     struct Int{ int n; }
4493     struct Pair{ string s; Int i; }
4494     formatTest( Pair("hello", Int(5)),
4495                 `Pair("hello", Int(5))` );
4496 }
4497 
4498 @system unittest
4499 {
4500     // union formatting without toString
4501     union U1
4502     {
4503         int n;
4504         string s;
4505     }
4506     U1 u1;
4507     formatTest( u1, "U1" );
4508 
4509     // union formatting with toString
4510     union U2
4511     {
4512         int n;
4513         string s;
4514         string toString() const { return s; }
4515     }
4516     U2 u2;
4517     u2.s = "hello";
4518     formatTest( u2, "hello" );
4519 }
4520 
4521 @system unittest
4522 {
4523     import std.array;
4524     // https://issues.dlang.org/show_bug.cgi?id=7230
4525     static struct Bug7230
4526     {
4527         string s = "hello";
4528         union {
4529             string a;
4530             int b;
4531             double c;
4532         }
4533         long x = 10;
4534     }
4535 
4536     Bug7230 bug;
4537     bug.b = 123;
4538 
4539     FormatSpec!char f;
4540     auto w = appender!(char[])();
4541     formatValue(w, bug, f);
4542     assert(w.data == `Bug7230("hello", #{overlap a, b, c}, 10)`);
4543 }
4544 
4545 @safe unittest
4546 {
4547     import std.array : appender;
4548     static struct S{ @disable this(this); }
4549     S s;
4550 
4551     FormatSpec!char f;
4552     auto w = appender!string();
4553     formatValue(w, s, f);
4554     assert(w.data == "S()");
4555 }
4556 
4557 @safe unittest
4558 {
4559     //struct Foo { @disable string toString(); }
4560     //Foo foo;
4561 
4562     interface Bar { @disable string toString(); }
4563     Bar bar;
4564 
4565     import std.array : appender;
4566     auto w = appender!(char[])();
4567     FormatSpec!char f;
4568 
4569     // NOTE: structs cant be tested : the assertion is correct so compilation
4570     // continues and fails when trying to link the unimplemented toString.
4571     //static assert(!__traits(compiles, formatValue(w, foo, f)));
4572     static assert(!__traits(compiles, formatValue(w, bar, f)));
4573 }
4574 
4575 /*
4576     `enum`s are formatted like their base value
4577  */
4578 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f)
4579 if (is(T == enum))
4580 {
4581     if (f.spec == 's')
4582     {
4583         foreach (i, e; EnumMembers!T)
4584         {
4585             if (val == e)
4586             {
4587                 formatValueImpl(w, __traits(allMembers, T)[i], f);
4588                 return;
4589             }
4590         }
4591 
4592         import std.array : appender;
4593         auto w2 = appender!string();
4594 
4595         // val is not a member of T, output cast(T) rawValue instead.
4596         put(w2, "cast(" ~ T.stringof ~ ")");
4597         static assert(!is(OriginalType!T == T), "OriginalType!" ~ T.stringof ~
4598                 "must not be equal to " ~ T.stringof);
4599 
4600         FormatSpec!Char f2 = f;
4601         f2.width = 0;
4602         formatValueImpl(w2, cast(OriginalType!T) val, f2);
4603         writeAligned(w, w2.data, f);
4604         return;
4605     }
4606     formatValueImpl(w, cast(OriginalType!T) val, f);
4607 }
4608 
4609 @safe unittest
4610 {
4611     enum A { first, second, third }
4612     formatTest( A.second, "second" );
4613     formatTest( cast(A) 72, "cast(A)72" );
4614 }
4615 @safe unittest
4616 {
4617     enum A : string { one = "uno", two = "dos", three = "tres" }
4618     formatTest( A.three, "three" );
4619     formatTest( cast(A)"mill\&oacute;n", "cast(A)mill\&oacute;n" );
4620 }
4621 @safe unittest
4622 {
4623     enum A : bool { no, yes }
4624     formatTest( A.yes, "yes" );
4625     formatTest( A.no, "no" );
4626 }
4627 @safe unittest
4628 {
4629     // Test for bug 6892
4630     enum Foo { A = 10 }
4631     formatTest("%s",    Foo.A, "A");
4632     formatTest(">%4s<", Foo.A, ">   A<");
4633     formatTest("%04d",  Foo.A, "0010");
4634     formatTest("%+2u",  Foo.A, "+10");
4635     formatTest("%02x",  Foo.A, "0a");
4636     formatTest("%3o",   Foo.A, " 12");
4637     formatTest("%b",    Foo.A, "1010");
4638 }
4639 
4640 @safe pure unittest
4641 {
4642     enum A { one, two, three }
4643 
4644     string t1 = format("[%6s] [%-6s]", A.one, A.one);
4645     assert(t1 == "[   one] [one   ]");
4646     string t2 = format("[%10s] [%-10s]", cast(A) 10, cast(A) 10);
4647     assert(t2 == "[ cast(A)" ~ "10] [cast(A)" ~ "10 ]"); // due to bug in style checker
4648 }
4649 
4650 /*
4651    Pointers are formatted as hex integers.
4652  */
4653 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope T val, scope const ref FormatSpec!Char f)
4654 if (isPointer!T && !is(T == enum) && !hasToString!(T, Char))
4655 {
4656     static if (is(typeof({ shared const void* p = val; })))
4657         alias SharedOf(T) = shared(T);
4658     else
4659         alias SharedOf(T) = T;
4660 
4661     const SharedOf!(void*) p = val;
4662     const pnum = ()@trusted{ return cast(ulong) p; }();
4663 
4664     if (f.spec == 's')
4665     {
4666         if (p is null)
4667         {
4668             writeAligned(w, "null", f);
4669             return;
4670         }
4671         FormatSpec!Char fs = f; // fs is copy for change its values.
4672         fs.spec = 'X';
4673         formatValueImpl(w, pnum, fs);
4674     }
4675     else
4676     {
4677         enforceFmt(f.spec == 'X' || f.spec == 'x',
4678            "Expected one of %s, %x or %X for pointer type.");
4679         formatValueImpl(w, pnum, f);
4680     }
4681 }
4682 
4683 @safe pure unittest
4684 {
4685     int* p;
4686 
4687     string t1 = format("[%6s] [%-6s]", p, p);
4688     assert(t1 == "[  null] [null  ]");
4689 }
4690 
4691 /*
4692    SIMD vectors are formatted as arrays.
4693  */
4694 private void formatValueImpl(Writer, V, Char)(auto ref Writer w, V val, scope const ref FormatSpec!Char f)
4695 if (isSIMDVector!V)
4696 {
4697     formatValueImpl(w, val.array, f);
4698 }
4699 
4700 @safe unittest
4701 {
4702     import core.simd;
4703     static if (is(float4))
4704     {
4705         version (X86)
4706         {
4707             version (OSX) {/* https://issues.dlang.org/show_bug.cgi?id=17823 */}
4708         }
4709         else
4710         {
4711             float4 f;
4712             f.array[0] = 1;
4713             f.array[1] = 2;
4714             f.array[2] = 3;
4715             f.array[3] = 4;
4716             formatTest(f, "[1, 2, 3, 4]");
4717         }
4718     }
4719 }
4720 
4721 @safe pure unittest
4722 {
4723     int* p = null;
4724     formatTest( p, "null" );
4725 
4726     auto q = ()@trusted{ return cast(void*) 0xFFEECCAA; }();
4727     formatTest( q, "FFEECCAA" );
4728 }
4729 
4730 // https://issues.dlang.org/show_bug.cgi?id=11782
4731 @safe pure unittest
4732 {
4733     import std.range : iota;
4734 
4735     auto a = iota(0, 10);
4736     auto b = iota(0, 10);
4737     auto p = ()@trusted{ auto p = &a; return p; }();
4738 
4739     assert(format("%s",p) != format("%s",b));
4740 }
4741 
4742 @system pure unittest
4743 {
4744     // Test for https://issues.dlang.org/show_bug.cgi?id=7869
4745     struct S
4746     {
4747         string toString() const { return ""; }
4748     }
4749     S* p = null;
4750     formatTest( p, "null" );
4751 
4752     S* q = cast(S*) 0xFFEECCAA;
4753     formatTest( q, "FFEECCAA" );
4754 }
4755 
4756 // https://issues.dlang.org/show_bug.cgi?id=8186
4757 @system unittest
4758 {
4759     class B
4760     {
4761         int*a;
4762         this(){ a = new int; }
4763         alias a this;
4764     }
4765     formatTest( B.init, "null" );
4766 }
4767 
4768 // https://issues.dlang.org/show_bug.cgi?id=9336
4769 @system pure unittest
4770 {
4771     shared int i;
4772     format("%s", &i);
4773 }
4774 
4775 // https://issues.dlang.org/show_bug.cgi?id=11778
4776 @system pure unittest
4777 {
4778     int* p = null;
4779     assertThrown!FormatException(format("%d", p));
4780     assertThrown!FormatException(format("%04d", p + 2));
4781 }
4782 
4783 // https://issues.dlang.org/show_bug.cgi?id=12505
4784 @safe pure unittest
4785 {
4786     void* p = null;
4787     formatTest( "%08X", p, "00000000" );
4788 }
4789 
4790 /*
4791    Delegates are formatted by `ReturnType delegate(Parameters) FunctionAttributes`
4792  */
4793 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope T, scope const ref FormatSpec!Char f)
4794 if (isDelegate!T)
4795 {
4796     formatValueImpl(w, T.stringof, f);
4797 }
4798 
4799 @safe unittest
4800 {
4801     void func() @system { __gshared int x; ++x; throw new Exception("msg"); }
4802     version (linux) formatTest( &func, "void delegate() @system" );
4803 }
4804 
4805 @safe pure unittest
4806 {
4807     int[] a = [ 1, 3, 2 ];
4808     formatTest( "testing %(%s & %) embedded", a,
4809                 "testing 1 & 3 & 2 embedded");
4810     formatTest( "testing %((%s) %)) wyda3", a,
4811                 "testing (1) (3) (2) wyda3" );
4812 
4813     int[0] empt = [];
4814     formatTest( "(%s)", empt,
4815                 "([])" );
4816 }
4817 
4818 // Fix for https://issues.dlang.org/show_bug.cgi?id=1591
4819 private int getNthInt(string kind, A...)(uint index, A args)
4820 {
4821     return getNth!(kind, isIntegral,int)(index, args);
4822 }
4823 
4824 private T getNth(string kind, alias Condition, T, A...)(uint index, A args)
4825 {
4826     import std.conv : text, to;
4827 
4828     switch (index)
4829     {
4830         foreach (n, _; A)
4831         {
4832             case n:
4833                 static if (Condition!(typeof(args[n])))
4834                 {
4835                     return to!T(args[n]);
4836                 }
4837                 else
4838                 {
4839                     throw new FormatException(
4840                         text(kind, " expected, not ", typeof(args[n]).stringof,
4841                             " for argument #", index + 1));
4842                 }
4843         }
4844         default:
4845             throw new FormatException(
4846                 text("Missing ", kind, " argument"));
4847     }
4848 }
4849 
4850 @safe unittest
4851 {
4852     // width/precision
4853     assert(collectExceptionMsg!FormatException(format("%*.d", 5.1, 2))
4854         == "integer width expected, not double for argument #1");
4855     assert(collectExceptionMsg!FormatException(format("%-1*.d", 5.1, 2))
4856         == "integer width expected, not double for argument #1");
4857 
4858     assert(collectExceptionMsg!FormatException(format("%.*d", '5', 2))
4859         == "integer precision expected, not char for argument #1");
4860     assert(collectExceptionMsg!FormatException(format("%-1.*d", 4.7, 3))
4861         == "integer precision expected, not double for argument #1");
4862     assert(collectExceptionMsg!FormatException(format("%.*d", 5))
4863         == "Orphan format specifier: %d");
4864     assert(collectExceptionMsg!FormatException(format("%*.*d", 5))
4865         == "Missing integer precision argument");
4866 
4867     // separatorCharPos
4868     assert(collectExceptionMsg!FormatException(format("%,?d", 5))
4869         == "separator character expected, not int for argument #1");
4870     assert(collectExceptionMsg!FormatException(format("%,?d", '?'))
4871         == "Orphan format specifier: %d");
4872     assert(collectExceptionMsg!FormatException(format("%.*,*?d", 5))
4873         == "Missing separator digit width argument");
4874 }
4875 
4876 /* ======================== Unit Tests ====================================== */
4877 
4878 version (StdUnittest)
4879 private void formatTest(T)(T val, string expected, size_t ln = __LINE__, string fn = __FILE__)
4880 {
4881     import core.exception : AssertError;
4882     import std.array : appender;
4883     import std.conv : text;
4884     FormatSpec!char f;
4885     auto w = appender!string();
4886     formatValue(w, val, f);
4887     enforce!AssertError(
4888             w.data == expected,
4889             text("expected = `", expected, "`, result = `", w.data, "`"), fn, ln);
4890 }
4891 
4892 version (StdUnittest)
4893 private void formatTest(T)(string fmt, T val, string expected, size_t ln = __LINE__, string fn = __FILE__) @safe
4894 {
4895     import core.exception : AssertError;
4896     import std.array : appender;
4897     import std.conv : text;
4898     auto w = appender!string();
4899     formattedWrite(w, fmt, val);
4900     enforce!AssertError(
4901             w.data == expected,
4902             text("expected = `", expected, "`, result = `", w.data, "`"), fn, ln);
4903 }
4904 
4905 version (StdUnittest)
4906 private void formatTest(T)(T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__)
4907 {
4908     import core.exception : AssertError;
4909     import std.array : appender;
4910     import std.conv : text;
4911     FormatSpec!char f;
4912     auto w = appender!string();
4913     formatValue(w, val, f);
4914     foreach (cur; expected)
4915     {
4916         if (w.data == cur) return;
4917     }
4918     enforce!AssertError(
4919             false,
4920             text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln);
4921 }
4922 
4923 version (StdUnittest)
4924 private void formatTest(T)(string fmt, T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__) @safe
4925 {
4926     import core.exception : AssertError;
4927     import std.array : appender;
4928     import std.conv : text;
4929     auto w = appender!string();
4930     formattedWrite(w, fmt, val);
4931     foreach (cur; expected)
4932     {
4933         if (w.data == cur) return;
4934     }
4935     enforce!AssertError(
4936             false,
4937             text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln);
4938 }
4939 
4940 @safe /*pure*/ unittest     // formatting floating point values is now impure
4941 {
4942     import std.array;
4943 
4944     auto stream = appender!string();
4945     formattedWrite(stream, "%s", 1.1);
4946     assert(stream.data == "1.1", stream.data);
4947 }
4948 
4949 @safe pure unittest
4950 {
4951     import std.algorithm;
4952     import std.array;
4953 
4954     auto stream = appender!string();
4955     formattedWrite(stream, "%s", map!"a*a"([2, 3, 5]));
4956     assert(stream.data == "[4, 9, 25]", stream.data);
4957 
4958     // Test shared data.
4959     stream = appender!string();
4960     shared int s = 6;
4961     formattedWrite(stream, "%s", s);
4962     assert(stream.data == "6");
4963 }
4964 
4965 @safe pure unittest
4966 {
4967     import std.array;
4968     auto stream = appender!string();
4969     formattedWrite(stream, "%u", 42);
4970     assert(stream.data == "42", stream.data);
4971 }
4972 
4973 @safe pure unittest
4974 {
4975     // testing raw writes
4976     import std.array;
4977     auto w = appender!(char[])();
4978     uint a = 0x02030405;
4979     formattedWrite(w, "%+r", a);
4980     assert(w.data.length == 4 && w.data[0] == 2 && w.data[1] == 3
4981         && w.data[2] == 4 && w.data[3] == 5);
4982     w.clear();
4983     formattedWrite(w, "%-r", a);
4984     assert(w.data.length == 4 && w.data[0] == 5 && w.data[1] == 4
4985         && w.data[2] == 3 && w.data[3] == 2);
4986 }
4987 
4988 @safe pure unittest
4989 {
4990     // testing positional parameters
4991     import std.array;
4992     auto w = appender!(char[])();
4993     formattedWrite(w,
4994             "Numbers %2$s and %1$s are reversed and %1$s%2$s repeated",
4995             42, 0);
4996     assert(w.data == "Numbers 0 and 42 are reversed and 420 repeated",
4997             w.data);
4998     assert(collectExceptionMsg!FormatException(formattedWrite(w, "%1$s, %3$s", 1, 2))
4999         == "Positional specifier %3$s index exceeds 2");
5000 
5001     w.clear();
5002     formattedWrite(w, "asd%s", 23);
5003     assert(w.data == "asd23", w.data);
5004     w.clear();
5005     formattedWrite(w, "%s%s", 23, 45);
5006     assert(w.data == "2345", w.data);
5007 }
5008 
5009 @safe unittest
5010 {
5011     import core.stdc..string : strlen;
5012     import std.array : appender;
5013     import std.conv : text, octal;
5014     import core.stdc.stdio : snprintf;
5015 
5016     debug(format) printf("std.format.format.unittest\n");
5017 
5018     auto stream = appender!(char[])();
5019     //goto here;
5020 
5021     formattedWrite(stream,
5022             "hello world! %s %s ", true, 57, 1_000_000_000, 'x', " foo");
5023     assert(stream.data == "hello world! true 57 ",
5024         stream.data);
5025 
5026     stream.clear();
5027     formattedWrite(stream, "%g %A %s", 1.67, -1.28, float.nan);
5028     assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan",
5029            stream.data);
5030     stream.clear();
5031 
5032     formattedWrite(stream, "%x %X", 0x1234AF, 0xAFAFAFAF);
5033     assert(stream.data == "1234af AFAFAFAF");
5034     stream.clear();
5035 
5036     formattedWrite(stream, "%b %o", 0x1234AF, 0xAFAFAFAF);
5037     assert(stream.data == "100100011010010101111 25753727657");
5038     stream.clear();
5039 
5040     formattedWrite(stream, "%d %s", 0x1234AF, 0xAFAFAFAF);
5041     assert(stream.data == "1193135 2947526575");
5042     stream.clear();
5043 
5044     // formattedWrite(stream, "%s", 1.2 + 3.4i);
5045     // assert(stream.data == "1.2+3.4i");
5046     // stream.clear();
5047 
5048     formattedWrite(stream, "%a %A", 1.32, 6.78f);
5049     //formattedWrite(stream, "%x %X", 1.32);
5050     assert(stream.data == "0x1.51eb851eb851fp+0 0X1.B1EB86P+2");
5051     stream.clear();
5052 
5053     formattedWrite(stream, "%#06.*f",2,12.345);
5054     assert(stream.data == "012.35");
5055     stream.clear();
5056 
5057     formattedWrite(stream, "%#0*.*f",6,2,12.345);
5058     assert(stream.data == "012.35");
5059     stream.clear();
5060 
5061     const real constreal = 1;
5062     formattedWrite(stream, "%g",constreal);
5063     assert(stream.data == "1");
5064     stream.clear();
5065 
5066     formattedWrite(stream, "%7.4g:", 12.678);
5067     assert(stream.data == "  12.68:");
5068     stream.clear();
5069 
5070     formattedWrite(stream, "%7.4g:", 12.678L);
5071     assert(stream.data == "  12.68:");
5072     stream.clear();
5073 
5074     formattedWrite(stream, "%04f|%05d|%#05x|%#5x",-4.0,-10,1,1);
5075     assert(stream.data == "-4.000000|-0010|0x001|  0x1",
5076             stream.data);
5077     stream.clear();
5078 
5079     int i;
5080     string s;
5081 
5082     i = -10;
5083     formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
5084     assert(stream.data == "-10|-10|-10|-10|-10.0000");
5085     stream.clear();
5086 
5087     i = -5;
5088     formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
5089     assert(stream.data == "-5| -5|-05|-5|-5.0000");
5090     stream.clear();
5091 
5092     i = 0;
5093     formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
5094     assert(stream.data == "0|  0|000|0|0.0000");
5095     stream.clear();
5096 
5097     i = 5;
5098     formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
5099     assert(stream.data == "5|  5|005|5|5.0000");
5100     stream.clear();
5101 
5102     i = 10;
5103     formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
5104     assert(stream.data == "10| 10|010|10|10.0000");
5105     stream.clear();
5106 
5107     formattedWrite(stream, "%.0d", 0);
5108     assert(stream.data == "");
5109     stream.clear();
5110 
5111     formattedWrite(stream, "%.g", .34);
5112     assert(stream.data == "0.3");
5113     stream.clear();
5114 
5115     stream.clear(); formattedWrite(stream, "%.0g", .34);
5116     assert(stream.data == "0.3");
5117 
5118     stream.clear(); formattedWrite(stream, "%.2g", .34);
5119     assert(stream.data == "0.34");
5120 
5121     stream.clear(); formattedWrite(stream, "%0.0008f", 1e-08);
5122     assert(stream.data == "0.00000001");
5123 
5124     stream.clear(); formattedWrite(stream, "%0.0008f", 1e-05);
5125     assert(stream.data == "0.00001000");
5126 
5127     //return;
5128     //core.stdc.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr);
5129 
5130     s = "helloworld";
5131     string r;
5132     stream.clear(); formattedWrite(stream, "%.2s", s[0 .. 5]);
5133     assert(stream.data == "he");
5134     stream.clear(); formattedWrite(stream, "%.20s", s[0 .. 5]);
5135     assert(stream.data == "hello");
5136     stream.clear(); formattedWrite(stream, "%8s", s[0 .. 5]);
5137     assert(stream.data == "   hello");
5138 
5139     byte[] arrbyte = new byte[4];
5140     arrbyte[0] = 100;
5141     arrbyte[1] = -99;
5142     arrbyte[3] = 0;
5143     stream.clear(); formattedWrite(stream, "%s", arrbyte);
5144     assert(stream.data == "[100, -99, 0, 0]", stream.data);
5145 
5146     ubyte[] arrubyte = new ubyte[4];
5147     arrubyte[0] = 100;
5148     arrubyte[1] = 200;
5149     arrubyte[3] = 0;
5150     stream.clear(); formattedWrite(stream, "%s", arrubyte);
5151     assert(stream.data == "[100, 200, 0, 0]", stream.data);
5152 
5153     short[] arrshort = new short[4];
5154     arrshort[0] = 100;
5155     arrshort[1] = -999;
5156     arrshort[3] = 0;
5157     stream.clear(); formattedWrite(stream, "%s", arrshort);
5158     assert(stream.data == "[100, -999, 0, 0]");
5159     stream.clear(); formattedWrite(stream, "%s",arrshort);
5160     assert(stream.data == "[100, -999, 0, 0]");
5161 
5162     ushort[] arrushort = new ushort[4];
5163     arrushort[0] = 100;
5164     arrushort[1] = 20_000;
5165     arrushort[3] = 0;
5166     stream.clear(); formattedWrite(stream, "%s", arrushort);
5167     assert(stream.data == "[100, 20000, 0, 0]");
5168 
5169     int[] arrint = new int[4];
5170     arrint[0] = 100;
5171     arrint[1] = -999;
5172     arrint[3] = 0;
5173     stream.clear(); formattedWrite(stream, "%s", arrint);
5174     assert(stream.data == "[100, -999, 0, 0]");
5175     stream.clear(); formattedWrite(stream, "%s",arrint);
5176     assert(stream.data == "[100, -999, 0, 0]");
5177 
5178     long[] arrlong = new long[4];
5179     arrlong[0] = 100;
5180     arrlong[1] = -999;
5181     arrlong[3] = 0;
5182     stream.clear(); formattedWrite(stream, "%s", arrlong);
5183     assert(stream.data == "[100, -999, 0, 0]");
5184     stream.clear(); formattedWrite(stream, "%s",arrlong);
5185     assert(stream.data == "[100, -999, 0, 0]");
5186 
5187     ulong[] arrulong = new ulong[4];
5188     arrulong[0] = 100;
5189     arrulong[1] = 999;
5190     arrulong[3] = 0;
5191     stream.clear(); formattedWrite(stream, "%s", arrulong);
5192     assert(stream.data == "[100, 999, 0, 0]");
5193 
5194     string[] arr2 = new string[4];
5195     arr2[0] = "hello";
5196     arr2[1] = "world";
5197     arr2[3] = "foo";
5198     stream.clear(); formattedWrite(stream, "%s", arr2);
5199     assert(stream.data == `["hello", "world", "", "foo"]`, stream.data);
5200 
5201     stream.clear(); formattedWrite(stream, "%.8d", 7);
5202     assert(stream.data == "00000007");
5203 
5204     stream.clear(); formattedWrite(stream, "%.8x", 10);
5205     assert(stream.data == "0000000a");
5206 
5207     stream.clear(); formattedWrite(stream, "%-3d", 7);
5208     assert(stream.data == "7  ");
5209 
5210     stream.clear(); formattedWrite(stream, "%*d", -3, 7);
5211     assert(stream.data == "7  ");
5212 
5213     stream.clear(); formattedWrite(stream, "%.*d", -3, 7);
5214     //writeln(stream.data);
5215     assert(stream.data == "7");
5216 
5217     stream.clear(); formattedWrite(stream, "%s", "abc"c);
5218     assert(stream.data == "abc");
5219     stream.clear(); formattedWrite(stream, "%s", "def"w);
5220     assert(stream.data == "def", text(stream.data.length));
5221     stream.clear(); formattedWrite(stream, "%s", "ghi"d);
5222     assert(stream.data == "ghi");
5223 
5224 here:
5225     @trusted void* deadBeef() { return cast(void*) 0xDEADBEEF; }
5226     stream.clear(); formattedWrite(stream, "%s", deadBeef());
5227     assert(stream.data == "DEADBEEF", stream.data);
5228 
5229     stream.clear(); formattedWrite(stream, "%#x", 0xabcd);
5230     assert(stream.data == "0xabcd");
5231     stream.clear(); formattedWrite(stream, "%#X", 0xABCD);
5232     assert(stream.data == "0XABCD");
5233 
5234     stream.clear(); formattedWrite(stream, "%#o", octal!12345);
5235     assert(stream.data == "012345");
5236     stream.clear(); formattedWrite(stream, "%o", 9);
5237     assert(stream.data == "11");
5238 
5239     stream.clear(); formattedWrite(stream, "%+d", 123);
5240     assert(stream.data == "+123");
5241     stream.clear(); formattedWrite(stream, "%+d", -123);
5242     assert(stream.data == "-123");
5243     stream.clear(); formattedWrite(stream, "% d", 123);
5244     assert(stream.data == " 123");
5245     stream.clear(); formattedWrite(stream, "% d", -123);
5246     assert(stream.data == "-123");
5247 
5248     stream.clear(); formattedWrite(stream, "%%");
5249     assert(stream.data == "%");
5250 
5251     stream.clear(); formattedWrite(stream, "%d", true);
5252     assert(stream.data == "1");
5253     stream.clear(); formattedWrite(stream, "%d", false);
5254     assert(stream.data == "0");
5255 
5256     stream.clear(); formattedWrite(stream, "%d", 'a');
5257     assert(stream.data == "97", stream.data);
5258     wchar wc = 'a';
5259     stream.clear(); formattedWrite(stream, "%d", wc);
5260     assert(stream.data == "97");
5261     dchar dc = 'a';
5262     stream.clear(); formattedWrite(stream, "%d", dc);
5263     assert(stream.data == "97");
5264 
5265     byte b = byte.max;
5266     stream.clear(); formattedWrite(stream, "%x", b);
5267     assert(stream.data == "7f");
5268     stream.clear(); formattedWrite(stream, "%x", ++b);
5269     assert(stream.data == "80");
5270     stream.clear(); formattedWrite(stream, "%x", ++b);
5271     assert(stream.data == "81");
5272 
5273     short sh = short.max;
5274     stream.clear(); formattedWrite(stream, "%x", sh);
5275     assert(stream.data == "7fff");
5276     stream.clear(); formattedWrite(stream, "%x", ++sh);
5277     assert(stream.data == "8000");
5278     stream.clear(); formattedWrite(stream, "%x", ++sh);
5279     assert(stream.data == "8001");
5280 
5281     i = int.max;
5282     stream.clear(); formattedWrite(stream, "%x", i);
5283     assert(stream.data == "7fffffff");
5284     stream.clear(); formattedWrite(stream, "%x", ++i);
5285     assert(stream.data == "80000000");
5286     stream.clear(); formattedWrite(stream, "%x", ++i);
5287     assert(stream.data == "80000001");
5288 
5289     stream.clear(); formattedWrite(stream, "%x", 10);
5290     assert(stream.data == "a");
5291     stream.clear(); formattedWrite(stream, "%X", 10);
5292     assert(stream.data == "A");
5293     stream.clear(); formattedWrite(stream, "%x", 15);
5294     assert(stream.data == "f");
5295     stream.clear(); formattedWrite(stream, "%X", 15);
5296     assert(stream.data == "F");
5297 
5298     @trusted void ObjectTest()
5299     {
5300         Object c = null;
5301         stream.clear(); formattedWrite(stream, "%s", c);
5302         assert(stream.data == "null");
5303     }
5304     ObjectTest();
5305 
5306     enum TestEnum
5307     {
5308         Value1, Value2
5309     }
5310     stream.clear(); formattedWrite(stream, "%s", TestEnum.Value2);
5311     assert(stream.data == "Value2", stream.data);
5312     stream.clear(); formattedWrite(stream, "%s", cast(TestEnum) 5);
5313     assert(stream.data == "cast(TestEnum)5", stream.data);
5314 
5315     //immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]);
5316     //stream.clear(); formattedWrite(stream, "%s", aa.values);
5317     //core.stdc.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr);
5318     //assert(stream.data == "[[h,e,l,l,o],[b,e,t,t,y]]");
5319     //stream.clear(); formattedWrite(stream, "%s", aa);
5320     //assert(stream.data == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]");
5321 
5322     static const dchar[] ds = ['a','b'];
5323     for (int j = 0; j < ds.length; ++j)
5324     {
5325         stream.clear(); formattedWrite(stream, " %d", ds[j]);
5326         if (j == 0)
5327             assert(stream.data == " 97");
5328         else
5329             assert(stream.data == " 98");
5330     }
5331 
5332     stream.clear(); formattedWrite(stream, "%.-3d", 7);
5333     assert(stream.data == "7", ">" ~ stream.data ~ "<");
5334 }
5335 
5336 @safe unittest
5337 {
5338     import std.array;
5339     import std.stdio;
5340 
5341     immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]);
5342     assert(aa[3] == "hello");
5343     assert(aa[4] == "betty");
5344 
5345     auto stream = appender!(char[])();
5346     alias AllNumerics =
5347         AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong,
5348                   float, double, real);
5349     foreach (T; AllNumerics)
5350     {
5351         T value = 1;
5352         stream.clear();
5353         formattedWrite(stream, "%s", value);
5354         assert(stream.data == "1");
5355     }
5356 
5357     stream.clear();
5358     formattedWrite(stream, "%s", aa);
5359 }
5360 
5361 @system unittest
5362 {
5363     string s = "hello!124:34.5";
5364     string a;
5365     int b;
5366     double c;
5367     formattedRead(s, "%s!%s:%s", &a, &b, &c);
5368     assert(a == "hello" && b == 124 && c == 34.5);
5369 }
5370 
5371 version (StdUnittest)
5372 private void formatReflectTest(T)(ref T val, string fmt, string formatted, string fn = __FILE__, size_t ln = __LINE__)
5373 {
5374     import core.exception : AssertError;
5375     import std.array : appender;
5376     auto w = appender!string();
5377     formattedWrite(w, fmt, val);
5378 
5379     auto input = w.data;
5380     enforce!AssertError(
5381             input == formatted,
5382             input, fn, ln);
5383 
5384     T val2;
5385     formattedRead(input, fmt, &val2);
5386     static if (isAssociativeArray!T)
5387     if (__ctfe)
5388     {
5389         alias aa1 = val;
5390         alias aa2 = val2;
5391         assert(aa1 == aa2);
5392 
5393         assert(aa1.length == aa2.length);
5394 
5395         assert(aa1.keys == aa2.keys);
5396 
5397         assert(aa1.values == aa2.values);
5398         assert(aa1.values.length == aa2.values.length);
5399         foreach (i; 0 .. aa1.values.length)
5400             assert(aa1.values[i] == aa2.values[i]);
5401 
5402         foreach (i, key; aa1.keys)
5403             assert(aa1.values[i] == aa1[key]);
5404         foreach (i, key; aa2.keys)
5405             assert(aa2.values[i] == aa2[key]);
5406         return;
5407     }
5408     enforce!AssertError(
5409             val == val2,
5410             input, fn, ln);
5411 }
5412 
5413 version (StdUnittest)
5414 private void formatReflectTest(T)(ref T val, string fmt, string[] formatted, string fn = __FILE__, size_t ln = __LINE__)
5415 {
5416     import core.exception : AssertError;
5417     import std.array : appender;
5418     auto w = appender!string();
5419     formattedWrite(w, fmt, val);
5420 
5421     auto input = w.data;
5422 
5423     foreach (cur; formatted)
5424     {
5425         if (input == cur) return;
5426     }
5427     enforce!AssertError(
5428             false,
5429             input,
5430             fn,
5431             ln);
5432 
5433     T val2;
5434     formattedRead(input, fmt, &val2);
5435     static if (isAssociativeArray!T)
5436     if (__ctfe)
5437     {
5438         alias aa1 = val;
5439         alias aa2 = val2;
5440         assert(aa1 == aa2);
5441 
5442         assert(aa1.length == aa2.length);
5443 
5444         assert(aa1.keys == aa2.keys);
5445 
5446         assert(aa1.values == aa2.values);
5447         assert(aa1.values.length == aa2.values.length);
5448         foreach (i; 0 .. aa1.values.length)
5449             assert(aa1.values[i] == aa2.values[i]);
5450 
5451         foreach (i, key; aa1.keys)
5452             assert(aa1.values[i] == aa1[key]);
5453         foreach (i, key; aa2.keys)
5454             assert(aa2.values[i] == aa2[key]);
5455         return;
5456     }
5457     enforce!AssertError(
5458             val == val2,
5459             input, fn, ln);
5460 }
5461 
5462 @system unittest
5463 {
5464     void booleanTest()
5465     {
5466         auto b = true;
5467         formatReflectTest(b, "%s",  `true`);
5468         formatReflectTest(b, "%b",  `1`);
5469         formatReflectTest(b, "%o",  `1`);
5470         formatReflectTest(b, "%d",  `1`);
5471         formatReflectTest(b, "%u",  `1`);
5472         formatReflectTest(b, "%x",  `1`);
5473     }
5474 
5475     void integerTest()
5476     {
5477         auto n = 127;
5478         formatReflectTest(n, "%s",  `127`);
5479         formatReflectTest(n, "%b",  `1111111`);
5480         formatReflectTest(n, "%o",  `177`);
5481         formatReflectTest(n, "%d",  `127`);
5482         formatReflectTest(n, "%u",  `127`);
5483         formatReflectTest(n, "%x",  `7f`);
5484     }
5485 
5486     void floatingTest()
5487     {
5488         auto f = 3.14;
5489         formatReflectTest(f, "%s",  `3.14`);
5490         version (MinGW)
5491             formatReflectTest(f, "%e",  `3.140000e+000`);
5492         else
5493             formatReflectTest(f, "%e",  `3.140000e+00`);
5494         formatReflectTest(f, "%f",  `3.140000`);
5495         formatReflectTest(f, "%g",  `3.14`);
5496     }
5497 
5498     void charTest()
5499     {
5500         auto c = 'a';
5501         formatReflectTest(c, "%s",  `a`);
5502         formatReflectTest(c, "%c",  `a`);
5503         formatReflectTest(c, "%b",  `1100001`);
5504         formatReflectTest(c, "%o",  `141`);
5505         formatReflectTest(c, "%d",  `97`);
5506         formatReflectTest(c, "%u",  `97`);
5507         formatReflectTest(c, "%x",  `61`);
5508     }
5509 
5510     void strTest()
5511     {
5512         auto s = "hello";
5513         formatReflectTest(s, "%s",                      `hello`);
5514         formatReflectTest(s, "%(%c,%)",                 `h,e,l,l,o`);
5515         formatReflectTest(s, "%(%s,%)",                 `'h','e','l','l','o'`);
5516         formatReflectTest(s, "[%(<%c>%| $ %)]",         `[<h> $ <e> $ <l> $ <l> $ <o>]`);
5517     }
5518 
5519     void daTest()
5520     {
5521         auto a = [1,2,3,4];
5522         formatReflectTest(a, "%s",                      `[1, 2, 3, 4]`);
5523         formatReflectTest(a, "[%(%s; %)]",              `[1; 2; 3; 4]`);
5524         formatReflectTest(a, "[%(<%s>%| $ %)]",         `[<1> $ <2> $ <3> $ <4>]`);
5525     }
5526 
5527     void saTest()
5528     {
5529         int[4] sa = [1,2,3,4];
5530         formatReflectTest(sa, "%s",                     `[1, 2, 3, 4]`);
5531         formatReflectTest(sa, "[%(%s; %)]",             `[1; 2; 3; 4]`);
5532         formatReflectTest(sa, "[%(<%s>%| $ %)]",        `[<1> $ <2> $ <3> $ <4>]`);
5533     }
5534 
5535     void aaTest()
5536     {
5537         auto aa = [1:"hello", 2:"world"];
5538         formatReflectTest(aa, "%s",                     [`[1:"hello", 2:"world"]`, `[2:"world", 1:"hello"]`]);
5539         formatReflectTest(aa, "[%(%s->%s, %)]",         [`[1->"hello", 2->"world"]`, `[2->"world", 1->"hello"]`]);
5540         formatReflectTest(aa, "{%([%s=%(%c%)]%|; %)}",  [`{[1=hello]; [2=world]}`, `{[2=world]; [1=hello]}`]);
5541     }
5542 
5543     import std.exception;
5544     assertCTFEable!(
5545     {
5546         booleanTest();
5547         integerTest();
5548         if (!__ctfe) floatingTest();    // snprintf
5549         charTest();
5550         strTest();
5551         daTest();
5552         saTest();
5553         aaTest();
5554         return true;
5555     });
5556 }
5557 
5558 //------------------------------------------------------------------------------
5559 private void skipData(Range, Char)(ref Range input, scope const ref FormatSpec!Char spec)
5560 {
5561     import std.ascii : isDigit;
5562     import std.conv : text;
5563 
5564     switch (spec.spec)
5565     {
5566         case 'c': input.popFront(); break;
5567         case 'd':
5568             if (input.front == '+' || input.front == '-') input.popFront();
5569             goto case 'u';
5570         case 'u':
5571             while (!input.empty && isDigit(input.front)) input.popFront();
5572             break;
5573         default:
5574             assert(false,
5575                     text("Format specifier not understood: %", spec.spec));
5576     }
5577 }
5578 
5579 private template acceptedSpecs(T)
5580 {
5581          static if (isIntegral!T)       enum acceptedSpecs = "bdosuxX";
5582     else static if (isFloatingPoint!T)  enum acceptedSpecs = "seEfgG";
5583     else static if (isSomeChar!T)       enum acceptedSpecs = "bcdosuxX";    // integral + 'c'
5584     else                                enum acceptedSpecs = "";
5585 }
5586 
5587 /**
5588  * Reads a value from the given _input range according to spec
5589  * and returns it as type `T`.
5590  *
5591  * Params:
5592  *     T = the type to return
5593  *     input = the _input range to read from
5594  *     spec = the `FormatSpec` to use when reading from `input`
5595  * Returns:
5596  *     A value from `input` of type `T`
5597  * Throws:
5598  *     A `FormatException` if `spec` cannot read a type `T`
5599  * See_Also:
5600  *     $(REF parse, std, conv) and $(REF to, std, conv)
5601  */
5602 T unformatValue(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec)
5603 {
5604     return unformatValueImpl!T(input, spec);
5605 }
5606 
5607 /// Booleans
5608 @safe pure unittest
5609 {
5610     auto str = "false";
5611     auto spec = singleSpec("%s");
5612     assert(unformatValue!bool(str, spec) == false);
5613 
5614     str = "1";
5615     spec = singleSpec("%d");
5616     assert(unformatValue!bool(str, spec));
5617 }
5618 
5619 /// Null values
5620 @safe pure unittest
5621 {
5622     auto str = "null";
5623     auto spec = singleSpec("%s");
5624     assert(str.unformatValue!(typeof(null))(spec) == null);
5625 }
5626 
5627 /// Integrals
5628 @safe pure unittest
5629 {
5630     auto str = "123";
5631     auto spec = singleSpec("%s");
5632     assert(str.unformatValue!int(spec) == 123);
5633 
5634     str = "ABC";
5635     spec = singleSpec("%X");
5636     assert(str.unformatValue!int(spec) == 2748);
5637 
5638     str = "11610";
5639     spec = singleSpec("%o");
5640     assert(str.unformatValue!int(spec) == 5000);
5641 }
5642 
5643 /// Floating point numbers
5644 @safe pure unittest
5645 {
5646     import std.math : approxEqual;
5647 
5648     auto str = "123.456";
5649     auto spec = singleSpec("%s");
5650     assert(str.unformatValue!double(spec).approxEqual(123.456));
5651 }
5652 
5653 /// Character input ranges
5654 @safe pure unittest
5655 {
5656     auto str = "aaa";
5657     auto spec = singleSpec("%s");
5658     assert(str.unformatValue!char(spec) == 'a');
5659 
5660     // Using a numerical format spec reads a Unicode value from a string
5661     str = "65";
5662     spec = singleSpec("%d");
5663     assert(str.unformatValue!char(spec) == 'A');
5664 
5665     str = "41";
5666     spec = singleSpec("%x");
5667     assert(str.unformatValue!char(spec) == 'A');
5668 
5669     str = "10003";
5670     spec = singleSpec("%d");
5671     assert(str.unformatValue!dchar(spec) == '✓');
5672 }
5673 
5674 /// Arrays and static arrays
5675 @safe pure unittest
5676 {
5677     string str = "aaa";
5678     auto spec = singleSpec("%s");
5679     assert(str.unformatValue!(dchar[])(spec) == "aaa"d);
5680 
5681     str = "aaa";
5682     spec = singleSpec("%s");
5683     dchar[3] ret = ['a', 'a', 'a'];
5684     assert(str.unformatValue!(dchar[3])(spec) == ret);
5685 
5686     str = "[1, 2, 3, 4]";
5687     spec = singleSpec("%s");
5688     assert(str.unformatValue!(int[])(spec) == [1, 2, 3, 4]);
5689 
5690     str = "[1, 2, 3, 4]";
5691     spec = singleSpec("%s");
5692     int[4] ret2 = [1, 2, 3, 4];
5693     assert(str.unformatValue!(int[4])(spec) == ret2);
5694 }
5695 
5696 /// Associative arrays
5697 @safe pure unittest
5698 {
5699     auto str = `["one": 1, "two": 2]`;
5700     auto spec = singleSpec("%s");
5701     assert(str.unformatValue!(int[string])(spec) == ["one": 1, "two": 2]);
5702 }
5703 
5704 // https://issues.dlang.org/show_bug.cgi?id=7241
5705 @safe pure unittest
5706 {
5707     string input = "a";
5708     auto spec = FormatSpec!char("%s");
5709     spec.readUpToNextSpec(input);
5710     auto result = unformatValue!(dchar[1])(input, spec);
5711     assert(result[0] == 'a');
5712 }
5713 
5714 private T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec)
5715 if (isInputRange!Range && is(immutable T == immutable bool))
5716 {
5717     import std.algorithm.searching : find;
5718     import std.conv : parse, text;
5719 
5720     if (spec.spec == 's') return parse!T(input);
5721 
5722     enforceFmt(find(acceptedSpecs!long, spec.spec).length,
5723             text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
5724 
5725     return unformatValue!long(input, spec) != 0;
5726 }
5727 
5728 private T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec)
5729 if (isInputRange!Range && is(T == typeof(null)))
5730 {
5731     import std.conv : parse, text;
5732     enforceFmt(spec.spec == 's',
5733             text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
5734 
5735     return parse!T(input);
5736 }
5737 
5738 /// ditto
5739 private T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec)
5740 if (isInputRange!Range && isIntegral!T && !is(T == enum) && isSomeChar!(ElementType!Range))
5741 {
5742 
5743     import std.algorithm.searching : find;
5744     import std.conv : parse, text;
5745 
5746     if (spec.spec == 'r')
5747     {
5748         static if (is(immutable ElementEncodingType!Range == immutable char)
5749                 || is(immutable ElementEncodingType!Range == immutable byte)
5750                 || is(immutable ElementEncodingType!Range == immutable ubyte))
5751             return rawRead!T(input);
5752         else
5753             throw new FormatException(
5754                 "The raw read specifier %r may only be used with narrow strings and ranges of bytes."
5755             );
5756     }
5757 
5758     enforceFmt(find(acceptedSpecs!T, spec.spec).length,
5759             text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
5760 
5761     enforceFmt(spec.width == 0, "Parsing integers with a width specification is not implemented");   // TODO
5762 
5763     immutable uint base =
5764         spec.spec == 'x' || spec.spec == 'X' ? 16 :
5765         spec.spec == 'o' ? 8 :
5766         spec.spec == 'b' ? 2 :
5767         spec.spec == 's' || spec.spec == 'd' || spec.spec == 'u' ? 10 : 0;
5768     assert(base != 0, "base must be not equal to zero");
5769 
5770     return parse!T(input, base);
5771 
5772 }
5773 
5774 /// ditto
5775 private T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec)
5776 if (isFloatingPoint!T && !is(T == enum) && isInputRange!Range
5777     && isSomeChar!(ElementType!Range)&& !is(Range == enum))
5778 {
5779     import std.algorithm.searching : find;
5780     import std.conv : parse, text;
5781 
5782     if (spec.spec == 'r')
5783     {
5784         static if (is(immutable ElementEncodingType!Range == immutable char)
5785                 || is(immutable ElementEncodingType!Range == immutable byte)
5786                 || is(immutable ElementEncodingType!Range == immutable ubyte))
5787             return rawRead!T(input);
5788         else
5789             throw new FormatException(
5790                 "The raw read specifier %r may only be used with narrow strings and ranges of bytes."
5791             );
5792     }
5793 
5794     enforceFmt(find(acceptedSpecs!T, spec.spec).length,
5795             text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
5796 
5797     return parse!T(input);
5798 }
5799 
5800 /// ditto
5801 private T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec)
5802 if (isInputRange!Range && isSomeChar!T && !is(T == enum) && isSomeChar!(ElementType!Range))
5803 {
5804     import std.algorithm.searching : find;
5805     import std.conv : to, text;
5806     if (spec.spec == 's' || spec.spec == 'c')
5807     {
5808         auto result = to!T(input.front);
5809         input.popFront();
5810         return result;
5811     }
5812     enforceFmt(find(acceptedSpecs!T, spec.spec).length,
5813             text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof));
5814 
5815     static if (T.sizeof == 1)
5816         return unformatValue!ubyte(input, spec);
5817     else static if (T.sizeof == 2)
5818         return unformatValue!ushort(input, spec);
5819     else static if (T.sizeof == 4)
5820         return unformatValue!uint(input, spec);
5821     else
5822         static assert(false, T.stringof ~ ".sizeof must be 1, 2, or 4 not " ~
5823                 to!string(T.sizeof));
5824 }
5825 
5826 /// ditto
5827 private T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char fmt)
5828 if (isInputRange!Range && is(StringTypeOf!T) && !isAggregateType!T && !is(T == enum))
5829 {
5830     import std.conv : text;
5831 
5832     const spec = fmt.spec;
5833     if (spec == '(')
5834     {
5835         return unformatRange!T(input, fmt);
5836     }
5837     enforceFmt(spec == 's',
5838             text("Wrong unformat specifier '%", spec , "' for ", T.stringof));
5839 
5840     static if (isStaticArray!T)
5841     {
5842         T result;
5843         auto app = result[];
5844     }
5845     else
5846     {
5847         import std.array : appender;
5848         auto app = appender!T();
5849     }
5850     if (fmt.trailing.empty)
5851     {
5852         for (; !input.empty; input.popFront())
5853         {
5854             static if (isStaticArray!T)
5855                 if (app.empty)
5856                     break;
5857             app.put(input.front);
5858         }
5859     }
5860     else
5861     {
5862         immutable end = fmt.trailing.front;
5863         for (; !input.empty && input.front != end; input.popFront())
5864         {
5865             static if (isStaticArray!T)
5866                 if (app.empty)
5867                     break;
5868             app.put(input.front);
5869         }
5870     }
5871     static if (isStaticArray!T)
5872     {
5873         enforceFmt(app.empty, "need more input");
5874         return result;
5875     }
5876     else
5877         return app.data;
5878 }
5879 
5880 /// ditto
5881 private T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char fmt)
5882 if (isInputRange!Range && isArray!T && !is(StringTypeOf!T) && !isAggregateType!T && !is(T == enum))
5883 {
5884     import std.conv : parse, text;
5885     const spec = fmt.spec;
5886     if (spec == '(')
5887     {
5888         return unformatRange!T(input, fmt);
5889     }
5890     enforceFmt(spec == 's',
5891             text("Wrong unformat specifier '%", spec , "' for ", T.stringof));
5892 
5893     return parse!T(input);
5894 }
5895 
5896 /// ditto
5897 private T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char fmt)
5898 if (isInputRange!Range && isAssociativeArray!T && !is(T == enum))
5899 {
5900     import std.conv : parse, text;
5901     const spec = fmt.spec;
5902     if (spec == '(')
5903     {
5904         return unformatRange!T(input, fmt);
5905     }
5906     enforceFmt(spec == 's',
5907             text("Wrong unformat specifier '%", spec , "' for ", T.stringof));
5908 
5909     return parse!T(input);
5910 }
5911 
5912 /**
5913  * Function that performs raw reading. Used by unformatValue
5914  * for integral and float types.
5915  */
5916 private T rawRead(T, Range)(ref Range input)
5917 if (is(immutable ElementEncodingType!Range == immutable char)
5918     || is(immutable ElementEncodingType!Range == immutable byte)
5919     || is(immutable ElementEncodingType!Range == immutable ubyte))
5920 {
5921     union X
5922     {
5923         ubyte[T.sizeof] raw;
5924         T typed;
5925     }
5926     X x;
5927     foreach (i; 0 .. T.sizeof)
5928     {
5929         static if (isSomeString!Range)
5930         {
5931             x.raw[i] = input[0];
5932             input = input[1 .. $];
5933         }
5934         else
5935         {
5936             // TODO: recheck this
5937             x.raw[i] = input.front;
5938             input.popFront();
5939         }
5940     }
5941     return x.typed;
5942 }
5943 
5944 //debug = unformatRange;
5945 
5946 private T unformatRange(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec)
5947 in
5948 {
5949     const char ss = spec.spec;
5950     assert(ss == '(', "spec.spec must be '(' not " ~ ss);
5951 }
5952 do
5953 {
5954     debug (unformatRange) printf("unformatRange:\n");
5955 
5956     T result;
5957     static if (isStaticArray!T)
5958     {
5959         size_t i;
5960     }
5961 
5962     const(Char)[] cont = spec.trailing;
5963     for (size_t j = 0; j < spec.trailing.length; ++j)
5964     {
5965         if (spec.trailing[j] == '%')
5966         {
5967             cont = spec.trailing[0 .. j];
5968             break;
5969         }
5970     }
5971     debug (unformatRange) printf("\t");
5972     debug (unformatRange) if (!input.empty) printf("input.front = %c, ", input.front);
5973     debug (unformatRange) printf("cont = %.*s\n", cast(int) cont.length, cont.ptr);
5974 
5975     bool checkEnd()
5976     {
5977         return input.empty || !cont.empty && input.front == cont.front;
5978     }
5979 
5980     if (!checkEnd())
5981     {
5982         for (;;)
5983         {
5984             auto fmt = FormatSpec!Char(spec.nested);
5985             fmt.readUpToNextSpec(input);
5986             enforceFmt(!input.empty, "Unexpected end of input when parsing range");
5987 
5988             debug (unformatRange) printf("\t) spec = %c, front = %c ", fmt.spec, input.front);
5989             static if (isStaticArray!T)
5990             {
5991                 result[i++] = unformatElement!(typeof(T.init[0]))(input, fmt);
5992             }
5993             else static if (isDynamicArray!T)
5994             {
5995                 import std.conv : WideElementType;
5996                 result ~= unformatElement!(WideElementType!T)(input, fmt);
5997             }
5998             else static if (isAssociativeArray!T)
5999             {
6000                 auto key = unformatElement!(typeof(T.init.keys[0]))(input, fmt);
6001                 fmt.readUpToNextSpec(input);        // eat key separator
6002 
6003                 result[key] = unformatElement!(typeof(T.init.values[0]))(input, fmt);
6004             }
6005             debug (unformatRange) {
6006             if (input.empty) printf("-> front = [empty] ");
6007             else             printf("-> front = %c ", input.front);
6008             }
6009 
6010             static if (isStaticArray!T)
6011             {
6012                 debug (unformatRange) printf("i = %u < %u\n", i, T.length);
6013                 enforceFmt(i <= T.length, "Too many format specifiers for static array of length %d".format(T.length));
6014             }
6015 
6016             if (spec.sep !is null)
6017                 fmt.readUpToNextSpec(input);
6018             auto sep = spec.sep !is null ? spec.sep
6019                          : fmt.trailing;
6020             debug (unformatRange) {
6021             if (!sep.empty && !input.empty) printf("-> %c, sep = %.*s\n", input.front, cast(int) sep.length, sep.ptr);
6022             else                            printf("\n");
6023             }
6024 
6025             if (checkEnd())
6026                 break;
6027 
6028             if (!sep.empty && input.front == sep.front)
6029             {
6030                 while (!sep.empty)
6031                 {
6032                     enforceFmt(!input.empty, "Unexpected end of input when parsing range separator");
6033                     enforceFmt(input.front == sep.front, "Unexpected character when parsing range separator");
6034                     input.popFront();
6035                     sep.popFront();
6036                 }
6037                 debug (unformatRange) printf("input.front = %c\n", input.front);
6038             }
6039         }
6040     }
6041     static if (isStaticArray!T)
6042     {
6043         enforceFmt(i == T.length, "Too few (%d) format specifiers for static array of length %d".format(i, T.length));
6044     }
6045     return result;
6046 }
6047 
6048 // Undocumented
6049 T unformatElement(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec)
6050 if (isInputRange!Range)
6051 {
6052     import std.conv : parseElement;
6053     static if (isSomeString!T)
6054     {
6055         if (spec.spec == 's')
6056         {
6057             return parseElement!T(input);
6058         }
6059     }
6060     else static if (isSomeChar!T)
6061     {
6062         if (spec.spec == 's')
6063         {
6064             return parseElement!T(input);
6065         }
6066     }
6067 
6068     return unformatValue!T(input, spec);
6069 }
6070 
6071 private bool needToSwapEndianess(Char)(scope const ref FormatSpec!Char f)
6072 {
6073     import std.system : endian, Endian;
6074 
6075     return endian == Endian.littleEndian && f.flPlus
6076         || endian == Endian.bigEndian && f.flDash;
6077 }
6078 
6079 /* ======================== Unit Tests ====================================== */
6080 
6081 @system unittest
6082 {
6083     import std.conv : octal;
6084 
6085     int i;
6086     string s;
6087 
6088     debug(format) printf("std.format.format.unittest\n");
6089 
6090     s = format("hello world! %s %s %s%s%s", true, 57, 1_000_000_000, 'x', " foo");
6091     assert(s == "hello world! true 57 1000000000x foo");
6092 
6093     s = format("%s %A %s", 1.67, -1.28, float.nan);
6094     assert(s == "1.67 -0X1.47AE147AE147BP+0 nan", s);
6095 
6096     s = format("%x %X", 0x1234AF, 0xAFAFAFAF);
6097     assert(s == "1234af AFAFAFAF");
6098 
6099     s = format("%b %o", 0x1234AF, 0xAFAFAFAF);
6100     assert(s == "100100011010010101111 25753727657");
6101 
6102     s = format("%d %s", 0x1234AF, 0xAFAFAFAF);
6103     assert(s == "1193135 2947526575");
6104 }
6105 
6106 version (TestComplex)
6107 deprecated
6108 @system unittest
6109 {
6110         string s = format("%s", 1.2 + 3.4i);
6111         assert(s == "1.2+3.4i", s);
6112 }
6113 
6114 @system unittest
6115 {
6116     import std.conv : octal;
6117 
6118     string s;
6119     int i;
6120 
6121     s = format("%#06.*f",2,12.345);
6122     assert(s == "012.35");
6123 
6124     s = format("%#0*.*f",6,2,12.345);
6125     assert(s == "012.35");
6126 
6127     s = format("%7.4g:", 12.678);
6128     assert(s == "  12.68:");
6129 
6130     s = format("%7.4g:", 12.678L);
6131     assert(s == "  12.68:");
6132 
6133     s = format("%04f|%05d|%#05x|%#5x",-4.0,-10,1,1);
6134     assert(s == "-4.000000|-0010|0x001|  0x1");
6135 
6136     i = -10;
6137     s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
6138     assert(s == "-10|-10|-10|-10|-10.0000");
6139 
6140     i = -5;
6141     s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
6142     assert(s == "-5| -5|-05|-5|-5.0000");
6143 
6144     i = 0;
6145     s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
6146     assert(s == "0|  0|000|0|0.0000");
6147 
6148     i = 5;
6149     s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
6150     assert(s == "5|  5|005|5|5.0000");
6151 
6152     i = 10;
6153     s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
6154     assert(s == "10| 10|010|10|10.0000");
6155 
6156     s = format("%.0d", 0);
6157     assert(s == "");
6158 
6159     s = format("%.g", .34);
6160     assert(s == "0.3");
6161 
6162     s = format("%.0g", .34);
6163     assert(s == "0.3");
6164 
6165     s = format("%.2g", .34);
6166     assert(s == "0.34");
6167 
6168     s = format("%0.0008f", 1e-08);
6169     assert(s == "0.00000001");
6170 
6171     s = format("%0.0008f", 1e-05);
6172     assert(s == "0.00001000");
6173 
6174     s = "helloworld";
6175     string r;
6176     r = format("%.2s", s[0 .. 5]);
6177     assert(r == "he");
6178     r = format("%.20s", s[0 .. 5]);
6179     assert(r == "hello");
6180     r = format("%8s", s[0 .. 5]);
6181     assert(r == "   hello");
6182 
6183     byte[] arrbyte = new byte[4];
6184     arrbyte[0] = 100;
6185     arrbyte[1] = -99;
6186     arrbyte[3] = 0;
6187     r = format("%s", arrbyte);
6188     assert(r == "[100, -99, 0, 0]");
6189 
6190     ubyte[] arrubyte = new ubyte[4];
6191     arrubyte[0] = 100;
6192     arrubyte[1] = 200;
6193     arrubyte[3] = 0;
6194     r = format("%s", arrubyte);
6195     assert(r == "[100, 200, 0, 0]");
6196 
6197     short[] arrshort = new short[4];
6198     arrshort[0] = 100;
6199     arrshort[1] = -999;
6200     arrshort[3] = 0;
6201     r = format("%s", arrshort);
6202     assert(r == "[100, -999, 0, 0]");
6203 
6204     ushort[] arrushort = new ushort[4];
6205     arrushort[0] = 100;
6206     arrushort[1] = 20_000;
6207     arrushort[3] = 0;
6208     r = format("%s", arrushort);
6209     assert(r == "[100, 20000, 0, 0]");
6210 
6211     int[] arrint = new int[4];
6212     arrint[0] = 100;
6213     arrint[1] = -999;
6214     arrint[3] = 0;
6215     r = format("%s", arrint);
6216     assert(r == "[100, -999, 0, 0]");
6217 
6218     long[] arrlong = new long[4];
6219     arrlong[0] = 100;
6220     arrlong[1] = -999;
6221     arrlong[3] = 0;
6222     r = format("%s", arrlong);
6223     assert(r == "[100, -999, 0, 0]");
6224 
6225     ulong[] arrulong = new ulong[4];
6226     arrulong[0] = 100;
6227     arrulong[1] = 999;
6228     arrulong[3] = 0;
6229     r = format("%s", arrulong);
6230     assert(r == "[100, 999, 0, 0]");
6231 
6232     string[] arr2 = new string[4];
6233     arr2[0] = "hello";
6234     arr2[1] = "world";
6235     arr2[3] = "foo";
6236     r = format("%s", arr2);
6237     assert(r == `["hello", "world", "", "foo"]`);
6238 
6239     r = format("%.8d", 7);
6240     assert(r == "00000007");
6241     r = format("%.8x", 10);
6242     assert(r == "0000000a");
6243 
6244     r = format("%-3d", 7);
6245     assert(r == "7  ");
6246 
6247     r = format("%-1*d", 4, 3);
6248     assert(r == "3   ");
6249 
6250     r = format("%*d", -3, 7);
6251     assert(r == "7  ");
6252 
6253     r = format("%.*d", -3, 7);
6254     assert(r == "7");
6255 
6256     r = format("%-1.*f", 2, 3.1415);
6257     assert(r == "3.14");
6258 
6259     r = format("abc"c);
6260     assert(r == "abc");
6261 
6262     //format() returns the same type as inputted.
6263     wstring wr;
6264     wr = format("def"w);
6265     assert(wr == "def"w);
6266 
6267     dstring dr;
6268     dr = format("ghi"d);
6269     assert(dr == "ghi"d);
6270 
6271     // Empty static character arrays work as well
6272     const char[0] cempty;
6273     assert(format("test%spath", cempty) == "testpath");
6274     const wchar[0] wempty;
6275     assert(format("test%spath", wempty) == "testpath");
6276     const dchar[0] dempty;
6277     assert(format("test%spath", dempty) == "testpath");
6278 
6279     void* p = cast(void*) 0xDEADBEEF;
6280     r = format("%s", p);
6281     assert(r == "DEADBEEF");
6282 
6283     r = format("%#x", 0xabcd);
6284     assert(r == "0xabcd");
6285     r = format("%#X", 0xABCD);
6286     assert(r == "0XABCD");
6287 
6288     r = format("%#o", octal!12345);
6289     assert(r == "012345");
6290     r = format("%o", 9);
6291     assert(r == "11");
6292     r = format("%#o", 0);   // https://issues.dlang.org/show_bug.cgi?id=15663
6293     assert(r == "0");
6294 
6295     r = format("%+d", 123);
6296     assert(r == "+123");
6297     r = format("%+d", -123);
6298     assert(r == "-123");
6299     r = format("% d", 123);
6300     assert(r == " 123");
6301     r = format("% d", -123);
6302     assert(r == "-123");
6303 
6304     r = format("%%");
6305     assert(r == "%");
6306 
6307     r = format("%d", true);
6308     assert(r == "1");
6309     r = format("%d", false);
6310     assert(r == "0");
6311 
6312     r = format("%d", 'a');
6313     assert(r == "97");
6314     wchar wc = 'a';
6315     r = format("%d", wc);
6316     assert(r == "97");
6317     dchar dc = 'a';
6318     r = format("%d", dc);
6319     assert(r == "97");
6320 
6321     byte b = byte.max;
6322     r = format("%x", b);
6323     assert(r == "7f");
6324     r = format("%x", ++b);
6325     assert(r == "80");
6326     r = format("%x", ++b);
6327     assert(r == "81");
6328 
6329     short sh = short.max;
6330     r = format("%x", sh);
6331     assert(r == "7fff");
6332     r = format("%x", ++sh);
6333     assert(r == "8000");
6334     r = format("%x", ++sh);
6335     assert(r == "8001");
6336 
6337     i = int.max;
6338     r = format("%x", i);
6339     assert(r == "7fffffff");
6340     r = format("%x", ++i);
6341     assert(r == "80000000");
6342     r = format("%x", ++i);
6343     assert(r == "80000001");
6344 
6345     r = format("%x", 10);
6346     assert(r == "a");
6347     r = format("%X", 10);
6348     assert(r == "A");
6349     r = format("%x", 15);
6350     assert(r == "f");
6351     r = format("%X", 15);
6352     assert(r == "F");
6353 
6354     Object c = null;
6355     r = format("%s", c);
6356     assert(r == "null");
6357 
6358     enum TestEnum
6359     {
6360         Value1, Value2
6361     }
6362     r = format("%s", TestEnum.Value2);
6363     assert(r == "Value2");
6364 
6365     immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]);
6366     r = format("%s", aa.values);
6367     assert(r == `["hello", "betty"]` || r == `["betty", "hello"]`);
6368     r = format("%s", aa);
6369     assert(r == `[3:"hello", 4:"betty"]` || r == `[4:"betty", 3:"hello"]`);
6370 
6371     static const dchar[] ds = ['a','b'];
6372     for (int j = 0; j < ds.length; ++j)
6373     {
6374         r = format(" %d", ds[j]);
6375         if (j == 0)
6376             assert(r == " 97");
6377         else
6378             assert(r == " 98");
6379     }
6380 
6381     r = format(">%14d<, %s", 15, [1,2,3]);
6382     assert(r == ">            15<, [1, 2, 3]");
6383 
6384     assert(format("%8s", "bar") == "     bar");
6385     assert(format("%8s", "b\u00e9ll\u00f4") == "   b\u00e9ll\u00f4");
6386 }
6387 
6388 // https://issues.dlang.org/show_bug.cgi?id=18205
6389 @safe pure unittest
6390 {
6391     assert("|%8s|".format("abc")        == "|     abc|");
6392     assert("|%8s|".format("αβγ")        == "|     αβγ|");
6393     assert("|%8s|".format("   ")        == "|        |");
6394     assert("|%8s|".format("été"d)       == "|     été|");
6395     assert("|%8s|".format("été 2018"w)  == "|été 2018|");
6396 
6397     assert("%2s".format("e\u0301"w) == " e\u0301");
6398     assert("%2s".format("a\u0310\u0337"d) == " a\u0310\u0337");
6399 }
6400 
6401 // https://issues.dlang.org/show_bug.cgi?id=3479
6402 @safe unittest
6403 {
6404     import std.array;
6405     auto stream = appender!(char[])();
6406     formattedWrite(stream, "%2$.*1$d", 12, 10);
6407     assert(stream.data == "000000000010", stream.data);
6408 }
6409 
6410 // https://issues.dlang.org/show_bug.cgi?id=6893
6411 @safe unittest
6412 {
6413     import std.array;
6414     enum E : ulong { A, B, C }
6415     auto stream = appender!(char[])();
6416     formattedWrite(stream, "%s", E.C);
6417     assert(stream.data == "C");
6418 }
6419 
6420 // Used to check format strings are compatible with argument types
6421 package static const checkFormatException(alias fmt, Args...) =
6422 {
6423     import std.conv : text;
6424 
6425     try
6426     {
6427         auto n = .formattedWrite(NoOpSink(), fmt, Args.init);
6428 
6429         enforceFmt(n == Args.length, text("Orphan format arguments: args[", n, "..", Args.length, "]"));
6430     }
6431     catch (Exception e)
6432         return (e.msg == ctfpMessage) ? null : e;
6433     return null;
6434 }();
6435 
6436 private void writeAligned(Writer, T, Char)(auto ref Writer w, T s, scope const ref FormatSpec!Char f)
6437 if (isSomeString!T)
6438 {
6439     size_t width;
6440     if (f.width > 0)
6441     {
6442         // check for non-ascii character
6443         import std.algorithm.searching : any;
6444         if (s.any!(a => a > 0x7F))
6445         {
6446             //TODO: optimize this
6447             import std.uni : graphemeStride;
6448             for (size_t i; i < s.length; i += graphemeStride(s, i))
6449                 ++width;
6450         }
6451         else
6452             width = s.length;
6453     }
6454     else
6455         width = s.length;
6456 
6457     if (!f.flDash)
6458     {
6459         // right align
6460         if (f.width > width)
6461             foreach (i ; 0 .. f.width - width) put(w, ' ');
6462         put(w, s);
6463     }
6464     else
6465     {
6466         // left align
6467         put(w, s);
6468         if (f.width > width)
6469             foreach (i ; 0 .. f.width - width) put(w, ' ');
6470     }
6471 }
6472 
6473 @safe pure unittest
6474 {
6475     import std.array : appender;
6476     auto w = appender!string();
6477     auto spec = singleSpec("%s");
6478     writeAligned(w, "a本Ä", spec);
6479     assert(w.data == "a本Ä", w.data);
6480 }
6481 
6482 @safe pure unittest
6483 {
6484     import std.array : appender;
6485     auto w = appender!string();
6486     auto spec = singleSpec("%10s");
6487     writeAligned(w, "a本Ä", spec);
6488     assert(w.data == "       a本Ä", "|" ~ w.data ~ "|");
6489 }
6490 
6491 @safe pure unittest
6492 {
6493     import std.array : appender;
6494     auto w = appender!string();
6495     auto spec = singleSpec("%-10s");
6496     writeAligned(w, "a本Ä", spec);
6497     assert(w.data == "a本Ä       ", w.data);
6498 }
6499 
6500 /**
6501  * Format arguments into a string.
6502  *
6503  * If the format string is fixed, passing it as a template parameter checks the
6504  * type correctness of the parameters at compile-time. This also can result in
6505  * better performance.
6506  *
6507  * Params: fmt  = Format string. For detailed specification, see $(LREF formattedWrite).
6508  *         args = Variadic list of arguments to format into returned string.
6509  *
6510  * Throws:
6511  *     $(LREF, FormatException) if the number of arguments doesn't match the number
6512  *     of format parameters and vice-versa.
6513  */
6514 typeof(fmt) format(alias fmt, Args...)(Args args)
6515 if (isSomeString!(typeof(fmt)))
6516 {
6517     import std.array : appender;
6518 
6519     alias e = checkFormatException!(fmt, Args);
6520     alias Char = Unqual!(ElementEncodingType!(typeof(fmt)));
6521 
6522     static assert(!e, e.msg);
6523     auto w = appender!(immutable(Char)[]);
6524 
6525     // no need to traverse the string twice during compile time
6526     if (!__ctfe)
6527     {
6528         enum len = guessLength!Char(fmt);
6529         w.reserve(len);
6530     }
6531     else
6532     {
6533         w.reserve(fmt.length);
6534     }
6535 
6536     formattedWrite(w, fmt, args);
6537     return w.data;
6538 }
6539 
6540 /// Type checking can be done when fmt is known at compile-time:
6541 @safe unittest
6542 {
6543     auto s = format!"%s is %s"("Pi", 3.14);
6544     assert(s == "Pi is 3.14");
6545 
6546     static assert(!__traits(compiles, {s = format!"%l"();}));     // missing arg
6547     static assert(!__traits(compiles, {s = format!""(404);}));    // surplus arg
6548     static assert(!__traits(compiles, {s = format!"%d"(4.03);})); // incompatible arg
6549 }
6550 
6551 // called during compilation to guess the length of the
6552 // result of format
6553 private size_t guessLength(Char, S)(S fmtString)
6554 {
6555     import std.array : appender;
6556 
6557     size_t len;
6558     auto output = appender!(immutable(Char)[])();
6559     auto spec = FormatSpec!Char(fmtString);
6560     while (spec.writeUpToNextSpec(output))
6561     {
6562         // take a guess
6563         if (spec.width == 0 && (spec.precision == spec.UNSPECIFIED || spec.precision == spec.DYNAMIC))
6564         {
6565             switch (spec.spec)
6566             {
6567                 case 'c':
6568                     ++len;
6569                     break;
6570                 case 'd':
6571                 case 'x':
6572                 case 'X':
6573                     len += 3;
6574                     break;
6575                 case 'b':
6576                     len += 8;
6577                     break;
6578                 case 'f':
6579                 case 'F':
6580                     len += 10;
6581                     break;
6582                 case 's':
6583                 case 'e':
6584                 case 'E':
6585                 case 'g':
6586                 case 'G':
6587                     len += 12;
6588                     break;
6589                 default: break;
6590             }
6591 
6592             continue;
6593         }
6594 
6595         if ((spec.spec == 'e' || spec.spec == 'E' || spec.spec == 'g' ||
6596              spec.spec == 'G' || spec.spec == 'f' || spec.spec == 'F') &&
6597             spec.precision != spec.UNSPECIFIED && spec.precision != spec.DYNAMIC &&
6598             spec.width == 0
6599         )
6600         {
6601             len += spec.precision + 5;
6602             continue;
6603         }
6604 
6605         if (spec.width == spec.precision)
6606             len += spec.width;
6607         else if (spec.width > 0 && spec.width != spec.DYNAMIC &&
6608                  (spec.precision == spec.UNSPECIFIED || spec.width > spec.precision))
6609         {
6610             len += spec.width;
6611         }
6612         else if (spec.precision != spec.UNSPECIFIED && spec.precision > spec.width)
6613             len += spec.precision;
6614     }
6615     len += output.data.length;
6616     return len;
6617 }
6618 
6619 @safe pure
6620 unittest
6621 {
6622     assert(guessLength!char("%c") == 1);
6623     assert(guessLength!char("%d") == 3);
6624     assert(guessLength!char("%x") == 3);
6625     assert(guessLength!char("%b") == 8);
6626     assert(guessLength!char("%f") == 10);
6627     assert(guessLength!char("%s") == 12);
6628     assert(guessLength!char("%02d") == 2);
6629     assert(guessLength!char("%02d") == 2);
6630     assert(guessLength!char("%4.4d") == 4);
6631     assert(guessLength!char("%2.4f") == 4);
6632     assert(guessLength!char("%02d:%02d:%02d") == 8);
6633     assert(guessLength!char("%0.2f") == 7);
6634     assert(guessLength!char("%0*d") == 0);
6635 }
6636 
6637 /// ditto
6638 immutable(Char)[] format(Char, Args...)(in Char[] fmt, Args args)
6639 if (isSomeChar!Char)
6640 {
6641     import std.array : appender;
6642     auto w = appender!(immutable(Char)[]);
6643     auto n = formattedWrite(w, fmt, args);
6644     version (all)
6645     {
6646         // In the future, this check will be removed to increase consistency
6647         // with formattedWrite
6648         import std.conv : text;
6649         enforceFmt(n == args.length, text("Orphan format arguments: args[", n, "..", args.length, "]"));
6650     }
6651     return w.data;
6652 }
6653 
6654 @safe pure unittest
6655 {
6656     import core.exception;
6657     import std.exception;
6658     assertCTFEable!(
6659     {
6660 //  assert(format(null) == "");
6661     assert(format("foo") == "foo");
6662     assert(format("foo%%") == "foo%");
6663     assert(format("foo%s", 'C') == "fooC");
6664     assert(format("%s foo", "bar") == "bar foo");
6665     assert(format("%s foo %s", "bar", "abc") == "bar foo abc");
6666     assert(format("foo %d", -123) == "foo -123");
6667     assert(format("foo %d", 123) == "foo 123");
6668 
6669     assertThrown!FormatException(format("foo %s"));
6670     assertThrown!FormatException(format("foo %s", 123, 456));
6671 
6672     assert(format("hel%slo%s%s%s", "world", -138, 'c', true) ==
6673                   "helworldlo-138ctrue");
6674     });
6675 
6676     assert(is(typeof(format("happy")) == string));
6677     assert(is(typeof(format("happy"w)) == wstring));
6678     assert(is(typeof(format("happy"d)) == dstring));
6679 }
6680 
6681 // https://issues.dlang.org/show_bug.cgi?id=16661
6682 @safe unittest
6683 {
6684     assert(format("%.2f"d, 0.4) == "0.40");
6685     assert("%02d"d.format(1) == "01"d);
6686 }
6687 
6688 /*****************************************************
6689  * Format arguments into buffer $(I buf) which must be large
6690  * enough to hold the result.
6691  *
6692  * Returns:
6693  *     The slice of `buf` containing the formatted string.
6694  *
6695  * Throws:
6696  *     A `RangeError` if `buf` isn't large enough to hold the
6697  *     formatted string.
6698  *
6699  *     A $(LREF FormatException) if the length of `args` is different
6700  *     than the number of format specifiers in `fmt`.
6701  */
6702 char[] sformat(alias fmt, Args...)(char[] buf, Args args)
6703 if (isSomeString!(typeof(fmt)))
6704 {
6705     alias e = checkFormatException!(fmt, Args);
6706     static assert(!e, e.msg);
6707     return .sformat(buf, fmt, args);
6708 }
6709 
6710 /// ditto
6711 char[] sformat(Char, Args...)(return scope char[] buf, scope const(Char)[] fmt, Args args)
6712 {
6713     import core.exception : RangeError;
6714     import std.utf : encode;
6715 
6716     static struct Sink
6717     {
6718         char[] buf;
6719         size_t i;
6720         void put(dchar c)
6721         {
6722             char[4] enc;
6723             auto n = encode(enc, c);
6724 
6725             if (buf.length < i + n)
6726                 throw new RangeError(__FILE__, __LINE__);
6727 
6728             buf[i .. i + n] = enc[0 .. n];
6729             i += n;
6730         }
6731         void put(scope const(char)[] s)
6732         {
6733             if (buf.length < i + s.length)
6734                 throw new RangeError(__FILE__, __LINE__);
6735 
6736             buf[i .. i + s.length] = s[];
6737             i += s.length;
6738         }
6739         void put(scope const(wchar)[] s)
6740         {
6741             for (; !s.empty; s.popFront())
6742                 put(s.front);
6743         }
6744         void put(scope const(dchar)[] s)
6745         {
6746             for (; !s.empty; s.popFront())
6747                 put(s.front);
6748         }
6749     }
6750     auto sink = Sink(buf);
6751     auto n = formattedWrite(sink, fmt, args);
6752     version (all)
6753     {
6754         // In the future, this check will be removed to increase consistency
6755         // with formattedWrite
6756         import std.conv : text;
6757         enforceFmt(
6758             n == args.length,
6759             text("Orphan format arguments: args[", n, " .. ", args.length, "]")
6760         );
6761     }
6762     return buf[0 .. sink.i];
6763 }
6764 
6765 /// The format string can be checked at compile-time (see $(LREF format) for details):
6766 @system unittest
6767 {
6768     char[10] buf;
6769 
6770     assert(buf[].sformat!"foo%s"('C') == "fooC");
6771     assert(sformat(buf[], "%s foo", "bar") == "bar foo");
6772 }
6773 
6774 @system unittest
6775 {
6776     import core.exception;
6777 
6778     debug(string) trustedPrintf("std.string.sformat.unittest\n");
6779 
6780     import std.exception;
6781     assertCTFEable!(
6782     {
6783     char[10] buf;
6784 
6785     assert(sformat(buf[], "foo") == "foo");
6786     assert(sformat(buf[], "foo%%") == "foo%");
6787     assert(sformat(buf[], "foo%s", 'C') == "fooC");
6788     assert(sformat(buf[], "%s foo", "bar") == "bar foo");
6789     assertThrown!RangeError(sformat(buf[], "%s foo %s", "bar", "abc"));
6790     assert(sformat(buf[], "foo %d", -123) == "foo -123");
6791     assert(sformat(buf[], "foo %d", 123) == "foo 123");
6792 
6793     assertThrown!FormatException(sformat(buf[], "foo %s"));
6794     assertThrown!FormatException(sformat(buf[], "foo %s", 123, 456));
6795 
6796     assert(sformat(buf[], "%s %s %s", "c"c, "w"w, "d"d) == "c w d");
6797     });
6798 }
6799 
6800 @system unittest // ensure that sformat avoids the GC
6801 {
6802     import core.memory : GC;
6803     const a = ["foo", "bar"];
6804     const u = GC.stats().usedSize;
6805     char[20] buf;
6806     sformat(buf, "%d", 123);
6807     sformat(buf, "%s", a);
6808     sformat(buf, "%s", 'c');
6809     assert(u == GC.stats().usedSize);
6810 }
6811 
6812 /*****************************
6813  * The .ptr is unsafe because it could be dereferenced and the length of the array may be 0.
6814  * Returns:
6815  *      the difference between the starts of the arrays
6816  */
6817 @trusted private pure nothrow @nogc
6818     ptrdiff_t arrayPtrDiff(T)(const T[] array1, const T[] array2)
6819 {
6820     return array1.ptr - array2.ptr;
6821 }
6822 
6823 @safe unittest
6824 {
6825     assertCTFEable!({
6826     auto tmp = format("%,d", 1000);
6827     assert(tmp == "1,000", "'" ~ tmp ~ "'");
6828 
6829     tmp = format("%,?d", 'z', 1234567);
6830     assert(tmp == "1z234z567", "'" ~ tmp ~ "'");
6831 
6832     tmp = format("%10,?d", 'z', 1234567);
6833     assert(tmp == " 1z234z567", "'" ~ tmp ~ "'");
6834 
6835     tmp = format("%11,2?d", 'z', 1234567);
6836     assert(tmp == " 1z23z45z67", "'" ~ tmp ~ "'");
6837 
6838     tmp = format("%11,*?d", 2, 'z', 1234567);
6839     assert(tmp == " 1z23z45z67", "'" ~ tmp ~ "'");
6840 
6841     tmp = format("%11,*d", 2, 1234567);
6842     assert(tmp == " 1,23,45,67", "'" ~ tmp ~ "'");
6843 
6844     tmp = format("%11,2d", 1234567);
6845     assert(tmp == " 1,23,45,67", "'" ~ tmp ~ "'");
6846     });
6847 }
6848 
6849 @safe unittest
6850 {
6851     auto tmp = format("%,f", 1000.0);
6852     assert(tmp == "1,000.000000", "'" ~ tmp ~ "'");
6853 
6854     tmp = format("%,f", 1234567.891011);
6855     assert(tmp == "1,234,567.891011", "'" ~ tmp ~ "'");
6856 
6857     tmp = format("%,f", -1234567.891011);
6858     assert(tmp == "-1,234,567.891011", "'" ~ tmp ~ "'");
6859 
6860     tmp = format("%,2f", 1234567.891011);
6861     assert(tmp == "1,23,45,67.891011", "'" ~ tmp ~ "'");
6862 
6863     tmp = format("%18,f", 1234567.891011);
6864     assert(tmp == "  1,234,567.891011", "'" ~ tmp ~ "'");
6865 
6866     tmp = format("%18,?f", '.', 1234567.891011);
6867     assert(tmp == "  1.234.567.891011", "'" ~ tmp ~ "'");
6868 
6869     tmp = format("%,?.3f", 'ä', 1234567.891011);
6870     assert(tmp == "1ä234ä567.891", "'" ~ tmp ~ "'");
6871 
6872     tmp = format("%,*?.3f", 1, 'ä', 1234567.891011);
6873     assert(tmp == "1ä2ä3ä4ä5ä6ä7.891", "'" ~ tmp ~ "'");
6874 
6875     tmp = format("%,4?.3f", '_', 1234567.891011);
6876     assert(tmp == "123_4567.891", "'" ~ tmp ~ "'");
6877 
6878     tmp = format("%12,3.3f", 1234.5678);
6879     assert(tmp == "   1,234.568", "'" ~ tmp ~ "'");
6880 
6881     tmp = format("%,e", 3.141592653589793238462);
6882     assert(tmp == "3.141593e+00", "'" ~ tmp ~ "'");
6883 
6884     tmp = format("%15,e", 3.141592653589793238462);
6885     assert(tmp == "   3.141593e+00", "'" ~ tmp ~ "'");
6886 
6887     tmp = format("%15,e", -3.141592653589793238462);
6888     assert(tmp == "  -3.141593e+00", "'" ~ tmp ~ "'");
6889 
6890     tmp = format("%.4,*e", 2, 3.141592653589793238462);
6891     assert(tmp == "3.1416e+00", "'" ~ tmp ~ "'");
6892 
6893     tmp = format("%13.4,*e", 2, 3.141592653589793238462);
6894     assert(tmp == "   3.1416e+00", "'" ~ tmp ~ "'");
6895 
6896     tmp = format("%,.0f", 3.14);
6897     assert(tmp == "3", "'" ~ tmp ~ "'");
6898 
6899     tmp = format("%3,g", 1_000_000.123456);
6900     assert(tmp == "1e+06", "'" ~ tmp ~ "'");
6901 
6902     tmp = format("%19,?f", '.', -1234567.891011);
6903     assert(tmp == "  -1.234.567.891011", "'" ~ tmp ~ "'");
6904 }
6905 
6906 // Test for multiple indexes
6907 @safe unittest
6908 {
6909     auto tmp = format("%2:5$s", 1, 2, 3, 4, 5);
6910     assert(tmp == "2345", tmp);
6911 }
6912 
6913 // https://issues.dlang.org/show_bug.cgi?id=18047
6914 @safe unittest
6915 {
6916     auto cmp = "     123,456";
6917     assert(cmp.length == 12, format("%d", cmp.length));
6918     auto tmp = format("%12,d", 123456);
6919     assert(tmp.length == 12, format("%d", tmp.length));
6920 
6921     assert(tmp == cmp, "'" ~ tmp ~ "'");
6922 }
6923 
6924 // https://issues.dlang.org/show_bug.cgi?id=17459
6925 @safe unittest
6926 {
6927     auto cmp = "100";
6928     auto tmp  = format("%0d", 100);
6929     assert(tmp == cmp, tmp);
6930 
6931     cmp = "0100";
6932     tmp  = format("%04d", 100);
6933     assert(tmp == cmp, tmp);
6934 
6935     cmp = "0,000,000,100";
6936     tmp  = format("%012,3d", 100);
6937     assert(tmp == cmp, tmp);
6938 
6939     cmp = "0,000,001,000";
6940     tmp = format("%012,3d", 1_000);
6941     assert(tmp == cmp, tmp);
6942 
6943     cmp = "0,000,100,000";
6944     tmp = format("%012,3d", 100_000);
6945     assert(tmp == cmp, tmp);
6946 
6947     cmp = "0,001,000,000";
6948     tmp = format("%012,3d", 1_000_000);
6949     assert(tmp == cmp, tmp);
6950 
6951     cmp = "0,100,000,000";
6952     tmp = format("%012,3d", 100_000_000);
6953     assert(tmp == cmp, tmp);
6954 }
6955 
6956 // https://issues.dlang.org/show_bug.cgi?id=17459
6957 @safe unittest
6958 {
6959     auto cmp = "100,000";
6960     auto tmp  = format("%06,d", 100_000);
6961     assert(tmp == cmp, tmp);
6962 
6963     cmp = "100,000";
6964     tmp  = format("%07,d", 100_000);
6965     assert(tmp == cmp, tmp);
6966 
6967     cmp = "0,100,000";
6968     tmp  = format("%08,d", 100_000);
6969     assert(tmp == cmp, tmp);
6970 }
6971 
6972 // https://issues.dlang.org/show_bug.cgi?id=20288
6973 @safe unittest
6974 {
6975     string s = format("%,.2f", double.nan);
6976     assert(s == "nan", s);
6977 
6978     s = format("%,.2F", double.nan);
6979     assert(s == "NAN", s);
6980 
6981     s = format("%,.2f", -double.nan);
6982     assert(s == "-nan", s);
6983 
6984     s = format("%,.2F", -double.nan);
6985     assert(s == "-NAN", s);
6986 
6987     string g = format("^%13s$", "nan");
6988     string h = "^          nan$";
6989     assert(g == h, "\ngot:" ~ g ~ "\nexp:" ~ h);
6990     string a = format("^%13,3.2f$", double.nan);
6991     string b = format("^%13,3.2F$", double.nan);
6992     string c = format("^%13,3.2f$", -double.nan);
6993     string d = format("^%13,3.2F$", -double.nan);
6994     assert(a == "^          nan$", "\ngot:'"~ a ~ "'\nexp:'^          nan$'");
6995     assert(b == "^          NAN$", "\ngot:'"~ b ~ "'\nexp:'^          NAN$'");
6996     assert(c == "^         -nan$", "\ngot:'"~ c ~ "'\nexp:'^         -nan$'");
6997     assert(d == "^         -NAN$", "\ngot:'"~ d ~ "'\nexp:'^         -NAN$'");
6998 
6999     a = format("^%-13,3.2f$", double.nan);
7000     b = format("^%-13,3.2F$", double.nan);
7001     c = format("^%-13,3.2f$", -double.nan);
7002     d = format("^%-13,3.2F$", -double.nan);
7003     assert(a == "^nan          $", "\ngot:'"~ a ~ "'\nexp:'^nan          $'");
7004     assert(b == "^NAN          $", "\ngot:'"~ b ~ "'\nexp:'^NAN          $'");
7005     assert(c == "^-nan         $", "\ngot:'"~ c ~ "'\nexp:'^-nan         $'");
7006     assert(d == "^-NAN         $", "\ngot:'"~ d ~ "'\nexp:'^-NAN         $'");
7007 
7008     a = format("^%+13,3.2f$", double.nan);
7009     b = format("^%+13,3.2F$", double.nan);
7010     c = format("^%+13,3.2f$", -double.nan);
7011     d = format("^%+13,3.2F$", -double.nan);
7012     assert(a == "^         +nan$", "\ngot:'"~ a ~ "'\nexp:'^         +nan$'");
7013     assert(b == "^         +NAN$", "\ngot:'"~ b ~ "'\nexp:'^         +NAN$'");
7014     assert(c == "^         -nan$", "\ngot:'"~ c ~ "'\nexp:'^         -nan$'");
7015     assert(d == "^         -NAN$", "\ngot:'"~ d ~ "'\nexp:'^         -NAN$'");
7016 
7017     a = format("^%-+13,3.2f$", double.nan);
7018     b = format("^%-+13,3.2F$", double.nan);
7019     c = format("^%-+13,3.2f$", -double.nan);
7020     d = format("^%-+13,3.2F$", -double.nan);
7021     assert(a == "^+nan         $", "\ngot:'"~ a ~ "'\nexp:'^+nan         $'");
7022     assert(b == "^+NAN         $", "\ngot:'"~ b ~ "'\nexp:'^+NAN         $'");
7023     assert(c == "^-nan         $", "\ngot:'"~ c ~ "'\nexp:'^-nan         $'");
7024     assert(d == "^-NAN         $", "\ngot:'"~ d ~ "'\nexp:'^-NAN         $'");
7025 
7026     a = format("^%- 13,3.2f$", double.nan);
7027     b = format("^%- 13,3.2F$", double.nan);
7028     c = format("^%- 13,3.2f$", -double.nan);
7029     d = format("^%- 13,3.2F$", -double.nan);
7030     assert(a == "^ nan         $", "\ngot:'"~ a ~ "'\nexp:'^ nan         $'");
7031     assert(b == "^ NAN         $", "\ngot:'"~ b ~ "'\nexp:'^ NAN         $'");
7032     assert(c == "^-nan         $", "\ngot:'"~ c ~ "'\nexp:'^-nan         $'");
7033     assert(d == "^-NAN         $", "\ngot:'"~ d ~ "'\nexp:'^-NAN         $'");
7034 }
7035 
7036 private auto printFloat(T, Char)(return char[] buf, T val, FormatSpec!Char f,
7037                                  RoundingMode rm = RoundingMode.toNearestTiesToEven)
7038 if (is(T == float) || is(T == double) || (is(T == real) && T.mant_dig == double.mant_dig))
7039 {
7040     union FloatBits
7041     {
7042         T floatValue;
7043         ulong ulongValue;
7044     }
7045 
7046     FloatBits fb;
7047     fb.floatValue = val;
7048     ulong ival = fb.ulongValue;
7049 
7050     static if (!is(T == float))
7051     {
7052         version (DigitalMars)
7053         {
7054             // hack to work around https://issues.dlang.org/show_bug.cgi?id=20363
7055             ival ^= rm;
7056             ival ^= rm;
7057         }
7058     }
7059 
7060     import std.math : log2;
7061     enum log2_max_exp = cast(int) log2(T.max_exp);
7062 
7063     ulong mnt = ival & ((1L << (T.mant_dig - 1)) - 1);
7064     int exp = (ival >> (T.mant_dig - 1)) & ((1L << (log2_max_exp + 1)) - 1);
7065     enum maxexp = 2 * T.max_exp - 1;
7066     string sgn = (ival >> (T.mant_dig + log2_max_exp)) & 1 ? "-" : "";
7067 
7068     if (sgn == "" && f.flPlus) sgn = "+";
7069     if (sgn == "" && f.flSpace) sgn = " ";
7070 
7071     assert(f.spec == 'a' || f.spec == 'A' || f.spec == 'e' || f.spec == 'E');
7072     bool is_upper = f.spec == 'A' || f.spec == 'E';
7073 
7074     // special treatment for nan and inf
7075     if (exp == maxexp)
7076     {
7077         import std.algorithm.comparison : max;
7078 
7079         size_t length = max(f.width, sgn.length + 3);
7080         char[] result = length <= buf.length ? buf[0 .. length] : new char[length];
7081         result[] = ' ';
7082 
7083         auto offset = f.flDash ? 0 : (result.length - 3);
7084 
7085         if (sgn != "")
7086         {
7087             if (f.flDash) ++offset;
7088             result[offset-1] = sgn[0];
7089         }
7090 
7091         result[offset .. offset + 3] = (mnt == 0) ? ( is_upper ? "INF" : "inf" ) : ( is_upper ? "NAN" : "nan" );
7092 
7093         return result;
7094     }
7095 
7096     final switch (f.spec)
7097     {
7098         case 'a': case 'A':
7099             return printFloatA(buf, val, f, rm, sgn, exp, mnt, is_upper);
7100         case 'e': case 'E':
7101             return printFloatE(buf, val, f, rm, sgn, exp, mnt, is_upper);
7102     }
7103 }
7104 
7105 private auto printFloatA(T, Char)(return char[] buf, T val, FormatSpec!Char f, RoundingMode rm,
7106                                   string sgn, int exp, ulong mnt, bool is_upper)
7107 if (is(T == float) || is(T == double) || (is(T == real) && T.mant_dig == double.mant_dig))
7108 {
7109     import std.algorithm.comparison : max;
7110 
7111     enum char[16] alpha = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'];
7112     enum char[16] Alpha = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'];
7113 
7114     enum int bias = T.max_exp - 1;
7115 
7116     static if (is(T == float))
7117     {
7118         mnt <<= 1; // make mnt dividable by 4
7119         enum mant_len = T.mant_dig;
7120     }
7121     else
7122         enum mant_len = T.mant_dig - 1;
7123     static assert(mant_len % 4 == 0);
7124 
7125     // print full mantissa
7126     char[(mant_len - 1) / 4 + 1] hex_mant;
7127     size_t hex_mant_pos = 0;
7128     size_t pos = mant_len;
7129 
7130     while (pos >= 4 && (mnt & ((1L << pos) - 1)) != 0)
7131     {
7132         pos -= 4;
7133         size_t tmp = (mnt >> pos) & 15;
7134         hex_mant[hex_mant_pos++] = is_upper ? Alpha[tmp] : alpha[tmp];
7135     }
7136 
7137     // save integer part
7138     auto first = exp == 0 ? '0' : '1';
7139 
7140     // print exponent
7141     if (exp == 0 && mnt == 0)
7142         exp = 0; // special treatment for 0.0
7143     else if (exp == 0)
7144         exp = 1 - bias; // denormalized number
7145     else
7146         exp -= bias;
7147 
7148     auto exp_sgn = exp >= 0 ? '+' : '-';
7149     if (exp < 0) exp = -exp;
7150 
7151     static if (is(T == float))
7152         enum max_exp_digits = 4;
7153     else
7154         enum max_exp_digits = 5;
7155 
7156     char[max_exp_digits] exp_str;
7157     size_t exp_pos = max_exp_digits;
7158 
7159     do
7160     {
7161         exp_str[--exp_pos] = '0' + exp%10;
7162         exp /= 10;
7163     } while (exp>0);
7164 
7165     exp_str[--exp_pos] = exp_sgn;
7166 
7167     // calculate needed buffer width
7168     auto precision = f.precision == f.UNSPECIFIED ? hex_mant_pos : f.precision;
7169     bool dot = precision > 0 || f.flHash;
7170 
7171     size_t width = sgn.length + 3 + (dot ? 1 : 0) + precision + 1 + (max_exp_digits - exp_pos);
7172 
7173     size_t length = max(width,f.width);
7174     char[] buffer = length <= buf.length ? buf[0 .. length] : new char[length];
7175     size_t b_pos = 0;
7176 
7177     size_t delta = f.width - width; // only used, when f.width > width
7178 
7179     // fill buffer
7180     if (!f.flDash && !f.flZero && f.width > width)
7181     {
7182         buffer[b_pos .. b_pos + delta] = ' ';
7183         b_pos += delta;
7184     }
7185 
7186     if (sgn != "") buffer[b_pos++] = sgn[0];
7187     buffer[b_pos++] = '0';
7188     buffer[b_pos++] = is_upper ? 'X' : 'x';
7189 
7190     if (!f.flDash && f.flZero && f.width > width)
7191     {
7192         buffer[b_pos .. b_pos + delta] = '0';
7193         b_pos += delta;
7194     }
7195 
7196     buffer[b_pos++] = first;
7197     if (dot) buffer[b_pos++] = '.';
7198     if (precision < hex_mant_pos)
7199     {
7200         buffer[b_pos .. b_pos + precision] = hex_mant[0 .. precision];
7201         b_pos += precision;
7202 
7203         enum roundType { ZERO, LOWER, FIVE, UPPER }
7204         roundType next;
7205 
7206         if (hex_mant[precision] == '0')
7207             next = roundType.ZERO;
7208         else if (hex_mant[precision] < '8')
7209             next = roundType.LOWER;
7210         else if (hex_mant[precision] > '8')
7211             next = roundType.UPPER;
7212         else
7213             next = roundType.FIVE;
7214 
7215         if (next == roundType.ZERO || next == roundType.FIVE)
7216         {
7217             foreach (i;precision + 1 .. hex_mant_pos)
7218             {
7219                 if (hex_mant[i] > '0')
7220                 {
7221                     next = next == roundType.ZERO ? roundType.LOWER : roundType.UPPER;
7222                     break;
7223                 }
7224             }
7225         }
7226 
7227         bool roundUp = false;
7228 
7229         if (rm == RoundingMode.up)
7230             roundUp = next != roundType.ZERO && sgn != "-";
7231         else if (rm == RoundingMode.down)
7232             roundUp = next != roundType.ZERO && sgn == "-";
7233         else if (rm == RoundingMode.toZero)
7234             roundUp = false;
7235         else
7236         {
7237             assert(rm == RoundingMode.toNearestTiesToEven || rm == RoundingMode.toNearestTiesAwayFromZero);
7238             roundUp = next == roundType.UPPER;
7239 
7240             if (next == roundType.FIVE)
7241             {
7242                 // IEEE754 allows for two different ways of implementing roundToNearest:
7243                 //
7244                 // Round to nearest, ties away from zero
7245                 if (rm == RoundingMode.toNearestTiesAwayFromZero)
7246                     roundUp = true;
7247                 else
7248                 {
7249                     // Round to nearest, ties to even
7250                     auto last = buffer[b_pos-1];
7251                     if (last == '.') last = buffer[b_pos-2];
7252                     roundUp = (last <= '9' && last % 2 != 0) || (last >= '9' && last % 2 == 0);
7253                 }
7254             }
7255         }
7256 
7257         if (roundUp)
7258         {
7259             foreach_reverse (i;b_pos - precision - 2 .. b_pos)
7260             {
7261                 if (buffer[i] == '.') continue;
7262                 if (buffer[i] == 'f' || buffer[i] == 'F')
7263                     buffer[i] = '0';
7264                 else
7265                 {
7266                     if (buffer[i] == '9')
7267                         buffer[i] = is_upper ? 'A' : 'a';
7268                     else
7269                         buffer[i]++;
7270                     break;
7271                 }
7272             }
7273         }
7274     }
7275     else
7276     {
7277         buffer[b_pos .. b_pos + hex_mant_pos] = hex_mant[0 .. hex_mant_pos];
7278         buffer[b_pos + hex_mant_pos .. b_pos + precision] = '0';
7279         b_pos += precision;
7280     }
7281 
7282     buffer[b_pos++] = is_upper ? 'P' : 'p';
7283     buffer[b_pos .. b_pos + (max_exp_digits - exp_pos)] = exp_str[exp_pos .. $];
7284     b_pos += max_exp_digits - exp_pos;
7285 
7286     if (f.flDash && f.width > width)
7287     {
7288         buffer[b_pos .. b_pos + delta] = ' ';
7289         b_pos += delta;
7290     }
7291 
7292     return buffer[0 .. b_pos];
7293 }
7294 
7295 @safe unittest
7296 {
7297     auto f = FormatSpec!dchar("");
7298     f.spec = 'a';
7299     char[256] buf;
7300     assert(printFloat(buf[], float.nan, f) == "nan");
7301     assert(printFloat(buf[], -float.nan, f) == "-nan");
7302     assert(printFloat(buf[], float.infinity, f) == "inf");
7303     assert(printFloat(buf[], -float.infinity, f) == "-inf");
7304     assert(printFloat(buf[], 0.0f, f) == "0x0p+0");
7305     assert(printFloat(buf[], -0.0f, f) == "-0x0p+0");
7306 
7307     assert(printFloat(buf[], double.nan, f) == "nan");
7308     assert(printFloat(buf[], -double.nan, f) == "-nan");
7309     assert(printFloat(buf[], double.infinity, f) == "inf");
7310     assert(printFloat(buf[], -double.infinity, f) == "-inf");
7311     assert(printFloat(buf[], 0.0, f) == "0x0p+0");
7312     assert(printFloat(buf[], -0.0, f) == "-0x0p+0");
7313 
7314     import std.math : nextUp;
7315 
7316     assert(printFloat(buf[], nextUp(0.0f), f) == "0x0.000002p-126");
7317     assert(printFloat(buf[], float.epsilon, f) == "0x1p-23");
7318     assert(printFloat(buf[], float.min_normal, f) == "0x1p-126");
7319     assert(printFloat(buf[], float.max, f) == "0x1.fffffep+127");
7320 
7321     assert(printFloat(buf[], nextUp(0.0), f) == "0x0.0000000000001p-1022");
7322     assert(printFloat(buf[], double.epsilon, f) == "0x1p-52");
7323     assert(printFloat(buf[], double.min_normal, f) == "0x1p-1022");
7324     assert(printFloat(buf[], double.max, f) == "0x1.fffffffffffffp+1023");
7325 
7326     import std.math : E, PI, PI_2, PI_4, M_1_PI, M_2_PI, M_2_SQRTPI,
7327                       LN10, LN2, LOG2, LOG2E, LOG2T, LOG10E, SQRT2, SQRT1_2;
7328 
7329     assert(printFloat(buf[], cast(float) E, f) == "0x1.5bf0a8p+1");
7330     assert(printFloat(buf[], cast(float) PI, f) == "0x1.921fb6p+1");
7331     assert(printFloat(buf[], cast(float) PI_2, f) == "0x1.921fb6p+0");
7332     assert(printFloat(buf[], cast(float) PI_4, f) == "0x1.921fb6p-1");
7333     assert(printFloat(buf[], cast(float) M_1_PI, f) == "0x1.45f306p-2");
7334     assert(printFloat(buf[], cast(float) M_2_PI, f) == "0x1.45f306p-1");
7335     assert(printFloat(buf[], cast(float) M_2_SQRTPI, f) == "0x1.20dd76p+0");
7336     assert(printFloat(buf[], cast(float) LN10, f) == "0x1.26bb1cp+1");
7337     assert(printFloat(buf[], cast(float) LN2, f) == "0x1.62e43p-1");
7338     assert(printFloat(buf[], cast(float) LOG2, f) == "0x1.344136p-2");
7339     assert(printFloat(buf[], cast(float) LOG2E, f) == "0x1.715476p+0");
7340     assert(printFloat(buf[], cast(float) LOG2T, f) == "0x1.a934fp+1");
7341     assert(printFloat(buf[], cast(float) LOG10E, f) == "0x1.bcb7b2p-2");
7342     assert(printFloat(buf[], cast(float) SQRT2, f) == "0x1.6a09e6p+0");
7343     assert(printFloat(buf[], cast(float) SQRT1_2, f) == "0x1.6a09e6p-1");
7344 
7345     assert(printFloat(buf[], cast(double) E, f) == "0x1.5bf0a8b145769p+1");
7346     assert(printFloat(buf[], cast(double) PI, f) == "0x1.921fb54442d18p+1");
7347     assert(printFloat(buf[], cast(double) PI_2, f) == "0x1.921fb54442d18p+0");
7348     assert(printFloat(buf[], cast(double) PI_4, f) == "0x1.921fb54442d18p-1");
7349     assert(printFloat(buf[], cast(double) M_1_PI, f) == "0x1.45f306dc9c883p-2");
7350     assert(printFloat(buf[], cast(double) M_2_PI, f) == "0x1.45f306dc9c883p-1");
7351     assert(printFloat(buf[], cast(double) M_2_SQRTPI, f) == "0x1.20dd750429b6dp+0");
7352     assert(printFloat(buf[], cast(double) LN10, f) == "0x1.26bb1bbb55516p+1");
7353     assert(printFloat(buf[], cast(double) LN2, f) == "0x1.62e42fefa39efp-1");
7354     assert(printFloat(buf[], cast(double) LOG2, f) == "0x1.34413509f79ffp-2");
7355     assert(printFloat(buf[], cast(double) LOG2E, f) == "0x1.71547652b82fep+0");
7356     assert(printFloat(buf[], cast(double) LOG2T, f) == "0x1.a934f0979a371p+1");
7357     assert(printFloat(buf[], cast(double) LOG10E, f) == "0x1.bcb7b1526e50ep-2");
7358     assert(printFloat(buf[], cast(double) SQRT2, f) == "0x1.6a09e667f3bcdp+0");
7359     assert(printFloat(buf[], cast(double) SQRT1_2, f) == "0x1.6a09e667f3bcdp-1");
7360 
7361 }
7362 
7363 @safe unittest
7364 {
7365     auto f = FormatSpec!dchar("");
7366     f.spec = 'a';
7367     f.precision = 3;
7368     char[32] buf;
7369 
7370     assert(printFloat(buf[], 1.0f, f) == "0x1.000p+0");
7371     assert(printFloat(buf[], 3.3f, f) == "0x1.a66p+1");
7372     assert(printFloat(buf[], 2.9f, f) == "0x1.733p+1");
7373 
7374     assert(printFloat(buf[], 1.0, f) == "0x1.000p+0");
7375     assert(printFloat(buf[], 3.3, f) == "0x1.a66p+1");
7376     assert(printFloat(buf[], 2.9, f) == "0x1.733p+1");
7377 }
7378 
7379 @safe unittest
7380 {
7381     auto f = FormatSpec!dchar("");
7382     f.spec = 'a';
7383     f.precision = 0;
7384     char[32] buf;
7385 
7386     assert(printFloat(buf[], 1.0f, f) == "0x1p+0");
7387     assert(printFloat(buf[], 3.3f, f) == "0x2p+1");
7388     assert(printFloat(buf[], 2.9f, f) == "0x1p+1");
7389 
7390     assert(printFloat(buf[], 1.0, f) == "0x1p+0");
7391     assert(printFloat(buf[], 3.3, f) == "0x2p+1");
7392     assert(printFloat(buf[], 2.9, f) == "0x1p+1");
7393 }
7394 
7395 @safe unittest
7396 {
7397     auto f = FormatSpec!dchar("");
7398     f.spec = 'a';
7399     f.precision = 0;
7400     f.flHash = true;
7401     char[32] buf;
7402 
7403     assert(printFloat(buf[], 1.0f, f) == "0x1.p+0");
7404     assert(printFloat(buf[], 3.3f, f) == "0x2.p+1");
7405     assert(printFloat(buf[], 2.9f, f) == "0x1.p+1");
7406 
7407     assert(printFloat(buf[], 1.0, f) == "0x1.p+0");
7408     assert(printFloat(buf[], 3.3, f) == "0x2.p+1");
7409     assert(printFloat(buf[], 2.9, f) == "0x1.p+1");
7410 }
7411 
7412 @safe unittest
7413 {
7414     auto f = FormatSpec!dchar("");
7415     f.spec = 'a';
7416     f.width = 22;
7417     char[32] buf;
7418 
7419     assert(printFloat(buf[], 1.0f, f) == "                0x1p+0");
7420     assert(printFloat(buf[], 3.3f, f) == "         0x1.a66666p+1");
7421     assert(printFloat(buf[], 2.9f, f) == "         0x1.733334p+1");
7422 
7423     assert(printFloat(buf[], 1.0, f) == "                0x1p+0");
7424     assert(printFloat(buf[], 3.3, f) == "  0x1.a666666666666p+1");
7425     assert(printFloat(buf[], 2.9, f) == "  0x1.7333333333333p+1");
7426 }
7427 
7428 @safe unittest
7429 {
7430     auto f = FormatSpec!dchar("");
7431     f.spec = 'a';
7432     f.width = 22;
7433     f.flDash = true;
7434     char[32] buf;
7435 
7436     assert(printFloat(buf[], 1.0f, f) == "0x1p+0                ");
7437     assert(printFloat(buf[], 3.3f, f) == "0x1.a66666p+1         ");
7438     assert(printFloat(buf[], 2.9f, f) == "0x1.733334p+1         ");
7439 
7440     assert(printFloat(buf[], 1.0, f) == "0x1p+0                ");
7441     assert(printFloat(buf[], 3.3, f) == "0x1.a666666666666p+1  ");
7442     assert(printFloat(buf[], 2.9, f) == "0x1.7333333333333p+1  ");
7443 }
7444 
7445 @safe unittest
7446 {
7447     auto f = FormatSpec!dchar("");
7448     f.spec = 'a';
7449     f.width = 22;
7450     f.flZero = true;
7451     char[32] buf;
7452 
7453     assert(printFloat(buf[], 1.0f, f) == "0x00000000000000001p+0");
7454     assert(printFloat(buf[], 3.3f, f) == "0x0000000001.a66666p+1");
7455     assert(printFloat(buf[], 2.9f, f) == "0x0000000001.733334p+1");
7456 
7457     assert(printFloat(buf[], 1.0, f) == "0x00000000000000001p+0");
7458     assert(printFloat(buf[], 3.3, f) == "0x001.a666666666666p+1");
7459     assert(printFloat(buf[], 2.9, f) == "0x001.7333333333333p+1");
7460 }
7461 
7462 @safe unittest
7463 {
7464     auto f = FormatSpec!dchar("");
7465     f.spec = 'a';
7466     f.width = 22;
7467     f.flPlus = true;
7468     char[32] buf;
7469 
7470     assert(printFloat(buf[], 1.0f, f) == "               +0x1p+0");
7471     assert(printFloat(buf[], 3.3f, f) == "        +0x1.a66666p+1");
7472     assert(printFloat(buf[], 2.9f, f) == "        +0x1.733334p+1");
7473 
7474     assert(printFloat(buf[], 1.0, f) == "               +0x1p+0");
7475     assert(printFloat(buf[], 3.3, f) == " +0x1.a666666666666p+1");
7476     assert(printFloat(buf[], 2.9, f) == " +0x1.7333333333333p+1");
7477 }
7478 
7479 @safe unittest
7480 {
7481     auto f = FormatSpec!dchar("");
7482     f.spec = 'a';
7483     f.width = 22;
7484     f.flDash = true;
7485     f.flSpace = true;
7486     char[32] buf;
7487 
7488     assert(printFloat(buf[], 1.0f, f) == " 0x1p+0               ");
7489     assert(printFloat(buf[], 3.3f, f) == " 0x1.a66666p+1        ");
7490     assert(printFloat(buf[], 2.9f, f) == " 0x1.733334p+1        ");
7491 
7492     assert(printFloat(buf[], 1.0, f) == " 0x1p+0               ");
7493     assert(printFloat(buf[], 3.3, f) == " 0x1.a666666666666p+1 ");
7494     assert(printFloat(buf[], 2.9, f) == " 0x1.7333333333333p+1 ");
7495 }
7496 
7497 @safe unittest
7498 {
7499     auto f = FormatSpec!dchar("");
7500     f.spec = 'a';
7501     f.precision = 1;
7502     char[32] buf;
7503 
7504     assert(printFloat(buf[], 0x1.18p0,  f, RoundingMode.toNearestTiesAwayFromZero) == "0x1.2p+0");
7505     assert(printFloat(buf[], 0x1.28p0,  f, RoundingMode.toNearestTiesAwayFromZero) == "0x1.3p+0");
7506     assert(printFloat(buf[], 0x1.1ap0,  f, RoundingMode.toNearestTiesAwayFromZero) == "0x1.2p+0");
7507     assert(printFloat(buf[], 0x1.16p0,  f, RoundingMode.toNearestTiesAwayFromZero) == "0x1.1p+0");
7508     assert(printFloat(buf[], 0x1.10p0,  f, RoundingMode.toNearestTiesAwayFromZero) == "0x1.1p+0");
7509     assert(printFloat(buf[], -0x1.18p0, f, RoundingMode.toNearestTiesAwayFromZero) == "-0x1.2p+0");
7510     assert(printFloat(buf[], -0x1.28p0, f, RoundingMode.toNearestTiesAwayFromZero) == "-0x1.3p+0");
7511     assert(printFloat(buf[], -0x1.1ap0, f, RoundingMode.toNearestTiesAwayFromZero) == "-0x1.2p+0");
7512     assert(printFloat(buf[], -0x1.16p0, f, RoundingMode.toNearestTiesAwayFromZero) == "-0x1.1p+0");
7513     assert(printFloat(buf[], -0x1.10p0, f, RoundingMode.toNearestTiesAwayFromZero) == "-0x1.1p+0");
7514 
7515     assert(printFloat(buf[], 0x1.18p0,  f) == "0x1.2p+0");
7516     assert(printFloat(buf[], 0x1.28p0,  f) == "0x1.2p+0");
7517     assert(printFloat(buf[], 0x1.1ap0,  f) == "0x1.2p+0");
7518     assert(printFloat(buf[], 0x1.16p0,  f) == "0x1.1p+0");
7519     assert(printFloat(buf[], 0x1.10p0,  f) == "0x1.1p+0");
7520     assert(printFloat(buf[], -0x1.18p0, f) == "-0x1.2p+0");
7521     assert(printFloat(buf[], -0x1.28p0, f) == "-0x1.2p+0");
7522     assert(printFloat(buf[], -0x1.1ap0, f) == "-0x1.2p+0");
7523     assert(printFloat(buf[], -0x1.16p0, f) == "-0x1.1p+0");
7524     assert(printFloat(buf[], -0x1.10p0, f) == "-0x1.1p+0");
7525 
7526     assert(printFloat(buf[], 0x1.18p0,  f, RoundingMode.toZero) == "0x1.1p+0");
7527     assert(printFloat(buf[], 0x1.28p0,  f, RoundingMode.toZero) == "0x1.2p+0");
7528     assert(printFloat(buf[], 0x1.1ap0,  f, RoundingMode.toZero) == "0x1.1p+0");
7529     assert(printFloat(buf[], 0x1.16p0,  f, RoundingMode.toZero) == "0x1.1p+0");
7530     assert(printFloat(buf[], 0x1.10p0,  f, RoundingMode.toZero) == "0x1.1p+0");
7531     assert(printFloat(buf[], -0x1.18p0, f, RoundingMode.toZero) == "-0x1.1p+0");
7532     assert(printFloat(buf[], -0x1.28p0, f, RoundingMode.toZero) == "-0x1.2p+0");
7533     assert(printFloat(buf[], -0x1.1ap0, f, RoundingMode.toZero) == "-0x1.1p+0");
7534     assert(printFloat(buf[], -0x1.16p0, f, RoundingMode.toZero) == "-0x1.1p+0");
7535     assert(printFloat(buf[], -0x1.10p0, f, RoundingMode.toZero) == "-0x1.1p+0");
7536 
7537     assert(printFloat(buf[], 0x1.18p0,  f, RoundingMode.up) == "0x1.2p+0");
7538     assert(printFloat(buf[], 0x1.28p0,  f, RoundingMode.up) == "0x1.3p+0");
7539     assert(printFloat(buf[], 0x1.1ap0,  f, RoundingMode.up) == "0x1.2p+0");
7540     assert(printFloat(buf[], 0x1.16p0,  f, RoundingMode.up) == "0x1.2p+0");
7541     assert(printFloat(buf[], 0x1.10p0,  f, RoundingMode.up) == "0x1.1p+0");
7542     assert(printFloat(buf[], -0x1.18p0, f, RoundingMode.up) == "-0x1.1p+0");
7543     assert(printFloat(buf[], -0x1.28p0, f, RoundingMode.up) == "-0x1.2p+0");
7544     assert(printFloat(buf[], -0x1.1ap0, f, RoundingMode.up) == "-0x1.1p+0");
7545     assert(printFloat(buf[], -0x1.16p0, f, RoundingMode.up) == "-0x1.1p+0");
7546     assert(printFloat(buf[], -0x1.10p0, f, RoundingMode.up) == "-0x1.1p+0");
7547 
7548     assert(printFloat(buf[], 0x1.18p0,  f, RoundingMode.down) == "0x1.1p+0");
7549     assert(printFloat(buf[], 0x1.28p0,  f, RoundingMode.down) == "0x1.2p+0");
7550     assert(printFloat(buf[], 0x1.1ap0,  f, RoundingMode.down) == "0x1.1p+0");
7551     assert(printFloat(buf[], 0x1.16p0,  f, RoundingMode.down) == "0x1.1p+0");
7552     assert(printFloat(buf[], 0x1.10p0,  f, RoundingMode.down) == "0x1.1p+0");
7553     assert(printFloat(buf[], -0x1.18p0, f, RoundingMode.down) == "-0x1.2p+0");
7554     assert(printFloat(buf[], -0x1.28p0, f, RoundingMode.down) == "-0x1.3p+0");
7555     assert(printFloat(buf[], -0x1.1ap0, f, RoundingMode.down) == "-0x1.2p+0");
7556     assert(printFloat(buf[], -0x1.16p0, f, RoundingMode.down) == "-0x1.2p+0");
7557     assert(printFloat(buf[], -0x1.10p0, f, RoundingMode.down) == "-0x1.1p+0");
7558 }
7559 
7560 // for 100% coverage
7561 @safe unittest
7562 {
7563     auto f = FormatSpec!dchar("");
7564     f.spec = 'a';
7565     f.precision = 3;
7566     char[32] buf;
7567 
7568     assert(printFloat(buf[], 0x1.19f81p0, f) == "0x1.1a0p+0");
7569     assert(printFloat(buf[], 0x1.19f01p0, f) == "0x1.19fp+0");
7570 }
7571 
7572 @safe unittest
7573 {
7574     auto f = FormatSpec!dchar("");
7575     f.spec = 'A';
7576     f.precision = 3;
7577     char[32] buf;
7578 
7579     assert(printFloat(buf[], 0x1.19f81p0, f) == "0X1.1A0P+0");
7580     assert(printFloat(buf[], 0x1.19f01p0, f) == "0X1.19FP+0");
7581 }
7582 
7583 private auto printFloatE(T, Char)(return char[] buf, T val, FormatSpec!Char f, RoundingMode rm,
7584                                   string sgn, int exp, ulong mnt, bool is_upper)
7585 if (is(T == float) || is(T == double) || (is(T == real) && T.mant_dig == double.mant_dig))
7586 {
7587     import std.conv : to;
7588     import std.algorithm.comparison : max;
7589 
7590     enum int bias = T.max_exp - 1;
7591 
7592     if (f.precision == f.UNSPECIFIED)
7593         f.precision = 6;
7594 
7595     // special treatment for 0.0
7596     if (exp == 0 && mnt == 0)
7597     {
7598         size_t length = max(f.width, f.precision + ((f.precision == 0 && !f.flHash) ? 1 : 2) + sgn.length + 4);
7599         char[] result = length <= buf.length ? buf[0 .. length] : new char[length];
7600         result[] = '0';
7601 
7602         if (f.flDash)
7603         {
7604             if (sgn != "")
7605                 result[0] = sgn[0];
7606 
7607             int dot_pos = cast(int) (sgn.length + 1);
7608             if (f.precision > 0 || f.flHash)
7609                 result[dot_pos] = '.';
7610 
7611             auto exp_start = dot_pos + ((f.precision > 0 || f.flHash) ? 1 : 0) + f.precision;
7612             if (exp_start + 4 < result.length)
7613                 result[exp_start + 4 .. $] = ' ';
7614 
7615             result[exp_start] = is_upper?'E':'e';
7616             result[exp_start + 1] = '+';
7617         }
7618         else
7619         {
7620             int sign_pos = cast(int) (result.length - 6);
7621             if (f.precision > 0 || f.flHash)
7622             {
7623                 int dot_pos = cast(int) (result.length - f.precision - 5);
7624                 result[dot_pos] = '.';
7625                 sign_pos = dot_pos - 2;
7626             }
7627 
7628             if (f.flZero)
7629                 sign_pos = 0;
7630             else if (sign_pos > 0)
7631                 result[0 .. sign_pos + (sgn.length == 0 ? 1 : 0)] = ' ';
7632 
7633             if (sgn != "")
7634                 result[sign_pos] = sgn[0];
7635 
7636             result[$ - 3] = '+';
7637             result[$ - 4] = is_upper ? 'E' : 'e';
7638         }
7639 
7640         return result;
7641     }
7642 
7643     // add leading 1 for normalized values or correct exponent for denormalied values
7644     if (exp != 0)
7645         mnt |= 1L << (T.mant_dig - 1);
7646     else
7647         exp = 1;
7648     exp -= bias;
7649 
7650     // estimate the number of bytes needed left and right of the decimal point
7651     // the speed of the algorithm depends on being as accurate as possible with
7652     // this estimate
7653 
7654     // Default for the right side is the number of digits given by f.precision plus one for the dot
7655     // plus 6 more for e+...
7656     auto max_right = f.precision + 7;
7657 
7658     // If the exponent is <= 0 there is only the sign and one digit left of the dot else
7659     // we have to estimate the number of digits. The factor between exp, which is the number of
7660     // digits in binary system and the searched number is log_2(10). We round this down to 3.32 to
7661     // get a conservative estimate. We need to add 3, because of the sign, the fact, that the
7662     // logarithm is one to small and because we need to round up instead of down, which to!int does.
7663     // And then we might need one more digit in case of a rounding overflow.
7664     auto max_left = exp>0 ? to!int(exp / 3.32) + 4 : 3;
7665 
7666     // If the result is not left justified, we may need to add more digits here for getting the
7667     // correct width.
7668     if (!f.flDash)
7669         max_left = max(max_left, f.width - max_right + max_left + 1);
7670 
7671     // If the result is left justified, we may need to add more digits to the right. This strongly
7672     // depends, on the exponent, see above. This time, we need to be conservative in the other direction
7673     // for not missing a digit; therefore we round log_2(10) up to 3.33.
7674     if (exp > 0 && f.flDash)
7675         max_right = max(max_right, f.width - to!int(exp / 3.33) - 2);
7676     else if (f.flDash)
7677         max_right = max(max_right, f.width);
7678 
7679 
7680     size_t length = max_left + max_right;
7681     char[] buffer = length <= buf.length ? buf[0 .. length] : new char[length];
7682     size_t start = max_left;
7683     size_t left = max_left;
7684     size_t right = max_left;
7685 
7686     int final_exp = 0;
7687 
7688     enum roundType { ZERO, LOWER, FIVE, UPPER }
7689     roundType next;
7690 
7691     // Depending on exp, we will use one of three algorithms:
7692     //
7693     // Algorithm A: For large exponents (exp >= T.mant_dig)
7694     // Algorithm B: For small exponents (exp < T.mant_dig - 61)
7695     // Algorithm C: For exponents close to 0.
7696     //
7697     // Algorithm A:
7698     //   The number to print looks like this: mantissa followed by several zeros.
7699     //
7700     //   We know, that there is no fractional part, so we can just use integer division,
7701     //   consecutivly dividing by 10 and writing down the remainder from right to left.
7702     //   Unfortunately the integer is too large to fit in an ulong, so we use something
7703     //   like BigInt: An array of ulongs. We only use 60 bits of that ulongs, because
7704     //   this simplifies (and speeds up) the division to come.
7705     //
7706     //   For the division we use integer division with reminder for each ulong and put
7707     //   the reminder of each step in the first 4 bits of ulong of the next step (think of
7708     //   long division for the rationale behind this). The final reminder is the next
7709     //   digit (from right to left).
7710     //
7711     //   This results in the output we would have for the %f specifier. We now adjust this
7712     //   for %e: First we calculate the place, where the exponent should be printed, filling
7713     //   up with zeros if needed and second we move the leftmost digit one to the left
7714     //   and inserting a dot.
7715     //
7716     //   After that we decide on the rounding type, using the digits right of the position,
7717     //   where the exponent will be printed (currently they are still there, but will be
7718     //   overwritten later).
7719     //
7720     // Algorithm B:
7721     //   The number to print looks like this: zero dot several zeros followed by the mantissa
7722     //
7723     //   We know, that the number has no integer part. The algorithm consecutivly multiplies
7724     //   by 10. The integer part (rounded down) after the multiplication is the next digit
7725     //   (from left to right). This integer part is removed after each step.
7726     //   Again, the number is represented as an array of ulongs, with only 60 bits used of
7727     //   every ulong.
7728     //
7729     //   For the multiplication we use normal integer multiplication, which can result in digits
7730     //   in the uppermost 4 bits. These 4 digits are the carry which is added to the result
7731     //   of the next multiplication and finally the last carry is the next digit.
7732     //
7733     //   Other than for the %f specifier, this multiplication is splitted into two almost
7734     //   identical parts. The first part lasts as long as we find zeros. We need to do this
7735     //   to calculate the correct exponent.
7736     //
7737     //   The second part will stop, when only zeros remain or when we've got enough digits
7738     //   for the requested precision. In the second case, we have to find out, which rounding
7739     //   we have. Aside from special cases we do this by calculating one more digit.
7740     //
7741     // Algorithm C:
7742     //   This time, we know, that the integral part and the fractional part each fit into a
7743     //   ulong. The mantissa might be partially in both parts or completely in the fractional
7744     //   part.
7745     //
7746     //   We first calculate the integral part by consecutive division by 10. Depending on the
7747     //   precision this might result in more digits, than we need. In that case we calculate
7748     //   the position of the exponent and the rounding type.
7749     //
7750     //   If there is no integral part, we need to find the first non zero digit. We do this by
7751     //   consecutive multiplication by 10, saving the first non zero digit followed by a dot.
7752     //
7753     //   In either case, we continue filling up with the fractional part until we have enough
7754     //   digits. If still necessary, we decide the rounding type, mainly by looking at the
7755     //   next digit.
7756 
7757     ulong[4] bigbuf;
7758     if (exp >= T.mant_dig)
7759     {
7760         // large number without fractional digits
7761         //
7762         // As this number does not fit in a ulong, we use an array of ulongs. We only use 60 of the 64 bits,
7763         // because this makes it much more easy to implement the division by 10.
7764         int count = exp / 60 + 1;
7765 
7766         // saved in big endian format
7767         ulong[] mybig = count <= bigbuf.length ? bigbuf[0 .. count] : new ulong[count];
7768 
7769         // only the first or the first two ulongs contain the mantiassa. The rest are zeros.
7770         int lower = 60 - (exp - T.mant_dig + 1) % 60;
7771         if (lower < T.mant_dig)
7772         {
7773             mybig[0] = mnt >> lower;
7774             mybig[1] = (mnt & ((1L << lower) - 1)) << 60 - lower;
7775         }
7776         else
7777             mybig[0] = (mnt & ((1L << lower) - 1)) << 60 - lower;
7778 
7779         // Generation of digits by consecutive division with reminder by 10.
7780         int msu = 0; // Most significant ulong; when it get's zero, we can ignore it further on
7781         while (msu < count - 1 || mybig[$-1] != 0)
7782         {
7783             ulong mod = 0;
7784             foreach (i;msu .. count)
7785             {
7786                 mybig[i] |= mod << 60;
7787                 mod = mybig[i] % 10;
7788                 mybig[i] /= 10;
7789             }
7790             if (mybig[msu] == 0)
7791                 ++msu;
7792 
7793             buffer[--left] = cast(byte) ('0' + mod);
7794             ++final_exp;
7795         }
7796         --final_exp;
7797 
7798         start = left + f.precision + 1;
7799 
7800         // we need more zeros for precision
7801         if (right < start)
7802             buffer[right .. start] = '0';
7803 
7804         // move leftmost digit one more left and add dot between
7805         buffer[left - 1] = buffer[left];
7806         buffer[left] = '.';
7807         --left;
7808 
7809         // rounding type
7810         if (start >= right)
7811             next = roundType.ZERO;
7812         else if (buffer[start] != '0' && buffer[start] != '5')
7813             next = buffer[start] > '5' ? roundType.UPPER : roundType.LOWER;
7814         else
7815         {
7816             next = buffer[start]=='5' ? roundType.FIVE : roundType.ZERO;
7817             foreach (i; start + 1 .. right)
7818                 if (buffer[i] > '0')
7819                 {
7820                     next = next == roundType.FIVE ? roundType.UPPER : roundType.LOWER;
7821                     break;
7822                 }
7823         }
7824 
7825         right = start;
7826         if (f.precision == 0 && !f.flHash) --right;
7827     }
7828     else if (exp + 61 < T.mant_dig)
7829     {
7830         // small number without integer digits
7831         //
7832         // Again this number does not fit in a ulong and we use an array of ulongs. And again we
7833         // only use 60 bits, because this simplifies the multiplication by 10.
7834         int count = (T.mant_dig - exp - 2) / 60 + 1;
7835 
7836         // saved in little endian format
7837         ulong[] mybig = count <= bigbuf.length ? bigbuf[0 .. count] : new ulong[count];
7838 
7839         // only the last or the last two ulongs contain the mantiassa. Because of little endian
7840         // format these are the ulongs at index 0 and 1. The rest are zeros.
7841         int upper = 60 - (-exp - 1) % 60;
7842         if (upper < T.mant_dig)
7843         {
7844             mybig[0] = (mnt & ((1L << (T.mant_dig - upper)) - 1)) << 60 - (T.mant_dig - upper);
7845             mybig[1] = mnt >> (T.mant_dig - upper);
7846         }
7847         else
7848             mybig[0] = mnt << (upper - T.mant_dig);
7849 
7850         int lsu = 0; // Least significant ulong; when it get's zero, we can ignore it further on
7851 
7852         // adding zeros, until we reach first nonzero
7853         while (lsu < count - 1 || mybig[$ - 1]!=0)
7854         {
7855             ulong over = 0;
7856             foreach (i; lsu .. count)
7857             {
7858                 mybig[i] = mybig[i] * 10 + over;
7859                 over = mybig[i] >> 60;
7860                 mybig[i] &= (1L << 60) - 1;
7861             }
7862             if (mybig[lsu] == 0)
7863                 ++lsu;
7864             --final_exp;
7865 
7866             if (over != 0)
7867             {
7868                 buffer[right++] = cast(byte) ('0' + over);
7869                 buffer[right++] = '.';
7870                 break;
7871             }
7872         }
7873 
7874         if (f.precision == 0 && !f.flHash) --right;
7875 
7876         // adding more digits
7877         start = right;
7878         while ((lsu < count - 1 || mybig[$ - 1] != 0) && right - start < f.precision)
7879         {
7880             ulong over = 0;
7881             foreach (i;lsu .. count)
7882             {
7883                 mybig[i] = mybig[i] * 10 + over;
7884                 over = mybig[i] >> 60;
7885                 mybig[i] &= (1L << 60) - 1;
7886             }
7887             if (mybig[lsu] == 0)
7888                 ++lsu;
7889 
7890             buffer[right++] = cast(byte) ('0' + over);
7891         }
7892 
7893         // filling up with zeros to match precision
7894         if (right < start + f.precision)
7895         {
7896             buffer[right .. start + f.precision] = '0';
7897             right = start + f.precision;
7898         }
7899 
7900         // rounding type
7901         if (lsu >= count - 1 && mybig[count - 1] == 0)
7902             next = roundType.ZERO;
7903         else if (lsu == count - 1 && mybig[lsu] == 1L << 59)
7904             next = roundType.FIVE;
7905         else
7906         {
7907             ulong over = 0;
7908             foreach (i;lsu .. count)
7909             {
7910                 mybig[i] = mybig[i] * 10 + over;
7911                 over = mybig[i] >> 60;
7912                 mybig[i] &= (1L << 60) - 1;
7913             }
7914             next = over >= 5 ? roundType.UPPER : roundType.LOWER;
7915         }
7916     }
7917     else
7918     {
7919         // medium sized number, probably with integer and fractional digits
7920         // this is fastest, because both parts fit into a ulong each
7921         ulong int_part = mnt >> (T.mant_dig - 1 - exp);
7922         ulong frac_part = mnt & ((1L << (T.mant_dig - 1 - exp)) - 1);
7923 
7924         start = 0;
7925 
7926         // could we already decide on the rounding mode in the integer part?
7927         bool found = false;
7928 
7929         if (int_part > 0)
7930         {
7931             // integer part, if there is something to print
7932             while (int_part >= 10)
7933             {
7934                 buffer[--left] = '0' + (int_part % 10);
7935                 int_part /= 10;
7936                 ++final_exp;
7937                 ++start;
7938             }
7939 
7940             buffer[--left] = '.';
7941             buffer[--left] = cast(byte) ('0' + int_part);
7942 
7943             if (right - left > f.precision + 2)
7944             {
7945                 auto old_right = right;
7946                 right = left + f.precision + 2;
7947 
7948                 if (buffer[right] == '5' || buffer[right] == '0')
7949                 {
7950                     next = buffer[right] == '5' ? roundType.FIVE : roundType.ZERO;
7951                     if (frac_part != 0)
7952                         next = next == roundType.FIVE ? roundType.UPPER : roundType.LOWER;
7953                     else
7954                         foreach (i;right + 1 .. old_right)
7955                             if (buffer[i] > '0')
7956                             {
7957                                 next = next == roundType.FIVE ? roundType.UPPER : roundType.LOWER;
7958                                 break;
7959                             }
7960                 }
7961                 else
7962                     next = buffer[right] > '5' ? roundType.UPPER : roundType.LOWER;
7963                 found = true;
7964             }
7965         }
7966         else
7967         {
7968             // fractional part, skipping leading zeros
7969             while (frac_part != 0)
7970             {
7971                 --final_exp;
7972                 frac_part *= 10;
7973                 auto tmp = frac_part >> (T.mant_dig - 1 - exp);
7974                 frac_part &= ((1L << (T.mant_dig - 1 - exp)) - 1);
7975                 if (tmp > 0)
7976                 {
7977                     buffer[right++] = cast(byte) ('0' + tmp);
7978                     buffer[right++] = '.';
7979                     break;
7980                 }
7981             }
7982 
7983             next = roundType.ZERO;
7984         }
7985 
7986         if (f.precision == 0 && !f.flHash) right--;
7987 
7988         // the fractional part after the zeros
7989         while (frac_part != 0 && start < f.precision)
7990         {
7991             frac_part *= 10;
7992             buffer[right++] = cast(byte) ('0' + (frac_part >> (T.mant_dig - 1 - exp)));
7993             frac_part &= ((1L << (T.mant_dig - 1 - exp)) - 1);
7994             ++start;
7995         }
7996 
7997         if (start < f.precision)
7998         {
7999             buffer[right .. right + f.precision - start] = '0';
8000             right += f.precision - start;
8001             start = f.precision;
8002         }
8003 
8004         // rounding mode, if not allready known
8005         if (frac_part != 0 && !found)
8006         {
8007             frac_part *= 10;
8008             auto nextDigit = frac_part >> (T.mant_dig - 1 - exp);
8009             frac_part &= ((1L << (T.mant_dig - 1 - exp)) - 1);
8010 
8011             if (nextDigit == 5 && frac_part == 0)
8012                 next = roundType.FIVE;
8013             else if (nextDigit >= 5)
8014                 next = roundType.UPPER;
8015             else
8016                 next = roundType.LOWER;
8017         }
8018     }
8019 
8020     // rounding
8021     bool roundUp = false;
8022     if (rm == RoundingMode.up)
8023         roundUp = next != roundType.ZERO && sgn != "-";
8024     else if (rm == RoundingMode.down)
8025         roundUp = next != roundType.ZERO && sgn == "-";
8026     else if (rm == RoundingMode.toZero)
8027         roundUp = false;
8028     else
8029     {
8030         assert(rm == RoundingMode.toNearestTiesToEven || rm == RoundingMode.toNearestTiesAwayFromZero);
8031         roundUp = next == roundType.UPPER;
8032 
8033         if (next == roundType.FIVE)
8034         {
8035             // IEEE754 allows for two different ways of implementing roundToNearest:
8036 
8037             // Round to nearest, ties away from zero
8038             if (rm == RoundingMode.toNearestTiesAwayFromZero)
8039                 roundUp = true;
8040             else
8041             {
8042                 // Round to nearest, ties to even
8043                 auto last = buffer[right-1];
8044                 if (last == '.') last = buffer[right-2];
8045                 roundUp = last % 2 != 0;
8046             }
8047         }
8048     }
8049 
8050     if (roundUp)
8051     {
8052         foreach_reverse (i;left .. right)
8053         {
8054             if (buffer[i] == '.') continue;
8055             if (buffer[i] == '9')
8056                 buffer[i] = '0';
8057             else
8058             {
8059                 buffer[i]++;
8060                 goto printFloat_done;
8061             }
8062         }
8063 
8064         // one more digit to the left, so we need to shift everything and increase exponent
8065         buffer[--left] = '1';
8066         buffer[left + 2] = buffer[left + 1];
8067         if (f.flHash || f.precision != 0)
8068             buffer[left + 1] = '.';
8069         right--;
8070         final_exp++;
8071 
8072 printFloat_done:
8073     }
8074 
8075     // printing exponent
8076     buffer[right++] = is_upper ? 'E' : 'e';
8077     buffer[right++] = final_exp >= 0 ? '+' : '-';
8078 
8079     if (final_exp < 0) final_exp = -final_exp;
8080 
8081     static if (is(T == float))
8082         enum max_exp_digits = 2;
8083     else
8084         enum max_exp_digits = 3;
8085 
8086     char[max_exp_digits] exp_str;
8087     size_t exp_pos = max_exp_digits;
8088 
8089     do
8090     {
8091         exp_str[--exp_pos] = '0' + final_exp%10;
8092         final_exp /= 10;
8093     } while (final_exp>0);
8094     if (max_exp_digits - exp_pos == 1)
8095         exp_str[--exp_pos] = '0';
8096 
8097     buffer[right .. right + max_exp_digits - exp_pos] = exp_str[exp_pos .. $];
8098     right += max_exp_digits - exp_pos;
8099 
8100     // sign and padding
8101     bool need_sgn = false;
8102     if (sgn != "")
8103     {
8104         // when padding with zeros we need to postpone adding the sign
8105         if (right - left < f.width && !f.flDash && f.flZero)
8106             need_sgn = true;
8107         else
8108             buffer[--left] = sgn[0];
8109     }
8110 
8111     if (right - left < f.width)
8112     {
8113         if (f.flDash)
8114         {
8115             // padding right
8116             buffer[right .. f.width + left] = ' ';
8117             right = f.width + left;
8118         }
8119         else
8120         {
8121             // padding left
8122             buffer[right - f.width .. left] = f.flZero ? '0' : ' ';
8123             left = right - f.width;
8124         }
8125     }
8126 
8127     if (need_sgn)
8128         buffer[left] = sgn[0];
8129 
8130     // without this cast it's getting too slow
8131     return buffer[left .. right];
8132 }
8133 
8134 @safe unittest
8135 {
8136     char[256] buf;
8137     auto f = FormatSpec!dchar("");
8138     f.spec = 'e';
8139     assert(printFloat(buf[], float.nan, f) == "nan");
8140     assert(printFloat(buf[], -float.nan, f) == "-nan");
8141     assert(printFloat(buf[], float.infinity, f) == "inf");
8142     assert(printFloat(buf[], -float.infinity, f) == "-inf");
8143     assert(printFloat(buf[], 0.0f, f) == "0.000000e+00");
8144     assert(printFloat(buf[], -0.0f, f) == "-0.000000e+00");
8145     // cast needed due to https://issues.dlang.org/show_bug.cgi?id=20361
8146     assert(printFloat(buf[], cast(float) 1e-40, f) == "9.999946e-41");
8147     assert(printFloat(buf[], cast(float) -1e-40, f) == "-9.999946e-41");
8148     assert(printFloat(buf[], 1e-30f, f) == "1.000000e-30");
8149     assert(printFloat(buf[], -1e-30f, f) == "-1.000000e-30");
8150     assert(printFloat(buf[], 1e-10f, f) == "1.000000e-10");
8151     assert(printFloat(buf[], -1e-10f, f) == "-1.000000e-10");
8152     assert(printFloat(buf[], 0.1f, f) == "1.000000e-01");
8153     assert(printFloat(buf[], -0.1f, f) == "-1.000000e-01");
8154     assert(printFloat(buf[], 10.0f, f) == "1.000000e+01");
8155     assert(printFloat(buf[], -10.0f, f) == "-1.000000e+01");
8156     assert(printFloat(buf[], 1e30f, f) == "1.000000e+30");
8157     assert(printFloat(buf[], -1e30f, f) == "-1.000000e+30");
8158 
8159     import std.math : nextUp, nextDown;
8160     assert(printFloat(buf[], nextUp(0.0f), f) == "1.401298e-45");
8161     assert(printFloat(buf[], nextDown(-0.0f), f) == "-1.401298e-45");
8162 }
8163 
8164 @safe unittest
8165 {
8166     char[256] buf;
8167     auto f = FormatSpec!dchar("");
8168     f.spec = 'e';
8169     f.width = 20;
8170     f.precision = 10;
8171 
8172     assert(printFloat(buf[], float.nan, f) == "                 nan");
8173     assert(printFloat(buf[], -float.nan, f) == "                -nan");
8174     assert(printFloat(buf[], float.infinity, f) == "                 inf");
8175     assert(printFloat(buf[], -float.infinity, f) == "                -inf");
8176     assert(printFloat(buf[], 0.0f, f) == "    0.0000000000e+00");
8177     assert(printFloat(buf[], -0.0f, f) == "   -0.0000000000e+00");
8178     // cast needed due to bug 20361
8179     assert(printFloat(buf[], cast(float) 1e-40, f) == "    9.9999461011e-41");
8180     assert(printFloat(buf[], cast(float) -1e-40, f) == "   -9.9999461011e-41");
8181     assert(printFloat(buf[], 1e-30f, f) == "    1.0000000032e-30");
8182     assert(printFloat(buf[], -1e-30f, f) == "   -1.0000000032e-30");
8183     assert(printFloat(buf[], 1e-10f, f) == "    1.0000000134e-10");
8184     assert(printFloat(buf[], -1e-10f, f) == "   -1.0000000134e-10");
8185     assert(printFloat(buf[], 0.1f, f) == "    1.0000000149e-01");
8186     assert(printFloat(buf[], -0.1f, f) == "   -1.0000000149e-01");
8187     assert(printFloat(buf[], 10.0f, f) == "    1.0000000000e+01");
8188     assert(printFloat(buf[], -10.0f, f) == "   -1.0000000000e+01");
8189     assert(printFloat(buf[], 1e30f, f) == "    1.0000000150e+30");
8190     assert(printFloat(buf[], -1e30f, f) == "   -1.0000000150e+30");
8191 
8192     import std.math : nextUp, nextDown;
8193     assert(printFloat(buf[], nextUp(0.0f), f) == "    1.4012984643e-45");
8194     assert(printFloat(buf[], nextDown(-0.0f), f) == "   -1.4012984643e-45");
8195 }
8196 
8197 @safe unittest
8198 {
8199     char[256] buf;
8200     auto f = FormatSpec!dchar("");
8201     f.spec = 'e';
8202     f.width = 20;
8203     f.precision = 10;
8204     f.flDash = true;
8205 
8206     assert(printFloat(buf[], float.nan, f) == "nan                 ");
8207     assert(printFloat(buf[], -float.nan, f) == "-nan                ");
8208     assert(printFloat(buf[], float.infinity, f) == "inf                 ");
8209     assert(printFloat(buf[], -float.infinity, f) == "-inf                ");
8210     assert(printFloat(buf[], 0.0f, f) == "0.0000000000e+00    ");
8211     assert(printFloat(buf[], -0.0f, f) == "-0.0000000000e+00   ");
8212     // cast needed due to bug 20361
8213     assert(printFloat(buf[], cast(float) 1e-40, f) == "9.9999461011e-41    ");
8214     assert(printFloat(buf[], cast(float) -1e-40, f) == "-9.9999461011e-41   ");
8215     assert(printFloat(buf[], 1e-30f, f) == "1.0000000032e-30    ");
8216     assert(printFloat(buf[], -1e-30f, f) == "-1.0000000032e-30   ");
8217     assert(printFloat(buf[], 1e-10f, f) == "1.0000000134e-10    ");
8218     assert(printFloat(buf[], -1e-10f, f) == "-1.0000000134e-10   ");
8219     assert(printFloat(buf[], 0.1f, f) == "1.0000000149e-01    ");
8220     assert(printFloat(buf[], -0.1f, f) == "-1.0000000149e-01   ");
8221     assert(printFloat(buf[], 10.0f, f) == "1.0000000000e+01    ");
8222     assert(printFloat(buf[], -10.0f, f) == "-1.0000000000e+01   ");
8223     assert(printFloat(buf[], 1e30f, f) == "1.0000000150e+30    ");
8224     assert(printFloat(buf[], -1e30f, f) == "-1.0000000150e+30   ");
8225 
8226     import std.math : nextUp, nextDown;
8227     assert(printFloat(buf[], nextUp(0.0f), f) == "1.4012984643e-45    ");
8228     assert(printFloat(buf[], nextDown(-0.0f), f) == "-1.4012984643e-45   ");
8229 }
8230 
8231 @safe unittest
8232 {
8233     char[256] buf;
8234     auto f = FormatSpec!dchar("");
8235     f.spec = 'e';
8236     f.width = 20;
8237     f.precision = 10;
8238     f.flZero = true;
8239 
8240     assert(printFloat(buf[], float.nan, f) == "                 nan");
8241     assert(printFloat(buf[], -float.nan, f) == "                -nan");
8242     assert(printFloat(buf[], float.infinity, f) == "                 inf");
8243     assert(printFloat(buf[], -float.infinity, f) == "                -inf");
8244     assert(printFloat(buf[], 0.0f, f) == "00000.0000000000e+00");
8245     assert(printFloat(buf[], -0.0f, f) == "-0000.0000000000e+00");
8246     // cast needed due to bug 20361
8247     assert(printFloat(buf[], cast(float) 1e-40, f) == "00009.9999461011e-41");
8248     assert(printFloat(buf[], cast(float) -1e-40, f) == "-0009.9999461011e-41");
8249     assert(printFloat(buf[], 1e-30f, f) == "00001.0000000032e-30");
8250     assert(printFloat(buf[], -1e-30f, f) == "-0001.0000000032e-30");
8251     assert(printFloat(buf[], 1e-10f, f) == "00001.0000000134e-10");
8252     assert(printFloat(buf[], -1e-10f, f) == "-0001.0000000134e-10");
8253     assert(printFloat(buf[], 0.1f, f) == "00001.0000000149e-01");
8254     assert(printFloat(buf[], -0.1f, f) == "-0001.0000000149e-01");
8255     assert(printFloat(buf[], 10.0f, f) == "00001.0000000000e+01");
8256     assert(printFloat(buf[], -10.0f, f) == "-0001.0000000000e+01");
8257     assert(printFloat(buf[], 1e30f, f) == "00001.0000000150e+30");
8258     assert(printFloat(buf[], -1e30f, f) == "-0001.0000000150e+30");
8259 
8260     import std.math : nextUp, nextDown;
8261     assert(printFloat(buf[], nextUp(0.0f), f) == "00001.4012984643e-45");
8262     assert(printFloat(buf[], nextDown(-0.0f), f) == "-0001.4012984643e-45");
8263 }
8264 
8265 @safe unittest
8266 {
8267     char[256] buf;
8268     auto f = FormatSpec!dchar("");
8269     f.spec = 'e';
8270     f.precision = 1;
8271 
8272     assert(printFloat(buf[], 11.5f, f, RoundingMode.toNearestTiesAwayFromZero) == "1.2e+01");
8273     assert(printFloat(buf[], 12.5f, f, RoundingMode.toNearestTiesAwayFromZero) == "1.3e+01");
8274     assert(printFloat(buf[], 11.7f, f, RoundingMode.toNearestTiesAwayFromZero) == "1.2e+01");
8275     assert(printFloat(buf[], 11.3f, f, RoundingMode.toNearestTiesAwayFromZero) == "1.1e+01");
8276     assert(printFloat(buf[], 11.0f, f, RoundingMode.toNearestTiesAwayFromZero) == "1.1e+01");
8277     assert(printFloat(buf[], -11.5f, f, RoundingMode.toNearestTiesAwayFromZero) == "-1.2e+01");
8278     assert(printFloat(buf[], -12.5f, f, RoundingMode.toNearestTiesAwayFromZero) == "-1.3e+01");
8279     assert(printFloat(buf[], -11.7f, f, RoundingMode.toNearestTiesAwayFromZero) == "-1.2e+01");
8280     assert(printFloat(buf[], -11.3f, f, RoundingMode.toNearestTiesAwayFromZero) == "-1.1e+01");
8281     assert(printFloat(buf[], -11.0f, f, RoundingMode.toNearestTiesAwayFromZero) == "-1.1e+01");
8282 
8283     assert(printFloat(buf[], 11.5f, f) == "1.2e+01");
8284     assert(printFloat(buf[], 12.5f, f) == "1.2e+01");
8285     assert(printFloat(buf[], 11.7f, f) == "1.2e+01");
8286     assert(printFloat(buf[], 11.3f, f) == "1.1e+01");
8287     assert(printFloat(buf[], 11.0f, f) == "1.1e+01");
8288     assert(printFloat(buf[], -11.5f, f) == "-1.2e+01");
8289     assert(printFloat(buf[], -12.5f, f) == "-1.2e+01");
8290     assert(printFloat(buf[], -11.7f, f) == "-1.2e+01");
8291     assert(printFloat(buf[], -11.3f, f) == "-1.1e+01");
8292     assert(printFloat(buf[], -11.0f, f) == "-1.1e+01");
8293 
8294     assert(printFloat(buf[], 11.5f, f, RoundingMode.toZero) == "1.1e+01");
8295     assert(printFloat(buf[], 12.5f, f, RoundingMode.toZero) == "1.2e+01");
8296     assert(printFloat(buf[], 11.7f, f, RoundingMode.toZero) == "1.1e+01");
8297     assert(printFloat(buf[], 11.3f, f, RoundingMode.toZero) == "1.1e+01");
8298     assert(printFloat(buf[], 11.0f, f, RoundingMode.toZero) == "1.1e+01");
8299     assert(printFloat(buf[], -11.5f, f, RoundingMode.toZero) == "-1.1e+01");
8300     assert(printFloat(buf[], -12.5f, f, RoundingMode.toZero) == "-1.2e+01");
8301     assert(printFloat(buf[], -11.7f, f, RoundingMode.toZero) == "-1.1e+01");
8302     assert(printFloat(buf[], -11.3f, f, RoundingMode.toZero) == "-1.1e+01");
8303     assert(printFloat(buf[], -11.0f, f, RoundingMode.toZero) == "-1.1e+01");
8304 
8305     assert(printFloat(buf[], 11.5f, f, RoundingMode.up) == "1.2e+01");
8306     assert(printFloat(buf[], 12.5f, f, RoundingMode.up) == "1.3e+01");
8307     assert(printFloat(buf[], 11.7f, f, RoundingMode.up) == "1.2e+01");
8308     assert(printFloat(buf[], 11.3f, f, RoundingMode.up) == "1.2e+01");
8309     assert(printFloat(buf[], 11.0f, f, RoundingMode.up) == "1.1e+01");
8310     assert(printFloat(buf[], -11.5f, f, RoundingMode.up) == "-1.1e+01");
8311     assert(printFloat(buf[], -12.5f, f, RoundingMode.up) == "-1.2e+01");
8312     assert(printFloat(buf[], -11.7f, f, RoundingMode.up) == "-1.1e+01");
8313     assert(printFloat(buf[], -11.3f, f, RoundingMode.up) == "-1.1e+01");
8314     assert(printFloat(buf[], -11.0f, f, RoundingMode.up) == "-1.1e+01");
8315 
8316     assert(printFloat(buf[], 11.5f, f, RoundingMode.down) == "1.1e+01");
8317     assert(printFloat(buf[], 12.5f, f, RoundingMode.down) == "1.2e+01");
8318     assert(printFloat(buf[], 11.7f, f, RoundingMode.down) == "1.1e+01");
8319     assert(printFloat(buf[], 11.3f, f, RoundingMode.down) == "1.1e+01");
8320     assert(printFloat(buf[], 11.0f, f, RoundingMode.down) == "1.1e+01");
8321     assert(printFloat(buf[], -11.5f, f, RoundingMode.down) == "-1.2e+01");
8322     assert(printFloat(buf[], -12.5f, f, RoundingMode.down) == "-1.3e+01");
8323     assert(printFloat(buf[], -11.7f, f, RoundingMode.down) == "-1.2e+01");
8324     assert(printFloat(buf[], -11.3f, f, RoundingMode.down) == "-1.2e+01");
8325     assert(printFloat(buf[], -11.0f, f, RoundingMode.down) == "-1.1e+01");
8326 }
8327 
8328 @safe unittest
8329 {
8330     char[256] buf;
8331     auto f = FormatSpec!dchar("");
8332     f.spec = 'e';
8333     assert(printFloat(buf[], double.nan, f) == "nan");
8334     assert(printFloat(buf[], -double.nan, f) == "-nan");
8335     assert(printFloat(buf[], double.infinity, f) == "inf");
8336     assert(printFloat(buf[], -double.infinity, f) == "-inf");
8337     assert(printFloat(buf[], 0.0, f) == "0.000000e+00");
8338     assert(printFloat(buf[], -0.0, f) == "-0.000000e+00");
8339     // / 1000 needed due to bug 20361
8340     assert(printFloat(buf[], 1e-307 / 1000, f) == "1.000000e-310");
8341     assert(printFloat(buf[], -1e-307 / 1000, f) == "-1.000000e-310");
8342     assert(printFloat(buf[], 1e-30, f) == "1.000000e-30");
8343     assert(printFloat(buf[], -1e-30, f) == "-1.000000e-30");
8344     assert(printFloat(buf[], 1e-10, f) == "1.000000e-10");
8345     assert(printFloat(buf[], -1e-10, f) == "-1.000000e-10");
8346     assert(printFloat(buf[], 0.1, f) == "1.000000e-01");
8347     assert(printFloat(buf[], -0.1, f) == "-1.000000e-01");
8348     assert(printFloat(buf[], 10.0, f) == "1.000000e+01");
8349     assert(printFloat(buf[], -10.0, f) == "-1.000000e+01");
8350     assert(printFloat(buf[], 1e300, f) == "1.000000e+300");
8351     assert(printFloat(buf[], -1e300, f) == "-1.000000e+300");
8352 
8353     import std.math : nextUp, nextDown;
8354     assert(printFloat(buf[], nextUp(0.0), f) == "4.940656e-324");
8355     assert(printFloat(buf[], nextDown(-0.0), f) == "-4.940656e-324");
8356 }
8357 
8358 @safe unittest
8359 {
8360     char[256] buf;
8361     auto f = FormatSpec!dchar("");
8362     f.spec = 'e';
8363 
8364     import std.math : nextUp;
8365 
8366     double eps = nextUp(0.0);
8367     f.precision = 1000;
8368     assert(printFloat(buf[], eps, f) ==
8369            "4.9406564584124654417656879286822137236505980261432476442558568250067550727020875186529983636163599"
8370            ~"23797965646954457177309266567103559397963987747960107818781263007131903114045278458171678489821036"
8371            ~"88718636056998730723050006387409153564984387312473397273169615140031715385398074126238565591171026"
8372            ~"65855668676818703956031062493194527159149245532930545654440112748012970999954193198940908041656332"
8373            ~"45247571478690147267801593552386115501348035264934720193790268107107491703332226844753335720832431"
8374            ~"93609238289345836806010601150616980975307834227731832924790498252473077637592724787465608477820373"
8375            ~"44696995336470179726777175851256605511991315048911014510378627381672509558373897335989936648099411"
8376            ~"64205702637090279242767544565229087538682506419718265533447265625000000000000000000000000000000000"
8377            ~"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
8378            ~"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
8379            ~"000000000000000000000e-324");
8380 
8381     f.precision = 50;
8382     assert(printFloat(buf[], double.max, f) ==
8383            "1.79769313486231570814527423731704356798070567525845e+308");
8384     assert(printFloat(buf[], double.epsilon, f) ==
8385            "2.22044604925031308084726333618164062500000000000000e-16");
8386 
8387     f.precision = 10;
8388     assert(printFloat(buf[], 1.0/3.0, f) == "3.3333333333e-01");
8389     assert(printFloat(buf[], 1.0/7.0, f) == "1.4285714286e-01");
8390     assert(printFloat(buf[], 1.0/9.0, f) == "1.1111111111e-01");
8391 }
8392 
8393 @safe unittest
8394 {
8395     char[256] buf;
8396     auto f = FormatSpec!dchar("");
8397     f.spec = 'e';
8398     f.precision = 15;
8399 
8400     import std.math : E, PI, PI_2, PI_4, M_1_PI, M_2_PI, M_2_SQRTPI,
8401                       LN10, LN2, LOG2, LOG2E, LOG2T, LOG10E, SQRT2, SQRT1_2;
8402 
8403     assert(printFloat(buf[], cast(double) E, f) == "2.718281828459045e+00");
8404     assert(printFloat(buf[], cast(double) PI, f) == "3.141592653589793e+00");
8405     assert(printFloat(buf[], cast(double) PI_2, f) == "1.570796326794897e+00");
8406     assert(printFloat(buf[], cast(double) PI_4, f) == "7.853981633974483e-01");
8407     assert(printFloat(buf[], cast(double) M_1_PI, f) == "3.183098861837907e-01");
8408     assert(printFloat(buf[], cast(double) M_2_PI, f) == "6.366197723675814e-01");
8409     assert(printFloat(buf[], cast(double) M_2_SQRTPI, f) == "1.128379167095513e+00");
8410     assert(printFloat(buf[], cast(double) LN10, f) == "2.302585092994046e+00");
8411     assert(printFloat(buf[], cast(double) LN2, f) == "6.931471805599453e-01");
8412     assert(printFloat(buf[], cast(double) LOG2, f) == "3.010299956639812e-01");
8413     assert(printFloat(buf[], cast(double) LOG2E, f) == "1.442695040888963e+00");
8414     assert(printFloat(buf[], cast(double) LOG2T, f) == "3.321928094887362e+00");
8415     assert(printFloat(buf[], cast(double) LOG10E, f) == "4.342944819032518e-01");
8416     assert(printFloat(buf[], cast(double) SQRT2, f) == "1.414213562373095e+00");
8417     assert(printFloat(buf[], cast(double) SQRT1_2, f) == "7.071067811865476e-01");
8418 }
8419 
8420 // for 100% coverage
8421 @safe unittest
8422 {
8423     char[256] buf;
8424     auto f = FormatSpec!dchar("");
8425     f.spec = 'E';
8426     f.precision = 80;
8427     assert(printFloat(buf[], 5.62776e+12f, f) ==
8428            "5.62775982080000000000000000000000000000000000000000000000000000000000000000000000E+12");
8429 
8430     f.precision = 49;
8431     assert(printFloat(buf[], 2.5997869e-12f, f) ==
8432            "2.5997869221999758693186777236405760049819946289062E-12");
8433 
8434     f.precision = 6;
8435     assert(printFloat(buf[], -1.1418613e+07f, f) == "-1.141861E+07");
8436     assert(printFloat(buf[], -1.368281e+07f, f) == "-1.368281E+07");
8437 
8438     f.precision = 0;
8439     assert(printFloat(buf[], 709422.0f, f, RoundingMode.up) == "8E+05");
8440 
8441     f.precision = 1;
8442     assert(printFloat(buf[], -245.666f, f) == "-2.5E+02");
8443 }
8444 
8445 // check no allocations
8446 @system unittest
8447 {
8448     import core.memory;
8449     auto stats = GC.stats;
8450 
8451     char[256] buf;
8452     auto f = FormatSpec!dchar("");
8453     f.spec = 'a';
8454     assert(printFloat(buf[], float.nan, f) == "nan");
8455     assert(printFloat(buf[], -float.infinity, f) == "-inf");
8456     assert(printFloat(buf[], 0.0f, f) == "0x0p+0");
8457 
8458     assert(printFloat(buf[], -double.nan, f) == "-nan");
8459     assert(printFloat(buf[], double.infinity, f) == "inf");
8460     assert(printFloat(buf[], -0.0, f) == "-0x0p+0");
8461 
8462     import std.math : nextUp, E;
8463 
8464     assert(printFloat(buf[], nextUp(0.0f), f) == "0x0.000002p-126");
8465     assert(printFloat(buf[], cast(float) E, f) == "0x1.5bf0a8p+1");
8466 
8467     f.spec = 'E';
8468     f.precision = 80;
8469     assert(printFloat(buf[], 5.62776e+12f, f) ==
8470            "5.62775982080000000000000000000000000000000000000000000000000000000000000000000000E+12");
8471 
8472     f.precision = 6;
8473     assert(printFloat(buf[], -1.1418613e+07f, f) == "-1.141861E+07");
8474 
8475     assert(GC.stats.usedSize == stats.usedSize);
8476 }
Suggestion Box / Bug Report