xref: /netbsd-src/external/gpl3/gcc/dist/libphobos/src/std/format/internal/write.d (revision b1e838363e3c6fc78a55519254d99869742dd33c)
1 // Written in the D programming language.
2 
3 /*
4    Copyright: Copyright The D Language Foundation 2000-2013.
5 
6    License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
7 
8    Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com,
9    Andrei Alexandrescu), and Kenji Hara
10 
11    Source: $(PHOBOSSRC std/format/internal/write.d)
12  */
13 module std.format.internal.write;
14 
15 import std.format.spec : FormatSpec;
16 import std.range.primitives : isInputRange;
17 import std.traits;
18 
version(StdUnittest)19 version (StdUnittest)
20 {
21     import std.exception : assertCTFEable;
22     import std.format : format;
23 }
24 
25 package(std.format):
26 
27 /*
28     `bool`s are formatted as `"true"` or `"false"` with `%s` and as `1` or
29     `0` with integral-specific format specs.
30  */
31 void formatValueImpl(Writer, T, Char)(auto ref Writer w, const(T) obj, scope const ref FormatSpec!Char f)
32 if (is(BooleanTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
33 {
34     BooleanTypeOf!T val = obj;
35 
36     if (f.spec == 's')
37         writeAligned(w, val ? "true" : "false", f);
38     else
39         formatValueImpl(w, cast(byte) val, f);
40 }
41 
42 @safe pure unittest
43 {
44     assertCTFEable!(
45     {
46         formatTest(false, "false");
47         formatTest(true,  "true");
48     });
49 }
50 
51 @safe unittest
52 {
53     class C1
54     {
55         bool val;
56         alias val this;
this(bool v)57         this(bool v){ val = v; }
58     }
59 
60     class C2 {
61         bool val;
62         alias val this;
this(bool v)63         this(bool v){ val = v; }
toString()64         override string toString() const { return "C"; }
65     }
66 
67     () @trusted {
68         formatTest(new C1(false), "false");
69         formatTest(new C1(true),  "true");
70         formatTest(new C2(false), "C");
71         formatTest(new C2(true),  "C");
72     } ();
73 
74     struct S1
75     {
76         bool val;
77         alias val this;
78     }
79 
80     struct S2
81     {
82         bool val;
83         alias val this;
toString()84         string toString() const { return "S"; }
85     }
86 
87     formatTest(S1(false), "false");
88     formatTest(S1(true),  "true");
89     formatTest(S2(false), "S");
90     formatTest(S2(true),  "S");
91 }
92 
93 @safe pure unittest
94 {
95     string t1 = format("[%6s] [%6s] [%-6s]", true, false, true);
96     assert(t1 == "[  true] [ false] [true  ]");
97 
98     string t2 = format("[%3s] [%-2s]", true, false);
99     assert(t2 == "[true] [false]");
100 }
101 
102 // https://issues.dlang.org/show_bug.cgi?id=20534
103 @safe pure unittest
104 {
105     assert(format("%r",false) == "\0");
106 }
107 
108 @safe pure unittest
109 {
110     assert(format("%07s",true) == "   true");
111 }
112 
113 @safe pure unittest
114 {
115     assert(format("%=8s",true)    == "  true  ");
116     assert(format("%=9s",false)   == "  false  ");
117     assert(format("%=9s",true)    == "   true  ");
118     assert(format("%-=9s",true)   == "  true   ");
119     assert(format("%=10s",false)  == "   false  ");
120     assert(format("%-=10s",false) == "  false   ");
121 }
122 
123 /*
124     `null` literal is formatted as `"null"`
125  */
126 void formatValueImpl(Writer, T, Char)(auto ref Writer w, const(T) obj, scope const ref FormatSpec!Char f)
127 if (is(immutable T == immutable typeof(null)) && !is(T == enum) && !hasToString!(T, Char))
128 {
129     import std.format : enforceFmt;
130 
131     const spec = f.spec;
132     enforceFmt(spec == 's', "null literal cannot match %" ~ spec);
133 
134     writeAligned(w, "null", f);
135 }
136 
137 @safe pure unittest
138 {
139     import std.exception : collectExceptionMsg;
140     import std.format : FormatException;
141     import std.range.primitives : back;
142 
143     assert(collectExceptionMsg!FormatException(format("%p", null)).back == 'p');
144 
145     assertCTFEable!(
146     {
147         formatTest(null, "null");
148     });
149 }
150 
151 @safe pure unittest
152 {
153     string t = format("[%6s] [%-6s]", null, null);
154     assert(t == "[  null] [null  ]");
155 }
156 
157 /*
158     Integrals are formatted like $(REF printf, core, stdc, stdio).
159  */
160 void formatValueImpl(Writer, T, Char)(auto ref Writer w, const(T) obj, scope const ref FormatSpec!Char f)
161 if (is(IntegralTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
162 {
163     alias U = IntegralTypeOf!T;
164     U val = obj;    // Extracting alias this may be impure/system/may-throw
165 
166     if (f.spec == 'r')
167     {
168         // raw write, skip all else and write the thing
169         auto raw = (ref val) @trusted {
170             return (cast(const char*) &val)[0 .. val.sizeof];
171         }(val);
172         import std.range.primitives : put;
173         if (needToSwapEndianess(f))
174             foreach_reverse (c; raw)
175                 put(w, c);
176         else
177             foreach (c; raw)
178                 put(w, c);
179         return;
180     }
181 
182     static if (isSigned!U)
183     {
184         const negative = val < 0 && f.spec != 'x' && f.spec != 'X' && f.spec != 'b' && f.spec != 'o' && f.spec != 'u';
185         ulong arg = negative ? -cast(ulong) val : val;
186     }
187     else
188     {
189         const negative = false;
190         ulong arg = val;
191     }
192     arg &= Unsigned!U.max;
193 
194     formatValueImplUlong!(Writer, Char)(w, arg, negative, f);
195 }
196 
197 // Helper function for `formatValueImpl` that avoids template bloat
198 private void formatValueImplUlong(Writer, Char)(auto ref Writer w, ulong arg, in bool negative,
199                                                 scope const ref FormatSpec!Char f)
200 {
201     immutable uint base = baseOfSpec(f.spec);
202 
203     const bool zero = arg == 0;
204     char[64] digits = void;
205     size_t pos = digits.length - 1;
206     do
207     {
208         /* `cast(char)` is needed because value range propagation (VRP) cannot
209          * analyze `base` because it’s computed in a separate function
210          * (`baseOfSpec`). */
211         digits[pos--] = cast(char) ('0' + arg % base);
212         if (base > 10 && digits[pos + 1] > '9')
213             digits[pos + 1] += ((f.spec == 'x' || f.spec == 'a') ? 'a' : 'A') - '0' - 10;
214         arg /= base;
215     } while (arg > 0);
216 
217     char[3] prefix = void;
218     size_t left = 2;
219     size_t right = 2;
220 
221     // add sign
222     if (f.spec != 'x' && f.spec != 'X' && f.spec != 'b' && f.spec != 'o' && f.spec != 'u')
223     {
224         if (negative)
225             prefix[right++] = '-';
226         else if (f.flPlus)
227             prefix[right++] = '+';
228         else if (f.flSpace)
229             prefix[right++] = ' ';
230     }
231 
232     // not a floating point like spec
233     if (f.spec == 'x' || f.spec == 'X' || f.spec == 'b' || f.spec == 'o' || f.spec == 'u'
234         || f.spec == 'd' || f.spec == 's')
235     {
236         if (f.flHash && (base == 16) && !zero)
237         {
238             prefix[--left] = f.spec;
239             prefix[--left] = '0';
240         }
241         if (f.flHash && (base == 8) && !zero
242             && (digits.length - (pos + 1) >= f.precision || f.precision == f.UNSPECIFIED))
243             prefix[--left] = '0';
244 
245         writeAligned(w, prefix[left .. right], digits[pos + 1 .. $], "", f, true);
246         return;
247     }
248 
249     FormatSpec!Char fs = f;
250     if (f.precision == f.UNSPECIFIED)
251         fs.precision = cast(typeof(fs.precision)) (digits.length - pos - 2);
252 
253     // %f like output
254     if (f.spec == 'f' || f.spec == 'F'
255         || ((f.spec == 'g' || f.spec == 'G') && (fs.precision >= digits.length - pos - 2)))
256     {
257         if (f.precision == f.UNSPECIFIED)
258             fs.precision = 0;
259 
260         writeAligned(w, prefix[left .. right], digits[pos + 1 .. $], ".", "", fs,
261                      (f.spec == 'g' || f.spec == 'G') ? PrecisionType.allDigits : PrecisionType.fractionalDigits);
262 
263         return;
264     }
265 
266     import std.algorithm.searching : all;
267 
268     // at least one digit for %g
269     if ((f.spec == 'g' || f.spec == 'G') && fs.precision == 0)
270         fs.precision = 1;
271 
272     // rounding
273     size_t digit_end = pos + fs.precision + ((f.spec == 'g' || f.spec == 'G') ? 1 : 2);
274     if (digit_end <= digits.length)
275     {
276         RoundingClass rt = RoundingClass.ZERO;
277         if (digit_end < digits.length)
278         {
279             auto tie = (f.spec == 'a' || f.spec == 'A') ? '8' : '5';
280             if (digits[digit_end] >= tie)
281             {
282                 rt = RoundingClass.UPPER;
283                 if (digits[digit_end] == tie && digits[digit_end + 1 .. $].all!(a => a == '0'))
284                     rt = RoundingClass.FIVE;
285             }
286             else
287             {
288                 rt = RoundingClass.LOWER;
289                 if (digits[digit_end .. $].all!(a => a == '0'))
290                     rt = RoundingClass.ZERO;
291             }
292         }
293 
294         if (round(digits, pos + 1, digit_end, rt, negative,
295                   f.spec == 'a' ? 'f' : (f.spec == 'A' ? 'F' : '9')))
296         {
297             pos--;
298             digit_end--;
299         }
300     }
301 
302     // convert to scientific notation
303     char[1] int_digit = void;
304     int_digit[0] = digits[pos + 1];
305     digits[pos + 1] = '.';
306 
307     char[4] suffix = void;
308 
309     if (f.spec == 'e' || f.spec == 'E' || f.spec == 'g' || f.spec == 'G')
310     {
311         suffix[0] = (f.spec == 'e' || f.spec == 'g') ? 'e' : 'E';
312         suffix[1] = '+';
313         suffix[2] = cast(char) ('0' + (digits.length - pos - 2) / 10);
314         suffix[3] = cast(char) ('0' + (digits.length - pos - 2) % 10);
315     }
316     else
317     {
318         if (right == 3)
319             prefix[0] = prefix[2];
320         prefix[1] = '0';
321         prefix[2] = f.spec == 'a' ? 'x' : 'X';
322 
323         left = right == 3 ? 0 : 1;
324         right = 3;
325 
326         suffix[0] = f.spec == 'a' ? 'p' : 'P';
327         suffix[1] = '+';
328         suffix[2] = cast(char) ('0' + ((digits.length - pos - 2) * 4) / 10);
329         suffix[3] = cast(char) ('0' + ((digits.length - pos - 2) * 4) % 10);
330     }
331 
332     import std.algorithm.comparison : min;
333 
334     // remove trailing zeros
335     if ((f.spec == 'g' || f.spec == 'G') && !f.flHash)
336     {
337         digit_end = min(digit_end, digits.length);
338         while (digit_end > pos + 1 &&
339                (digits[digit_end - 1] == '0' || digits[digit_end - 1] == '.'))
340             digit_end--;
341     }
342 
343     writeAligned(w, prefix[left .. right], int_digit[0 .. $],
344                  digits[pos + 1 .. min(digit_end, $)],
345                  suffix[0 .. $], fs,
346                  (f.spec == 'g' || f.spec == 'G') ? PrecisionType.allDigits : PrecisionType.fractionalDigits);
347 }
348 
349 private uint baseOfSpec(in char spec) @safe pure
350 {
351     typeof(return) base =
352         spec == 'x' || spec == 'X' || spec == 'a' || spec == 'A' ? 16 :
353         spec == 'o' ? 8 :
354         spec == 'b' ? 2 :
355         spec == 's' || spec == 'd' || spec == 'u'
356         || spec == 'e' || spec == 'E' || spec == 'f' || spec == 'F'
357         || spec == 'g' || spec == 'G' ? 10 :
358         0;
359 
360     import std.format : enforceFmt;
361     enforceFmt(base > 0,
362         "incompatible format character for integral argument: %" ~ spec);
363 
364     return base;
365 }
366 
367 @safe pure unittest
368 {
369     assertCTFEable!(
370     {
371         formatTest(byte.min, "-128");
372         formatTest(byte.max, "127");
373         formatTest(short.min, "-32768");
374         formatTest(short.max, "32767");
375         formatTest(int.min, "-2147483648");
376         formatTest(int.max, "2147483647");
377         formatTest(long.min, "-9223372036854775808");
378         formatTest(long.max, "9223372036854775807");
379 
380         formatTest(ubyte.min, "0");
381         formatTest(ubyte.max, "255");
382         formatTest(ushort.min, "0");
383         formatTest(ushort.max, "65535");
384         formatTest(uint.min, "0");
385         formatTest(uint.max, "4294967295");
386         formatTest(ulong.min, "0");
387         formatTest(ulong.max, "18446744073709551615");
388     });
389 }
390 
391 // https://issues.dlang.org/show_bug.cgi?id=18838
392 @safe pure unittest
393 {
394     assert("%12,d".format(0) == "           0");
395 }
396 
397 @safe pure unittest
398 {
399     import std.exception : collectExceptionMsg;
400     import std.format : FormatException;
401     import std.range.primitives : back;
402 
403     assert(collectExceptionMsg!FormatException(format("%c", 5)).back == 'c');
404 
405     assertCTFEable!(
406     {
407         formatTest(9, "9");
408         formatTest(10, "10");
409     });
410 }
411 
412 @safe unittest
413 {
414     class C1
415     {
416         long val;
417         alias val this;
418         this(long v){ val = v; }
419     }
420 
421     class C2
422     {
423         long val;
424         alias val this;
425         this(long v){ val = v; }
426         override string toString() const { return "C"; }
427     }
428 
429     () @trusted {
430         formatTest(new C1(10), "10");
431         formatTest(new C2(10), "C");
432     } ();
433 
434     struct S1
435     {
436         long val;
437         alias val this;
438     }
439 
440     struct S2
441     {
442         long val;
443         alias val this;
444         string toString() const { return "S"; }
445     }
446 
447     formatTest(S1(10), "10");
448     formatTest(S2(10), "S");
449 }
450 
451 // https://issues.dlang.org/show_bug.cgi?id=20064
452 @safe unittest
453 {
454     assert(format( "%03,d",  1234) ==              "1,234");
455     assert(format( "%04,d",  1234) ==              "1,234");
456     assert(format( "%05,d",  1234) ==              "1,234");
457     assert(format( "%06,d",  1234) ==             "01,234");
458     assert(format( "%07,d",  1234) ==            "001,234");
459     assert(format( "%08,d",  1234) ==          "0,001,234");
460     assert(format( "%09,d",  1234) ==          "0,001,234");
461     assert(format("%010,d",  1234) ==         "00,001,234");
462     assert(format("%011,d",  1234) ==        "000,001,234");
463     assert(format("%012,d",  1234) ==      "0,000,001,234");
464     assert(format("%013,d",  1234) ==      "0,000,001,234");
465     assert(format("%014,d",  1234) ==     "00,000,001,234");
466     assert(format("%015,d",  1234) ==    "000,000,001,234");
467     assert(format("%016,d",  1234) ==  "0,000,000,001,234");
468     assert(format("%017,d",  1234) ==  "0,000,000,001,234");
469 
470     assert(format( "%03,d", -1234) ==             "-1,234");
471     assert(format( "%04,d", -1234) ==             "-1,234");
472     assert(format( "%05,d", -1234) ==             "-1,234");
473     assert(format( "%06,d", -1234) ==             "-1,234");
474     assert(format( "%07,d", -1234) ==            "-01,234");
475     assert(format( "%08,d", -1234) ==           "-001,234");
476     assert(format( "%09,d", -1234) ==         "-0,001,234");
477     assert(format("%010,d", -1234) ==         "-0,001,234");
478     assert(format("%011,d", -1234) ==        "-00,001,234");
479     assert(format("%012,d", -1234) ==       "-000,001,234");
480     assert(format("%013,d", -1234) ==     "-0,000,001,234");
481     assert(format("%014,d", -1234) ==     "-0,000,001,234");
482     assert(format("%015,d", -1234) ==    "-00,000,001,234");
483     assert(format("%016,d", -1234) ==   "-000,000,001,234");
484     assert(format("%017,d", -1234) == "-0,000,000,001,234");
485 }
486 
487 @safe pure unittest
488 {
489     string t1 = format("[%6s] [%-6s]", 123, 123);
490     assert(t1 == "[   123] [123   ]");
491 
492     string t2 = format("[%6s] [%-6s]", -123, -123);
493     assert(t2 == "[  -123] [-123  ]");
494 }
495 
496 @safe pure unittest
497 {
498     formatTest(byte.min, "-128");
499     formatTest(short.min, "-32768");
500     formatTest(int.min, "-2147483648");
501     formatTest(long.min, "-9223372036854775808");
502 }
503 
504 // https://issues.dlang.org/show_bug.cgi?id=21777
505 @safe pure unittest
506 {
507     assert(format!"%20.5,d"(cast(short) 120) == "              00,120");
508     assert(format!"%20.5,o"(cast(short) 120) == "              00,170");
509     assert(format!"%20.5,x"(cast(short) 120) == "              00,078");
510     assert(format!"%20.5,2d"(cast(short) 120) == "             0,01,20");
511     assert(format!"%20.5,2o"(cast(short) 120) == "             0,01,70");
512     assert(format!"%20.5,4d"(cast(short) 120) == "              0,0120");
513     assert(format!"%20.5,4o"(cast(short) 120) == "              0,0170");
514     assert(format!"%20.5,4x"(cast(short) 120) == "              0,0078");
515     assert(format!"%20.5,2x"(3000) == "             0,0b,b8");
516     assert(format!"%20.5,4d"(3000) == "              0,3000");
517     assert(format!"%20.5,4o"(3000) == "              0,5670");
518     assert(format!"%20.5,4x"(3000) == "              0,0bb8");
519     assert(format!"%20.5,d"(-400) == "             -00,400");
520     assert(format!"%20.30d"(-400) == "-000000000000000000000000000400");
521     assert(format!"%20.5,4d"(0) == "              0,0000");
522     assert(format!"%0#.8,2s"(12345) == "00,01,23,45");
523     assert(format!"%0#.9,3x"(55) == "0x000,000,037");
524 }
525 
526 // https://issues.dlang.org/show_bug.cgi?id=21814
527 @safe pure unittest
528 {
529     assert(format("%,0d",1000) == "1000");
530 }
531 
532 // https://issues.dlang.org/show_bug.cgi?id=21817
533 @safe pure unittest
534 {
535     assert(format!"%u"(-5) == "4294967291");
536 }
537 
538 // https://issues.dlang.org/show_bug.cgi?id=21820
539 @safe pure unittest
540 {
541     assert(format!"%#.0o"(0) == "0");
542 }
543 
544 @safe pure unittest
545 {
546     assert(format!"%e"(10000) == "1.0000e+04");
547     assert(format!"%.2e"(10000) == "1.00e+04");
548     assert(format!"%.10e"(10000) == "1.0000000000e+04");
549 
550     assert(format!"%e"(9999) == "9.999e+03");
551     assert(format!"%.2e"(9999) == "1.00e+04");
552     assert(format!"%.10e"(9999) == "9.9990000000e+03");
553 
554     assert(format!"%f"(10000) == "10000");
555     assert(format!"%.2f"(10000) == "10000.00");
556 
557     assert(format!"%g"(10000) == "10000");
558     assert(format!"%.2g"(10000) == "1e+04");
559     assert(format!"%.10g"(10000) == "10000");
560 
561     assert(format!"%#g"(10000) == "10000.");
562     assert(format!"%#.2g"(10000) == "1.0e+04");
563     assert(format!"%#.10g"(10000) == "10000.00000");
564 
565     assert(format!"%g"(9999) == "9999");
566     assert(format!"%.2g"(9999) == "1e+04");
567     assert(format!"%.10g"(9999) == "9999");
568 
569     assert(format!"%a"(0x10000) == "0x1.0000p+16");
570     assert(format!"%.2a"(0x10000) == "0x1.00p+16");
571     assert(format!"%.10a"(0x10000) == "0x1.0000000000p+16");
572 
573     assert(format!"%a"(0xffff) == "0xf.fffp+12");
574     assert(format!"%.2a"(0xffff) == "0x1.00p+16");
575     assert(format!"%.10a"(0xffff) == "0xf.fff0000000p+12");
576 }
577 
578 @safe pure unittest
579 {
580     assert(format!"%.3e"(ulong.max) == "1.845e+19");
581     assert(format!"%.3f"(ulong.max) == "18446744073709551615.000");
582     assert(format!"%.3g"(ulong.max) == "1.84e+19");
583     assert(format!"%.3a"(ulong.max) == "0x1.000p+64");
584 
585     assert(format!"%.3e"(long.min) == "-9.223e+18");
586     assert(format!"%.3f"(long.min) == "-9223372036854775808.000");
587     assert(format!"%.3g"(long.min) == "-9.22e+18");
588     assert(format!"%.3a"(long.min) == "-0x8.000p+60");
589 
590     assert(format!"%e"(0) == "0e+00");
591     assert(format!"%f"(0) == "0");
592     assert(format!"%g"(0) == "0");
593     assert(format!"%a"(0) == "0x0p+00");
594 }
595 
596 @safe pure unittest
597 {
598     assert(format!"%.0g"(1500) == "2e+03");
599 }
600 
601 // https://issues.dlang.org/show_bug.cgi?id=21900#
602 @safe pure unittest
603 {
604     assert(format!"%.1a"(472) == "0x1.ep+08");
605 }
606 
607 /*
608     Floating-point values are formatted like $(REF printf, core, stdc, stdio)
609  */
610 void formatValueImpl(Writer, T, Char)(auto ref Writer w, const(T) obj,
611                                       scope const ref FormatSpec!Char f)
612 if (is(FloatingPointTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
613 {
614     import std.algorithm.searching : find;
615     import std.format : enforceFmt;
616     import std.range.primitives : put;
617 
618     FloatingPointTypeOf!T val = obj;
619     const char spec = f.spec;
620 
621     if (spec == 'r')
622     {
623         // raw write, skip all else and write the thing
624         auto raw = (ref val) @trusted {
625             return (cast(const char*) &val)[0 .. val.sizeof];
626         }(val);
627 
628         if (needToSwapEndianess(f))
629         {
630             foreach_reverse (c; raw)
631                 put(w, c);
632         }
633         else
634         {
635             foreach (c; raw)
636                 put(w, c);
637         }
638         return;
639     }
640 
641     enforceFmt(find("fgFGaAeEs", spec).length,
642         "incompatible format character for floating point argument: %" ~ spec);
643 
644     FormatSpec!Char fs = f; // fs is copy for change its values.
645     fs.spec = spec == 's' ? 'g' : spec;
646 
647     static if (is(T == float) || is(T == double)
648                || (is(T == real) && (T.mant_dig == double.mant_dig || T.mant_dig == 64)))
649     {
650         alias tval = val;
651     }
652     else
653     {
654         import std.math.traits : isInfinity;
655         import std.math.operations : nextUp;
656 
657         // reals that are not supported by printFloat are cast to double.
658         double tval = val;
659 
660         // Numbers greater than double.max are converted to double.max:
661         if (val > double.max && !isInfinity(val))
662             tval = double.max;
663         if (val < -double.max && !isInfinity(val))
664             tval = -double.max;
665 
666         // Numbers between the smallest representable double subnormal and 0.0
667         // are converted to the smallest representable double subnormal:
668         enum doubleLowest = nextUp(0.0);
669         if (val > 0 && val < doubleLowest)
670             tval = doubleLowest;
671         if (val < 0 && val > -doubleLowest)
672             tval = -doubleLowest;
673     }
674 
675     import std.format.internal.floats : printFloat;
676     printFloat(w, tval, fs);
677 }
678 
679 @safe unittest
680 {
681     assert(format("%.1f", 1337.7) == "1337.7");
682     assert(format("%,3.2f", 1331.982) == "1,331.98");
683     assert(format("%,3.0f", 1303.1982) == "1,303");
684     assert(format("%#,3.4f", 1303.1982) == "1,303.1982");
685     assert(format("%#,3.0f", 1303.1982) == "1,303.");
686 }
687 
688 @safe pure unittest
689 {
690     import std.conv : to;
691     import std.exception : collectExceptionMsg;
692     import std.format : FormatException;
693     import std.meta : AliasSeq;
694     import std.range.primitives : back;
695 
696     assert(collectExceptionMsg!FormatException(format("%d", 5.1)).back == 'd');
697 
698     static foreach (T; AliasSeq!(float, double, real))
699     {
700         formatTest(to!(          T)(5.5), "5.5");
701         formatTest(to!(    const T)(5.5), "5.5");
702         formatTest(to!(immutable T)(5.5), "5.5");
703 
704         formatTest(T.nan, "nan");
705     }
706 }
707 
708 @safe unittest
709 {
710     formatTest(2.25, "2.25");
711 
712     class C1
713     {
714         double val;
715         alias val this;
716         this(double v){ val = v; }
717     }
718 
719     class C2
720     {
721         double val;
722         alias val this;
723         this(double v){ val = v; }
724         override string toString() const { return "C"; }
725     }
726 
727     () @trusted {
728         formatTest(new C1(2.25), "2.25");
729         formatTest(new C2(2.25), "C");
730     } ();
731 
732     struct S1
733     {
734         double val;
735         alias val this;
736     }
737     struct S2
738     {
739         double val;
740         alias val this;
741         string toString() const { return "S"; }
742     }
743 
744     formatTest(S1(2.25), "2.25");
745     formatTest(S2(2.25), "S");
746 }
747 
748 // https://issues.dlang.org/show_bug.cgi?id=19939
749 @safe unittest
750 {
751     assert(format("^%13,3.2f$",          1.00) == "^         1.00$");
752     assert(format("^%13,3.2f$",         10.00) == "^        10.00$");
753     assert(format("^%13,3.2f$",        100.00) == "^       100.00$");
754     assert(format("^%13,3.2f$",      1_000.00) == "^     1,000.00$");
755     assert(format("^%13,3.2f$",     10_000.00) == "^    10,000.00$");
756     assert(format("^%13,3.2f$",    100_000.00) == "^   100,000.00$");
757     assert(format("^%13,3.2f$",  1_000_000.00) == "^ 1,000,000.00$");
758     assert(format("^%13,3.2f$", 10_000_000.00) == "^10,000,000.00$");
759 }
760 
761 // https://issues.dlang.org/show_bug.cgi?id=20069
762 @safe unittest
763 {
764     assert(format("%012,f",   -1234.0) ==    "-1,234.000000");
765     assert(format("%013,f",   -1234.0) ==    "-1,234.000000");
766     assert(format("%014,f",   -1234.0) ==   "-01,234.000000");
767     assert(format("%011,f",    1234.0) ==     "1,234.000000");
768     assert(format("%012,f",    1234.0) ==     "1,234.000000");
769     assert(format("%013,f",    1234.0) ==    "01,234.000000");
770     assert(format("%014,f",    1234.0) ==   "001,234.000000");
771     assert(format("%015,f",    1234.0) == "0,001,234.000000");
772     assert(format("%016,f",    1234.0) == "0,001,234.000000");
773 
774     assert(format( "%08,.2f", -1234.0) ==        "-1,234.00");
775     assert(format( "%09,.2f", -1234.0) ==        "-1,234.00");
776     assert(format("%010,.2f", -1234.0) ==       "-01,234.00");
777     assert(format("%011,.2f", -1234.0) ==      "-001,234.00");
778     assert(format("%012,.2f", -1234.0) ==    "-0,001,234.00");
779     assert(format("%013,.2f", -1234.0) ==    "-0,001,234.00");
780     assert(format("%014,.2f", -1234.0) ==   "-00,001,234.00");
781     assert(format( "%08,.2f",  1234.0) ==         "1,234.00");
782     assert(format( "%09,.2f",  1234.0) ==        "01,234.00");
783     assert(format("%010,.2f",  1234.0) ==       "001,234.00");
784     assert(format("%011,.2f",  1234.0) ==     "0,001,234.00");
785     assert(format("%012,.2f",  1234.0) ==     "0,001,234.00");
786     assert(format("%013,.2f",  1234.0) ==    "00,001,234.00");
787     assert(format("%014,.2f",  1234.0) ==   "000,001,234.00");
788     assert(format("%015,.2f",  1234.0) == "0,000,001,234.00");
789     assert(format("%016,.2f",  1234.0) == "0,000,001,234.00");
790 }
791 
792 @safe unittest
793 {
794     import std.math.hardware; // cannot be selective, because FloatingPointControl might not be defined
795 
796     // std.math's FloatingPointControl isn't available on all target platforms
797     static if (is(FloatingPointControl))
798     {
799         assert(FloatingPointControl.rounding == FloatingPointControl.roundToNearest);
800     }
801 
802     // issue 20320
803     real a = 0.16;
804     real b = 0.016;
805     assert(format("%.1f", a) == "0.2");
806     assert(format("%.2f", b) == "0.02");
807 
808     double a1 = 0.16;
809     double b1 = 0.016;
810     assert(format("%.1f", a1) == "0.2");
811     assert(format("%.2f", b1) == "0.02");
812 
813     // issue 9889
814     assert(format("%.1f", 0.09) == "0.1");
815     assert(format("%.1f", -0.09) == "-0.1");
816     assert(format("%.1f", 0.095) == "0.1");
817     assert(format("%.1f", -0.095) == "-0.1");
818     assert(format("%.1f", 0.094) == "0.1");
819     assert(format("%.1f", -0.094) == "-0.1");
820 }
821 
822 @safe unittest
823 {
824     double a = 123.456;
825     double b = -123.456;
826     double c = 123.0;
827 
828     assert(format("%10.4f",a)  == "  123.4560");
829     assert(format("%-10.4f",a) == "123.4560  ");
830     assert(format("%+10.4f",a) == " +123.4560");
831     assert(format("% 10.4f",a) == "  123.4560");
832     assert(format("%010.4f",a) == "00123.4560");
833     assert(format("%#10.4f",a) == "  123.4560");
834 
835     assert(format("%10.4f",b)  == " -123.4560");
836     assert(format("%-10.4f",b) == "-123.4560 ");
837     assert(format("%+10.4f",b) == " -123.4560");
838     assert(format("% 10.4f",b) == " -123.4560");
839     assert(format("%010.4f",b) == "-0123.4560");
840     assert(format("%#10.4f",b) == " -123.4560");
841 
842     assert(format("%10.0f",c)  == "       123");
843     assert(format("%-10.0f",c) == "123       ");
844     assert(format("%+10.0f",c) == "      +123");
845     assert(format("% 10.0f",c) == "       123");
846     assert(format("%010.0f",c) == "0000000123");
847     assert(format("%#10.0f",c) == "      123.");
848 
849     assert(format("%+010.4f",a) == "+0123.4560");
850     assert(format("% 010.4f",a) == " 0123.4560");
851     assert(format("% +010.4f",a) == "+0123.4560");
852 }
853 
854 @safe unittest
855 {
856     string t1 = format("[%6s] [%-6s]", 12.3, 12.3);
857     assert(t1 == "[  12.3] [12.3  ]");
858 
859     string t2 = format("[%6s] [%-6s]", -12.3, -12.3);
860     assert(t2 == "[ -12.3] [-12.3 ]");
861 }
862 
863 // https://issues.dlang.org/show_bug.cgi?id=20396
864 @safe unittest
865 {
866     import std.math.operations : nextUp;
867 
868     assert(format!"%a"(nextUp(0.0f)) == "0x0.000002p-126");
869     assert(format!"%a"(nextUp(0.0)) == "0x0.0000000000001p-1022");
870 }
871 
872 // https://issues.dlang.org/show_bug.cgi?id=20371
873 @safe unittest
874 {
875     assert(format!"%.1000a"(1.0).length == 1007);
876     assert(format!"%.600f"(0.1).length == 602);
877     assert(format!"%.600e"(0.1L).length == 606);
878 }
879 
880 @safe unittest
881 {
882     import std.math.hardware; // cannot be selective, because FloatingPointControl might not be defined
883 
884     // std.math's FloatingPointControl isn't available on all target platforms
885     static if (is(FloatingPointControl))
886     {
887         FloatingPointControl fpctrl;
888 
889         fpctrl.rounding = FloatingPointControl.roundUp;
890         assert(format!"%.0e"(3.5) == "4e+00");
891         assert(format!"%.0e"(4.5) == "5e+00");
892         assert(format!"%.0e"(-3.5) == "-3e+00");
893         assert(format!"%.0e"(-4.5) == "-4e+00");
894 
895         fpctrl.rounding = FloatingPointControl.roundDown;
896         assert(format!"%.0e"(3.5) == "3e+00");
897         assert(format!"%.0e"(4.5) == "4e+00");
898         assert(format!"%.0e"(-3.5) == "-4e+00");
899         assert(format!"%.0e"(-4.5) == "-5e+00");
900 
901         fpctrl.rounding = FloatingPointControl.roundToZero;
902         assert(format!"%.0e"(3.5) == "3e+00");
903         assert(format!"%.0e"(4.5) == "4e+00");
904         assert(format!"%.0e"(-3.5) == "-3e+00");
905         assert(format!"%.0e"(-4.5) == "-4e+00");
906 
907         fpctrl.rounding = FloatingPointControl.roundToNearest;
908         assert(format!"%.0e"(3.5) == "4e+00");
909         assert(format!"%.0e"(4.5) == "4e+00");
910         assert(format!"%.0e"(-3.5) == "-4e+00");
911         assert(format!"%.0e"(-4.5) == "-4e+00");
912     }
913 }
914 
915 @safe pure unittest
916 {
917     static assert(format("%e",1.0) == "1.000000e+00");
918     static assert(format("%e",-1.234e156) == "-1.234000e+156");
919     static assert(format("%a",1.0) == "0x1p+0");
920     static assert(format("%a",-1.234e156) == "-0x1.7024c96ca3ce4p+518");
921     static assert(format("%f",1.0) == "1.000000");
922     static assert(format("%f",-1.234e156) ==
923                   "-123399999999999990477495546305353609103201879173427886566531" ~
924                   "0740685826234179310516880117527217443004051984432279880308552" ~
925                   "009640198043032289366552939010719744.000000");
926     static assert(format("%g",1.0) == "1");
927     static assert(format("%g",-1.234e156) == "-1.234e+156");
928 
929     static assert(format("%e",1.0f) == "1.000000e+00");
930     static assert(format("%e",-1.234e23f) == "-1.234000e+23");
931     static assert(format("%a",1.0f) == "0x1p+0");
932     static assert(format("%a",-1.234e23f) == "-0x1.a2187p+76");
933     static assert(format("%f",1.0f) == "1.000000");
934     static assert(format("%f",-1.234e23f) == "-123399998884238311030784.000000");
935     static assert(format("%g",1.0f) == "1");
936     static assert(format("%g",-1.234e23f) == "-1.234e+23");
937 }
938 
939 // https://issues.dlang.org/show_bug.cgi?id=21641
940 @safe unittest
941 {
942     float a = -999999.8125;
943     assert(format("%#.5g",a) == "-1.0000e+06");
944     assert(format("%#.6g",a) == "-1.00000e+06");
945 }
946 
947 // https://issues.dlang.org/show_bug.cgi?id=8424
948 @safe pure unittest
949 {
950     static assert(format("%s", 0.6f) == "0.6");
951     static assert(format("%s", 0.6) == "0.6");
952     static assert(format("%s", 0.6L) == "0.6");
953 }
954 
955 // https://issues.dlang.org/show_bug.cgi?id=9297
956 @safe pure unittest
957 {
958     static if (real.mant_dig == 64) // 80 bit reals
959     {
960         assert(format("%.25f", 1.6180339887_4989484820_4586834365L) == "1.6180339887498948482072100");
961     }
962 }
963 
964 // https://issues.dlang.org/show_bug.cgi?id=21853
965 @safe pure unittest
966 {
967     import std.math.exponential : log2;
968 
969     // log2 is broken for x87-reals on some computers in CTFE
970     // the following test excludes these computers from the test
971     // (issue 21757)
972     enum test = cast(int) log2(3.05e2312L);
973     static if (real.mant_dig == 64 && test == 7681) // 80 bit reals
974     {
975         static assert(format!"%e"(real.max) == "1.189731e+4932");
976     }
977 }
978 
979 // https://issues.dlang.org/show_bug.cgi?id=21842
980 @safe pure unittest
981 {
982     assert(format!"%-+05,g"(1.0) == "+1   ");
983 }
984 
985 // https://issues.dlang.org/show_bug.cgi?id=20536
986 @safe pure unittest
987 {
988     real r = .00000095367431640625L;
989     assert(format("%a", r) == "0x1p-20");
990 }
991 
992 // https://issues.dlang.org/show_bug.cgi?id=21840
993 @safe pure unittest
994 {
995     assert(format!"% 0,e"(0.0) == " 0.000000e+00");
996 }
997 
998 // https://issues.dlang.org/show_bug.cgi?id=21841
999 @safe pure unittest
1000 {
1001     assert(format!"%0.0,e"(0.0) == "0e+00");
1002 }
1003 
1004 // https://issues.dlang.org/show_bug.cgi?id=21836
1005 @safe pure unittest
1006 {
1007     assert(format!"%-5,1g"(0.0) == "0    ");
1008 }
1009 
1010 // https://issues.dlang.org/show_bug.cgi?id=21838
1011 @safe pure unittest
1012 {
1013     assert(format!"%#,a"(0.0) == "0x0.p+0");
1014 }
1015 
1016 /*
1017     Formatting a `creal` is deprecated but still kept around for a while.
1018  */
1019 deprecated("Use of complex types is deprecated. Use std.complex")
1020 void formatValueImpl(Writer, T, Char)(auto ref Writer w, const(T) obj, scope const ref FormatSpec!Char f)
1021 if (is(immutable T : immutable creal) && !is(T == enum) && !hasToString!(T, Char))
1022 {
1023     import std.range.primitives : put;
1024 
1025     immutable creal val = obj;
1026 
1027     formatValueImpl(w, val.re, f);
1028     if (val.im >= 0)
1029     {
1030         put(w, '+');
1031     }
1032     formatValueImpl(w, val.im, f);
1033     put(w, 'i');
1034 }
1035 
1036 /*
1037     Formatting an `ireal` is deprecated but still kept around for a while.
1038  */
1039 deprecated("Use of imaginary types is deprecated. Use std.complex")
1040 void formatValueImpl(Writer, T, Char)(auto ref Writer w, const(T) obj, scope const ref FormatSpec!Char f)
1041 if (is(immutable T : immutable ireal) && !is(T == enum) && !hasToString!(T, Char))
1042 {
1043     import std.range.primitives : put;
1044 
1045     immutable ireal val = obj;
1046 
1047     formatValueImpl(w, val.im, f);
1048     put(w, 'i');
1049 }
1050 
1051 /*
1052     Individual characters are formatted as Unicode characters with `%s`
1053     and as integers with integral-specific format specs
1054  */
1055 void formatValueImpl(Writer, T, Char)(auto ref Writer w, const(T) obj, scope const ref FormatSpec!Char f)
1056 if (is(CharTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
1057 {
1058     import std.meta : AliasSeq;
1059 
1060     CharTypeOf!T[1] val = obj;
1061 
1062     if (f.spec == 's' || f.spec == 'c')
1063         writeAligned(w, val[], f);
1064     else
1065     {
1066         alias U = AliasSeq!(ubyte, ushort, uint)[CharTypeOf!T.sizeof/2];
1067         formatValueImpl(w, cast(U) val[0], f);
1068     }
1069 }
1070 
1071 @safe pure unittest
1072 {
1073     assertCTFEable!(
1074     {
1075         formatTest('c', "c");
1076     });
1077 }
1078 
1079 @safe unittest
1080 {
1081     class C1
1082     {
1083         char val;
1084         alias val this;
1085         this(char v){ val = v; }
1086     }
1087 
1088     class C2
1089     {
1090         char val;
1091         alias val this;
1092         this(char v){ val = v; }
1093         override string toString() const { return "C"; }
1094     }
1095 
1096     () @trusted {
1097         formatTest(new C1('c'), "c");
1098         formatTest(new C2('c'), "C");
1099     } ();
1100 
1101     struct S1
1102     {
1103         char val;
1104         alias val this;
1105     }
1106 
1107     struct S2
1108     {
1109         char val;
1110         alias val this;
1111         string toString() const { return "S"; }
1112     }
1113 
1114     formatTest(S1('c'), "c");
1115     formatTest(S2('c'), "S");
1116 }
1117 
1118 @safe unittest
1119 {
1120     //Little Endian
1121     formatTest("%-r", cast( char)'c', ['c'         ]);
1122     formatTest("%-r", cast(wchar)'c', ['c', 0      ]);
1123     formatTest("%-r", cast(dchar)'c', ['c', 0, 0, 0]);
1124     formatTest("%-r", '本', ['\x2c', '\x67'] );
1125 
1126     //Big Endian
1127     formatTest("%+r", cast( char)'c', [         'c']);
1128     formatTest("%+r", cast(wchar)'c', [0,       'c']);
1129     formatTest("%+r", cast(dchar)'c', [0, 0, 0, 'c']);
1130     formatTest("%+r", '本', ['\x67', '\x2c']);
1131 }
1132 
1133 
1134 @safe pure unittest
1135 {
1136     string t1 = format("[%6s] [%-6s]", 'A', 'A');
1137     assert(t1 == "[     A] [A     ]");
1138     string t2 = format("[%6s] [%-6s]", '本', '本');
1139     assert(t2 == "[     本] [本     ]");
1140 }
1141 
1142 /*
1143     Strings are formatted like $(REF printf, core, stdc, stdio)
1144  */
1145 void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope const(T) obj,
1146     scope const ref FormatSpec!Char f)
1147 if (is(StringTypeOf!T) && !is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
1148 {
1149     Unqual!(const(StringTypeOf!T)) val = obj;  // for `alias this`, see bug5371
1150     formatRange(w, val, f);
1151 }
1152 
1153 @safe unittest
1154 {
1155     formatTest("abc", "abc");
1156 }
1157 
1158 @safe pure unittest
1159 {
1160     import std.exception : collectExceptionMsg;
1161     import std.range.primitives : back;
1162 
1163     assert(collectExceptionMsg(format("%d", "hi")).back == 'd');
1164 }
1165 
1166 @safe unittest
1167 {
1168     // Test for bug 5371 for classes
1169     class C1
1170     {
1171         const string var;
1172         alias var this;
1173         this(string s){ var = s; }
1174     }
1175 
1176     class C2
1177     {
1178         string var;
1179         alias var this;
1180         this(string s){ var = s; }
1181     }
1182 
1183     () @trusted {
1184         formatTest(new C1("c1"), "c1");
1185         formatTest(new C2("c2"), "c2");
1186     } ();
1187 
1188     // Test for bug 5371 for structs
1189     struct S1
1190     {
1191         const string var;
1192         alias var this;
1193     }
1194 
1195     struct S2
1196     {
1197         string var;
1198         alias var this;
1199     }
1200 
1201     formatTest(S1("s1"), "s1");
1202     formatTest(S2("s2"), "s2");
1203 }
1204 
1205 @safe unittest
1206 {
1207     class C3
1208     {
1209         string val;
1210         alias val this;
1211         this(string s){ val = s; }
1212         override string toString() const { return "C"; }
1213     }
1214 
1215     () @trusted { formatTest(new C3("c3"), "C"); } ();
1216 
1217     struct S3
1218     {
1219         string val; alias val this;
1220         string toString() const { return "S"; }
1221     }
1222 
1223     formatTest(S3("s3"), "S");
1224 }
1225 
1226 @safe pure unittest
1227 {
1228     //Little Endian
1229     formatTest("%-r", "ab"c, ['a'         , 'b'         ]);
1230     formatTest("%-r", "ab"w, ['a', 0      , 'b', 0      ]);
1231     formatTest("%-r", "ab"d, ['a', 0, 0, 0, 'b', 0, 0, 0]);
1232     formatTest("%-r", "日本語"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac',
1233                                   '\xe8', '\xaa', '\x9e']);
1234     formatTest("%-r", "日本語"w, ['\xe5', '\x65', '\x2c', '\x67', '\x9e', '\x8a']);
1235     formatTest("%-r", "日本語"d, ['\xe5', '\x65', '\x00', '\x00', '\x2c', '\x67',
1236                                   '\x00', '\x00', '\x9e', '\x8a', '\x00', '\x00']);
1237 
1238     //Big Endian
1239     formatTest("%+r", "ab"c, [         'a',          'b']);
1240     formatTest("%+r", "ab"w, [      0, 'a',       0, 'b']);
1241     formatTest("%+r", "ab"d, [0, 0, 0, 'a', 0, 0, 0, 'b']);
1242     formatTest("%+r", "日本語"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac',
1243                                   '\xe8', '\xaa', '\x9e']);
1244     formatTest("%+r", "日本語"w, ['\x65', '\xe5', '\x67', '\x2c', '\x8a', '\x9e']);
1245     formatTest("%+r", "日本語"d, ['\x00', '\x00', '\x65', '\xe5', '\x00', '\x00',
1246                                   '\x67', '\x2c', '\x00', '\x00', '\x8a', '\x9e']);
1247 }
1248 
1249 @safe pure unittest
1250 {
1251     string t1 = format("[%6s] [%-6s]", "AB", "AB");
1252     assert(t1 == "[    AB] [AB    ]");
1253     string t2 = format("[%6s] [%-6s]", "本Ä", "本Ä");
1254     assert(t2 == "[    本Ä] [本Ä    ]");
1255 }
1256 
1257 // https://issues.dlang.org/show_bug.cgi?id=6640
1258 @safe unittest
1259 {
1260     import std.range.primitives : front, popFront;
1261 
1262     struct Range
1263     {
1264         @safe:
1265 
1266         string value;
1267         @property bool empty() const { return !value.length; }
1268         @property dchar front() const { return value.front; }
1269         void popFront() { value.popFront(); }
1270 
1271         @property size_t length() const { return value.length; }
1272     }
1273     immutable table =
1274     [
1275         ["[%s]", "[string]"],
1276         ["[%10s]", "[    string]"],
1277         ["[%-10s]", "[string    ]"],
1278         ["[%(%02x %)]", "[73 74 72 69 6e 67]"],
1279         ["[%(%c %)]", "[s t r i n g]"],
1280     ];
1281     foreach (e; table)
1282     {
1283         formatTest(e[0], "string", e[1]);
1284         formatTest(e[0], Range("string"), e[1]);
1285     }
1286 }
1287 
1288 @safe unittest
1289 {
1290     import std.meta : AliasSeq;
1291 
1292     // string literal from valid UTF sequence is encoding free.
1293     static foreach (StrType; AliasSeq!(string, wstring, dstring))
1294     {
1295         // Valid and printable (ASCII)
1296         formatTest([cast(StrType)"hello"],
1297                    `["hello"]`);
1298 
1299         // 1 character escape sequences (' is not escaped in strings)
1300         formatTest([cast(StrType)"\"'\0\\\a\b\f\n\r\t\v"],
1301                    `["\"'\0\\\a\b\f\n\r\t\v"]`);
1302 
1303         // 1 character optional escape sequences
1304         formatTest([cast(StrType)"\'\?"],
1305                    `["'?"]`);
1306 
1307         // Valid and non-printable code point (<= U+FF)
1308         formatTest([cast(StrType)"\x10\x1F\x20test"],
1309                    `["\x10\x1F test"]`);
1310 
1311         // Valid and non-printable code point (<= U+FFFF)
1312         formatTest([cast(StrType)"\u200B..\u200F"],
1313                    `["\u200B..\u200F"]`);
1314 
1315         // Valid and non-printable code point (<= U+10FFFF)
1316         formatTest([cast(StrType)"\U000E0020..\U000E007F"],
1317                    `["\U000E0020..\U000E007F"]`);
1318     }
1319 
1320     // invalid UTF sequence needs hex-string literal postfix (c/w/d)
1321     () @trusted
1322     {
1323         // U+FFFF with UTF-8 (Invalid code point for interchange)
1324         formatTest([cast(string)[0xEF, 0xBF, 0xBF]],
1325                    `[[cast(char) 0xEF, cast(char) 0xBF, cast(char) 0xBF]]`);
1326 
1327         // U+FFFF with UTF-16 (Invalid code point for interchange)
1328         formatTest([cast(wstring)[0xFFFF]],
1329                    `[[cast(wchar) 0xFFFF]]`);
1330 
1331         // U+FFFF with UTF-32 (Invalid code point for interchange)
1332         formatTest([cast(dstring)[0xFFFF]],
1333                    `[[cast(dchar) 0xFFFF]]`);
1334     } ();
1335 }
1336 
1337 /*
1338     Static-size arrays are formatted as dynamic arrays.
1339  */
1340 void formatValueImpl(Writer, T, Char)(auto ref Writer w, auto ref const(T) obj,
1341     scope const ref FormatSpec!Char f)
1342 if (is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
1343 {
1344     formatValueImpl(w, obj[], f);
1345 }
1346 
1347 // Test for https://issues.dlang.org/show_bug.cgi?id=8310
1348 @safe unittest
1349 {
1350     import std.array : appender;
1351     import std.format : formatValue;
1352 
1353     FormatSpec!char f;
1354     auto w = appender!string();
1355 
1356     char[2] two = ['a', 'b'];
1357     formatValue(w, two, f);
1358 
1359     char[2] getTwo() { return two; }
1360     formatValue(w, getTwo(), f);
1361 }
1362 
1363 // https://issues.dlang.org/show_bug.cgi?id=18205
1364 @safe pure unittest
1365 {
1366     assert("|%8s|".format("abc")       == "|     abc|");
1367     assert("|%8s|".format("αβγ")       == "|     αβγ|");
1368     assert("|%8s|".format("   ")       == "|        |");
1369     assert("|%8s|".format("été"d)      == "|     été|");
1370     assert("|%8s|".format("été 2018"w) == "|été 2018|");
1371 
1372     assert("%2s".format("e\u0301"w) == " e\u0301");
1373     assert("%2s".format("a\u0310\u0337"d) == " a\u0310\u0337");
1374 }
1375 
1376 /*
1377     Dynamic arrays are formatted as input ranges.
1378  */
1379 void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
1380 if (is(DynamicArrayTypeOf!T) && !is(StringTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
1381 {
1382     static if (is(immutable(ArrayTypeOf!T) == immutable(void[])))
1383     {
1384         formatValueImpl(w, cast(const ubyte[]) obj, f);
1385     }
1386     else static if (!isInputRange!T)
1387     {
1388         alias U = Unqual!(ArrayTypeOf!T);
1389         static assert(isInputRange!U, U.stringof ~ " must be an InputRange");
1390         U val = obj;
1391         formatValueImpl(w, val, f);
1392     }
1393     else
1394     {
1395         formatRange(w, obj, f);
1396     }
1397 }
1398 
1399 // https://issues.dlang.org/show_bug.cgi?id=20848
1400 @safe unittest
1401 {
1402     class C
1403     {
1404         immutable(void)[] data;
1405     }
1406 
1407     import std.typecons : Nullable;
1408     Nullable!C c;
1409 }
1410 
1411 // alias this, input range I/F, and toString()
1412 @safe unittest
1413 {
1414     struct S(int flags)
1415     {
1416         int[] arr;
1417         static if (flags & 1)
1418             alias arr this;
1419 
1420         static if (flags & 2)
1421         {
1422             @property bool empty() const { return arr.length == 0; }
1423             @property int front() const { return arr[0] * 2; }
1424             void popFront() { arr = arr[1 .. $]; }
1425         }
1426 
1427         static if (flags & 4)
1428             string toString() const { return "S"; }
1429     }
1430 
1431     formatTest(S!0b000([0, 1, 2]), "S!0([0, 1, 2])");
1432     formatTest(S!0b001([0, 1, 2]), "[0, 1, 2]");        // Test for bug 7628
1433     formatTest(S!0b010([0, 1, 2]), "[0, 2, 4]");
1434     formatTest(S!0b011([0, 1, 2]), "[0, 2, 4]");
1435     formatTest(S!0b100([0, 1, 2]), "S");
1436     formatTest(S!0b101([0, 1, 2]), "S");                // Test for bug 7628
1437     formatTest(S!0b110([0, 1, 2]), "S");
1438     formatTest(S!0b111([0, 1, 2]), "S");
1439 
1440     class C(uint flags)
1441     {
1442         int[] arr;
1443         static if (flags & 1)
1444             alias arr this;
1445 
1446         this(int[] a) { arr = a; }
1447 
1448         static if (flags & 2)
1449         {
1450             @property bool empty() const { return arr.length == 0; }
1451             @property int front() const { return arr[0] * 2; }
1452             void popFront() { arr = arr[1 .. $]; }
1453         }
1454 
1455         static if (flags & 4)
1456             override string toString() const { return "C"; }
1457     }
1458 
1459     () @trusted {
1460         formatTest(new C!0b000([0, 1, 2]), (new C!0b000([])).toString());
1461         formatTest(new C!0b001([0, 1, 2]), "[0, 1, 2]");    // Test for bug 7628
1462         formatTest(new C!0b010([0, 1, 2]), "[0, 2, 4]");
1463         formatTest(new C!0b011([0, 1, 2]), "[0, 2, 4]");
1464         formatTest(new C!0b100([0, 1, 2]), "C");
1465         formatTest(new C!0b101([0, 1, 2]), "C");            // Test for bug 7628
1466         formatTest(new C!0b110([0, 1, 2]), "C");
1467         formatTest(new C!0b111([0, 1, 2]), "C");
1468     } ();
1469 }
1470 
1471 @safe unittest
1472 {
1473     // void[]
1474     void[] val0;
1475     formatTest(val0, "[]");
1476 
1477     void[] val = cast(void[]) cast(ubyte[])[1, 2, 3];
1478     formatTest(val, "[1, 2, 3]");
1479 
1480     void[0] sval0 = [];
1481     formatTest(sval0, "[]");
1482 
1483     void[3] sval = () @trusted { return cast(void[3]) cast(ubyte[3])[1, 2, 3]; } ();
1484     formatTest(sval, "[1, 2, 3]");
1485 }
1486 
1487 @safe unittest
1488 {
1489     // const(T[]) -> const(T)[]
1490     const short[] a = [1, 2, 3];
1491     formatTest(a, "[1, 2, 3]");
1492 
1493     struct S
1494     {
1495         const(int[]) arr;
1496         alias arr this;
1497     }
1498 
1499     auto s = S([1,2,3]);
1500     formatTest(s, "[1, 2, 3]");
1501 }
1502 
1503 @safe unittest
1504 {
1505     // nested range formatting with array of string
1506     formatTest("%({%(%02x %)}%| %)", ["test", "msg"],
1507                `{74 65 73 74} {6d 73 67}`);
1508 }
1509 
1510 @safe unittest
1511 {
1512     // stop auto escaping inside range formatting
1513     auto arr = ["hello", "world"];
1514     formatTest("%(%s, %)",  arr, `"hello", "world"`);
1515     formatTest("%-(%s, %)", arr, `hello, world`);
1516 
1517     auto aa1 = [1:"hello", 2:"world"];
1518     formatTest("%(%s:%s, %)",  aa1, [`1:"hello", 2:"world"`, `2:"world", 1:"hello"`]);
1519     formatTest("%-(%s:%s, %)", aa1, [`1:hello, 2:world`, `2:world, 1:hello`]);
1520 
1521     auto aa2 = [1:["ab", "cd"], 2:["ef", "gh"]];
1522     formatTest("%-(%s:%s, %)",        aa2, [`1:["ab", "cd"], 2:["ef", "gh"]`, `2:["ef", "gh"], 1:["ab", "cd"]`]);
1523     formatTest("%-(%s:%(%s%), %)",    aa2, [`1:"ab""cd", 2:"ef""gh"`, `2:"ef""gh", 1:"ab""cd"`]);
1524     formatTest("%-(%s:%-(%s%)%|, %)", aa2, [`1:abcd, 2:efgh`, `2:efgh, 1:abcd`]);
1525 }
1526 
1527 // https://issues.dlang.org/show_bug.cgi?id=18778
1528 @safe pure unittest
1529 {
1530     assert(format("%-(%1$s - %1$s, %)", ["A", "B", "C"]) == "A - A, B - B, C - C");
1531 }
1532 
1533 @safe pure unittest
1534 {
1535     int[] a = [ 1, 3, 2 ];
1536     formatTest("testing %(%s & %) embedded", a,
1537                "testing 1 & 3 & 2 embedded");
1538     formatTest("testing %((%s) %)) wyda3", a,
1539                "testing (1) (3) (2) wyda3");
1540 
1541     int[0] empt = [];
1542     formatTest("(%s)", empt, "([])");
1543 }
1544 
1545 // input range formatting
1546 private void formatRange(Writer, T, Char)(ref Writer w, ref T val, scope const ref FormatSpec!Char f)
1547 if (isInputRange!T)
1548 {
1549     import std.conv : text;
1550     import std.format : FormatException, formatValue, NoOpSink;
1551     import std.range.primitives : ElementType, empty, front, hasLength,
1552         walkLength, isForwardRange, isInfinite, popFront, put;
1553 
1554     // in this mode, we just want to do a representative print to discover
1555     // if the format spec is valid
1556     enum formatTestMode = is(Writer == NoOpSink);
1557 
1558     // Formatting character ranges like string
1559     if (f.spec == 's')
1560     {
1561         alias E = ElementType!T;
1562 
1563         static if (!is(E == enum) && is(CharTypeOf!E))
1564         {
1565             static if (is(StringTypeOf!T))
1566                 writeAligned(w, val[0 .. f.precision < $ ? f.precision : $], f);
1567             else
1568             {
1569                 if (!f.flDash)
1570                 {
1571                     static if (hasLength!T)
1572                     {
1573                         // right align
1574                         auto len = val.length;
1575                     }
1576                     else static if (isForwardRange!T && !isInfinite!T)
1577                     {
1578                         auto len = walkLength(val.save);
1579                     }
1580                     else
1581                     {
1582                         import std.format : enforceFmt;
1583                         enforceFmt(f.width == 0, "Cannot right-align a range without length");
1584                         size_t len = 0;
1585                     }
1586                     if (f.precision != f.UNSPECIFIED && len > f.precision)
1587                         len = f.precision;
1588 
1589                     if (f.width > len)
1590                         foreach (i ; 0 .. f.width - len)
1591                             put(w, ' ');
1592                     if (f.precision == f.UNSPECIFIED)
1593                         put(w, val);
1594                     else
1595                     {
1596                         size_t printed = 0;
1597                         for (; !val.empty && printed < f.precision; val.popFront(), ++printed)
1598                             put(w, val.front);
1599                     }
1600                 }
1601                 else
1602                 {
1603                     size_t printed = void;
1604 
1605                     // left align
1606                     if (f.precision == f.UNSPECIFIED)
1607                     {
1608                         static if (hasLength!T)
1609                         {
1610                             printed = val.length;
1611                             put(w, val);
1612                         }
1613                         else
1614                         {
1615                             printed = 0;
1616                             for (; !val.empty; val.popFront(), ++printed)
1617                             {
1618                                 put(w, val.front);
1619                                 static if (formatTestMode) break; // one is enough to test
1620                             }
1621                         }
1622                     }
1623                     else
1624                     {
1625                         printed = 0;
1626                         for (; !val.empty && printed < f.precision; val.popFront(), ++printed)
1627                             put(w, val.front);
1628                     }
1629 
1630                     if (f.width > printed)
1631                         foreach (i ; 0 .. f.width - printed)
1632                             put(w, ' ');
1633                 }
1634             }
1635         }
1636         else
1637         {
1638             put(w, f.seqBefore);
1639             if (!val.empty)
1640             {
1641                 formatElement(w, val.front, f);
1642                 val.popFront();
1643                 for (size_t i; !val.empty; val.popFront(), ++i)
1644                 {
1645                     put(w, f.seqSeparator);
1646                     formatElement(w, val.front, f);
1647                     static if (formatTestMode) break; // one is enough to test
1648                 }
1649             }
1650             static if (!isInfinite!T) put(w, f.seqAfter);
1651         }
1652     }
1653     else if (f.spec == 'r')
1654     {
1655         static if (is(DynamicArrayTypeOf!T))
1656         {
1657             alias ARR = DynamicArrayTypeOf!T;
1658             scope a = cast(ARR) val;
1659             foreach (e ; a)
1660             {
1661                 formatValue(w, e, f);
1662                 static if (formatTestMode) break; // one is enough to test
1663             }
1664         }
1665         else
1666         {
1667             for (size_t i; !val.empty; val.popFront(), ++i)
1668             {
1669                 formatValue(w, val.front, f);
1670                 static if (formatTestMode) break; // one is enough to test
1671             }
1672         }
1673     }
1674     else if (f.spec == '(')
1675     {
1676         if (val.empty)
1677             return;
1678         // Nested specifier is to be used
1679         for (;;)
1680         {
1681             auto fmt = FormatSpec!Char(f.nested);
1682             w: while (true)
1683             {
1684                 immutable r = fmt.writeUpToNextSpec(w);
1685                 // There was no format specifier, so break
1686                 if (!r)
1687                     break;
1688                 if (f.flDash)
1689                     formatValue(w, val.front, fmt);
1690                 else
1691                     formatElement(w, val.front, fmt);
1692                 // Check if there will be a format specifier farther on in the
1693                 // string. If so, continue the loop, otherwise break. This
1694                 // prevents extra copies of the `sep` from showing up.
1695                 foreach (size_t i; 0 .. fmt.trailing.length)
1696                     if (fmt.trailing[i] == '%')
1697                         continue w;
1698                 break w;
1699             }
1700             static if (formatTestMode)
1701             {
1702                 break; // one is enough to test
1703             }
1704             else
1705             {
1706                 if (f.sep !is null)
1707                 {
1708                     put(w, fmt.trailing);
1709                     val.popFront();
1710                     if (val.empty)
1711                         break;
1712                     put(w, f.sep);
1713                 }
1714                 else
1715                 {
1716                     val.popFront();
1717                     if (val.empty)
1718                         break;
1719                     put(w, fmt.trailing);
1720                 }
1721             }
1722         }
1723     }
1724     else
1725         throw new FormatException(text("Incorrect format specifier for range: %", f.spec));
1726 }
1727 
1728 // https://issues.dlang.org/show_bug.cgi?id=20218
1729 @safe pure unittest
1730 {
1731     void notCalled()
1732     {
1733         import std.range : repeat;
1734 
1735         auto value = 1.repeat;
1736 
1737         // test that range is not evaluated to completion at compiletime
1738         format!"%s"(value);
1739     }
1740 }
1741 
1742 // character formatting with ecaping
1743 void formatChar(Writer)(ref Writer w, in dchar c, in char quote)
1744 {
1745     import std.format : formattedWrite;
1746     import std.range.primitives : put;
1747     import std.uni : isGraphical;
1748 
1749     string fmt;
1750     if (isGraphical(c))
1751     {
1752         if (c == quote || c == '\\')
1753             put(w, '\\');
1754         put(w, c);
1755         return;
1756     }
1757     else if (c <= 0xFF)
1758     {
1759         if (c < 0x20)
1760         {
1761             foreach (i, k; "\n\r\t\a\b\f\v\0")
1762             {
1763                 if (c == k)
1764                 {
1765                     put(w, '\\');
1766                     put(w, "nrtabfv0"[i]);
1767                     return;
1768                 }
1769             }
1770         }
1771         fmt = "\\x%02X";
1772     }
1773     else if (c <= 0xFFFF)
1774         fmt = "\\u%04X";
1775     else
1776         fmt = "\\U%08X";
1777 
1778     formattedWrite(w, fmt, cast(uint) c);
1779 }
1780 
1781 /*
1782     Associative arrays are formatted by using `':'` and $(D ", ") as
1783     separators, and enclosed by `'['` and `']'`.
1784  */
1785 void formatValueImpl(Writer, T, Char)(auto ref Writer w, const(T) obj, scope const ref FormatSpec!Char f)
1786 if (is(AssocArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
1787 {
1788     import std.format : enforceFmt, formatValue;
1789     import std.range.primitives : put;
1790 
1791     AssocArrayTypeOf!(const(T)) val = obj;
1792     const spec = f.spec;
1793 
1794     enforceFmt(spec == 's' || spec == '(',
1795         "incompatible format character for associative array argument: %" ~ spec);
1796 
1797     enum const(Char)[] defSpec = "%s" ~ f.keySeparator ~ "%s" ~ f.seqSeparator;
1798     auto fmtSpec = spec == '(' ? f.nested : defSpec;
1799 
1800     auto key_first = true;
1801 
1802     // testing correct nested format spec
1803     import std.format : NoOpSink;
1804     auto noop = NoOpSink();
1805     auto test = FormatSpec!Char(fmtSpec);
1806     enforceFmt(test.writeUpToNextSpec(noop),
1807         "nested format string for associative array contains no format specifier");
1808     enforceFmt(test.indexStart <= 2,
1809         "positional parameter in nested format string for associative array may only be 1 or 2");
1810     if (test.indexStart == 2)
1811         key_first = false;
1812 
1813     enforceFmt(test.writeUpToNextSpec(noop),
1814         "nested format string for associative array contains only one format specifier");
1815     enforceFmt(test.indexStart <= 2,
1816         "positional parameter in nested format string for associative array may only be 1 or 2");
1817     enforceFmt(test.indexStart == 0 || ((test.indexStart == 2) == key_first),
1818         "wrong combination of positional parameters in nested format string");
1819 
1820     enforceFmt(!test.writeUpToNextSpec(noop),
1821         "nested format string for associative array contains more than two format specifiers");
1822 
1823     size_t i = 0;
1824     immutable end = val.length;
1825 
1826     if (spec == 's')
1827         put(w, f.seqBefore);
1828     foreach (k, ref v; val)
1829     {
1830         auto fmt = FormatSpec!Char(fmtSpec);
1831 
1832         foreach (pos; 1 .. 3)
1833         {
1834             fmt.writeUpToNextSpec(w);
1835 
1836             if (key_first == (pos == 1))
1837             {
1838                 if (f.flDash)
1839                     formatValue(w, k, fmt);
1840                 else
1841                     formatElement(w, k, fmt);
1842             }
1843             else
1844             {
1845                 if (f.flDash)
1846                     formatValue(w, v, fmt);
1847                 else
1848                     formatElement(w, v, fmt);
1849             }
1850         }
1851 
1852         if (f.sep !is null)
1853         {
1854             fmt.writeUpToNextSpec(w);
1855             if (++i != end)
1856                 put(w, f.sep);
1857         }
1858         else
1859         {
1860             if (++i != end)
1861                 fmt.writeUpToNextSpec(w);
1862         }
1863     }
1864     if (spec == 's')
1865         put(w, f.seqAfter);
1866 }
1867 
1868 @safe unittest
1869 {
1870     import std.exception : collectExceptionMsg;
1871     import std.format : FormatException;
1872     import std.range.primitives : back;
1873 
1874     assert(collectExceptionMsg!FormatException(format("%d", [0:1])).back == 'd');
1875 
1876     int[string] aa0;
1877     formatTest(aa0, `[]`);
1878 
1879     // elements escaping
1880     formatTest(["aaa":1, "bbb":2],
1881                [`["aaa":1, "bbb":2]`, `["bbb":2, "aaa":1]`]);
1882     formatTest(['c':"str"],
1883                `['c':"str"]`);
1884     formatTest(['"':"\"", '\'':"'"],
1885                [`['"':"\"", '\'':"'"]`, `['\'':"'", '"':"\""]`]);
1886 
1887     // range formatting for AA
1888     auto aa3 = [1:"hello", 2:"world"];
1889     // escape
1890     formatTest("{%(%s:%s $ %)}", aa3,
1891                [`{1:"hello" $ 2:"world"}`, `{2:"world" $ 1:"hello"}`]);
1892     // use range formatting for key and value, and use %|
1893     formatTest("{%([%04d->%(%c.%)]%| $ %)}", aa3,
1894                [`{[0001->h.e.l.l.o] $ [0002->w.o.r.l.d]}`,
1895                 `{[0002->w.o.r.l.d] $ [0001->h.e.l.l.o]}`]);
1896 
1897     // https://issues.dlang.org/show_bug.cgi?id=12135
1898     formatTest("%(%s:<%s>%|,%)", [1:2], "1:<2>");
1899     formatTest("%(%s:<%s>%|%)" , [1:2], "1:<2>");
1900 }
1901 
1902 @safe unittest
1903 {
1904     class C1
1905     {
1906         int[char] val;
1907         alias val this;
1908         this(int[char] v){ val = v; }
1909     }
1910 
1911     class C2
1912     {
1913         int[char] val;
1914         alias val this;
1915         this(int[char] v){ val = v; }
1916         override string toString() const { return "C"; }
1917     }
1918 
1919     () @trusted {
1920         formatTest(new C1(['c':1, 'd':2]), [`['c':1, 'd':2]`, `['d':2, 'c':1]`]);
1921         formatTest(new C2(['c':1, 'd':2]), "C");
1922     } ();
1923 
1924     struct S1
1925     {
1926         int[char] val;
1927         alias val this;
1928     }
1929 
1930     struct S2
1931     {
1932         int[char] val;
1933         alias val this;
1934         string toString() const { return "S"; }
1935     }
1936 
1937     formatTest(S1(['c':1, 'd':2]), [`['c':1, 'd':2]`, `['d':2, 'c':1]`]);
1938     formatTest(S2(['c':1, 'd':2]), "S");
1939 }
1940 
1941 // https://issues.dlang.org/show_bug.cgi?id=21875
1942 @safe unittest
1943 {
1944     import std.exception : assertThrown;
1945     import std.format : FormatException;
1946 
1947     auto aa = [ 1 : "x", 2 : "y", 3 : "z" ];
1948 
1949     assertThrown!FormatException(format("%(%)", aa));
1950     assertThrown!FormatException(format("%(%s%)", aa));
1951     assertThrown!FormatException(format("%(%s%s%s%)", aa));
1952 }
1953 
1954 @safe unittest
1955 {
1956     import std.exception : assertThrown;
1957     import std.format : FormatException;
1958 
1959     auto aa = [ 1 : "x", 2 : "y", 3 : "z" ];
1960 
1961     assertThrown!FormatException(format("%(%3$s%s%)", aa));
1962     assertThrown!FormatException(format("%(%s%3$s%)", aa));
1963     assertThrown!FormatException(format("%(%1$s%1$s%)", aa));
1964     assertThrown!FormatException(format("%(%2$s%2$s%)", aa));
1965     assertThrown!FormatException(format("%(%s%1$s%)", aa));
1966 }
1967 
1968 // https://issues.dlang.org/show_bug.cgi?id=21808
1969 @safe unittest
1970 {
1971     auto spelled = [ 1 : "one" ];
1972     assert(format("%-(%2$s (%1$s)%|, %)", spelled) == "one (1)");
1973 
1974     spelled[2] = "two";
1975     auto result = format("%-(%2$s (%1$s)%|, %)", spelled);
1976     assert(result == "one (1), two (2)" || result == "two (2), one (1)");
1977 }
1978 
1979 enum HasToStringResult
1980 {
1981     none,
1982     hasSomeToString,
1983     inCharSink,
1984     inCharSinkFormatString,
1985     inCharSinkFormatSpec,
1986     constCharSink,
1987     constCharSinkFormatString,
1988     constCharSinkFormatSpec,
1989     customPutWriter,
1990     customPutWriterFormatSpec,
1991 }
1992 
1993 private enum hasPreviewIn = !is(typeof(mixin(q{(in ref int a) => a})));
1994 
1995 template hasToString(T, Char)
1996 {
1997     static if (isPointer!T)
1998     {
1999         // X* does not have toString, even if X is aggregate type has toString.
2000         enum hasToString = HasToStringResult.none;
2001     }
2002     else static if (is(typeof(
2003         (T val) {
2004             const FormatSpec!Char f;
2005             static struct S {void put(scope Char s){}}
2006             S s;
2007             val.toString(s, f);
2008             static assert(!__traits(compiles, val.toString(s, FormatSpec!Char())),
2009                           "force toString to take parameters by ref");
2010             static assert(!__traits(compiles, val.toString(S(), f)),
2011                           "force toString to take parameters by ref");
2012         })))
2013     {
2014         enum hasToString = HasToStringResult.customPutWriterFormatSpec;
2015     }
2016     else static if (is(typeof(
2017         (T val) {
2018             static struct S {void put(scope Char s){}}
2019             S s;
2020             val.toString(s);
2021             static assert(!__traits(compiles, val.toString(S())),
2022                           "force toString to take parameters by ref");
2023         })))
2024     {
2025         enum hasToString = HasToStringResult.customPutWriter;
2026     }
2027     else static if (is(typeof((T val) { FormatSpec!Char f; val.toString((scope const(char)[] s){}, f); })))
2028     {
2029         enum hasToString = HasToStringResult.constCharSinkFormatSpec;
2030     }
2031     else static if (is(typeof((T val) { val.toString((scope const(char)[] s){}, "%s"); })))
2032     {
2033         enum hasToString = HasToStringResult.constCharSinkFormatString;
2034     }
2035     else static if (is(typeof((T val) { val.toString((scope const(char)[] s){}); })))
2036     {
2037         enum hasToString = HasToStringResult.constCharSink;
2038     }
2039 
2040     else static if (hasPreviewIn &&
2041                     is(typeof((T val) { FormatSpec!Char f; val.toString((in char[] s){}, f); })))
2042     {
2043         enum hasToString = HasToStringResult.inCharSinkFormatSpec;
2044     }
2045     else static if (hasPreviewIn &&
2046                     is(typeof((T val) { val.toString((in char[] s){}, "%s"); })))
2047     {
2048         enum hasToString = HasToStringResult.inCharSinkFormatString;
2049     }
2050     else static if (hasPreviewIn &&
2051                     is(typeof((T val) { val.toString((in char[] s){}); })))
2052     {
2053         enum hasToString = HasToStringResult.inCharSink;
2054     }
2055 
2056     else static if (is(ReturnType!((T val) { return val.toString(); }) S) && isSomeString!S)
2057     {
2058         enum hasToString = HasToStringResult.hasSomeToString;
2059     }
2060     else
2061     {
2062         enum hasToString = HasToStringResult.none;
2063     }
2064 }
2065 
2066 @safe unittest
2067 {
2068     import std.range.primitives : isOutputRange;
2069 
2070     static struct A
2071     {
2072         void toString(Writer)(ref Writer w)
2073         if (isOutputRange!(Writer, string))
2074         {}
2075     }
2076     static struct B
2077     {
2078         void toString(scope void delegate(scope const(char)[]) sink, scope FormatSpec!char fmt) {}
2079     }
2080     static struct C
2081     {
2082         void toString(scope void delegate(scope const(char)[]) sink, string fmt) {}
2083     }
2084     static struct D
2085     {
2086         void toString(scope void delegate(scope const(char)[]) sink) {}
2087     }
2088     static struct E
2089     {
2090         string toString() {return "";}
2091     }
2092     static struct F
2093     {
2094         void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt)
2095         if (isOutputRange!(Writer, string))
2096         {}
2097     }
2098     static struct G
2099     {
2100         string toString() {return "";}
2101         void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, string)) {}
2102     }
2103     static struct H
2104     {
2105         string toString() {return "";}
2106         void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt)
2107         if (isOutputRange!(Writer, string))
2108         {}
2109     }
2110     static struct I
2111     {
2112         void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, string)) {}
2113         void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt)
2114         if (isOutputRange!(Writer, string))
2115         {}
2116     }
2117     static struct J
2118     {
2119         string toString() {return "";}
2120         void toString(Writer)(ref Writer w, scope ref FormatSpec!char fmt)
2121         if (isOutputRange!(Writer, string))
2122         {}
2123     }
2124     static struct K
2125     {
2126         void toString(Writer)(Writer w, scope const ref FormatSpec!char fmt)
2127         if (isOutputRange!(Writer, string))
2128         {}
2129     }
2130     static struct L
2131     {
2132         void toString(Writer)(ref Writer w, scope const FormatSpec!char fmt)
2133         if (isOutputRange!(Writer, string))
2134         {}
2135     }
2136     static struct M
2137     {
2138         void toString(scope void delegate(in char[]) sink, in FormatSpec!char fmt) {}
2139     }
2140     static struct N
2141     {
2142         void toString(scope void delegate(in char[]) sink, string fmt) {}
2143     }
2144     static struct O
2145     {
2146         void toString(scope void delegate(in char[]) sink) {}
2147     }
2148 
2149     with(HasToStringResult)
2150     {
2151         static assert(hasToString!(A, char) == customPutWriter);
2152         static assert(hasToString!(B, char) == constCharSinkFormatSpec);
2153         static assert(hasToString!(C, char) == constCharSinkFormatString);
2154         static assert(hasToString!(D, char) == constCharSink);
2155         static assert(hasToString!(E, char) == hasSomeToString);
2156         static assert(hasToString!(F, char) == customPutWriterFormatSpec);
2157         static assert(hasToString!(G, char) == customPutWriter);
2158         static assert(hasToString!(H, char) == customPutWriterFormatSpec);
2159         static assert(hasToString!(I, char) == customPutWriterFormatSpec);
2160         static assert(hasToString!(J, char) == hasSomeToString);
2161         static assert(hasToString!(K, char) == constCharSinkFormatSpec);
2162         static assert(hasToString!(L, char) == none);
2163         static if (hasPreviewIn)
2164         {
2165             static assert(hasToString!(M, char) == inCharSinkFormatSpec);
2166             static assert(hasToString!(N, char) == inCharSinkFormatString);
2167             static assert(hasToString!(O, char) == inCharSink);
2168         }
2169     }
2170 }
2171 
2172 // const toString methods
2173 @safe unittest
2174 {
2175     import std.range.primitives : isOutputRange;
2176 
2177     static struct A
2178     {
2179         void toString(Writer)(ref Writer w) const
2180         if (isOutputRange!(Writer, string))
2181         {}
2182     }
2183     static struct B
2184     {
2185         void toString(scope void delegate(scope const(char)[]) sink, scope FormatSpec!char fmt) const {}
2186     }
2187     static struct C
2188     {
2189         void toString(scope void delegate(scope const(char)[]) sink, string fmt) const {}
2190     }
2191     static struct D
2192     {
2193         void toString(scope void delegate(scope const(char)[]) sink) const {}
2194     }
2195     static struct E
2196     {
2197         string toString() const {return "";}
2198     }
2199     static struct F
2200     {
2201         void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt) const
2202         if (isOutputRange!(Writer, string))
2203         {}
2204     }
2205     static struct G
2206     {
2207         string toString() const {return "";}
2208         void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, string)) {}
2209     }
2210     static struct H
2211     {
2212         string toString() const {return "";}
2213         void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt) const
2214         if (isOutputRange!(Writer, string))
2215         {}
2216     }
2217     static struct I
2218     {
2219         void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, string)) {}
2220         void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt) const
2221         if (isOutputRange!(Writer, string))
2222         {}
2223     }
2224     static struct J
2225     {
2226         string toString() const {return "";}
2227         void toString(Writer)(ref Writer w, scope ref FormatSpec!char fmt) const
2228         if (isOutputRange!(Writer, string))
2229         {}
2230     }
2231     static struct K
2232     {
2233         void toString(Writer)(Writer w, scope const ref FormatSpec!char fmt) const
2234         if (isOutputRange!(Writer, string))
2235         {}
2236     }
2237     static struct L
2238     {
2239         void toString(Writer)(ref Writer w, scope const FormatSpec!char fmt) const
2240         if (isOutputRange!(Writer, string))
2241         {}
2242     }
2243     static struct M
2244     {
2245         void toString(scope void delegate(in char[]) sink, in FormatSpec!char fmt) const {}
2246     }
2247     static struct N
2248     {
2249         void toString(scope void delegate(in char[]) sink, string fmt) const {}
2250     }
2251     static struct O
2252     {
2253         void toString(scope void delegate(in char[]) sink) const {}
2254     }
2255 
2256     with(HasToStringResult)
2257     {
2258         static assert(hasToString!(A, char) == customPutWriter);
2259         static assert(hasToString!(B, char) == constCharSinkFormatSpec);
2260         static assert(hasToString!(C, char) == constCharSinkFormatString);
2261         static assert(hasToString!(D, char) == constCharSink);
2262         static assert(hasToString!(E, char) == hasSomeToString);
2263         static assert(hasToString!(F, char) == customPutWriterFormatSpec);
2264         static assert(hasToString!(G, char) == customPutWriter);
2265         static assert(hasToString!(H, char) == customPutWriterFormatSpec);
2266         static assert(hasToString!(I, char) == customPutWriterFormatSpec);
2267         static assert(hasToString!(J, char) == hasSomeToString);
2268         static assert(hasToString!(K, char) == constCharSinkFormatSpec);
2269         static assert(hasToString!(L, char) == none);
2270         static if (hasPreviewIn)
2271         {
2272             static assert(hasToString!(M, char) == inCharSinkFormatSpec);
2273             static assert(hasToString!(N, char) == inCharSinkFormatString);
2274             static assert(hasToString!(O, char) == inCharSink);
2275         }
2276 
2277         // https://issues.dlang.org/show_bug.cgi?id=22873
2278         static assert(hasToString!(inout(A), char) == customPutWriter);
2279         static assert(hasToString!(inout(B), char) == constCharSinkFormatSpec);
2280         static assert(hasToString!(inout(C), char) == constCharSinkFormatString);
2281         static assert(hasToString!(inout(D), char) == constCharSink);
2282         static assert(hasToString!(inout(E), char) == hasSomeToString);
2283         static assert(hasToString!(inout(F), char) == customPutWriterFormatSpec);
2284         static assert(hasToString!(inout(G), char) == customPutWriter);
2285         static assert(hasToString!(inout(H), char) == customPutWriterFormatSpec);
2286         static assert(hasToString!(inout(I), char) == customPutWriterFormatSpec);
2287         static assert(hasToString!(inout(J), char) == hasSomeToString);
2288         static assert(hasToString!(inout(K), char) == constCharSinkFormatSpec);
2289         static assert(hasToString!(inout(L), char) == none);
2290         static if (hasPreviewIn)
2291         {
2292             static assert(hasToString!(inout(M), char) == inCharSinkFormatSpec);
2293             static assert(hasToString!(inout(N), char) == inCharSinkFormatString);
2294             static assert(hasToString!(inout(O), char) == inCharSink);
2295         }
2296     }
2297 }
2298 
2299 // object formatting with toString
2300 private void formatObject(Writer, T, Char)(ref Writer w, ref T val, scope const ref FormatSpec!Char f)
2301 if (hasToString!(T, Char))
2302 {
2303     import std.format : NoOpSink;
2304     import std.range.primitives : put;
2305 
2306     enum overload = hasToString!(T, Char);
2307 
2308     enum noop = is(Writer == NoOpSink);
2309 
2310     static if (overload == HasToStringResult.customPutWriterFormatSpec)
2311     {
2312         static if (!noop) val.toString(w, f);
2313     }
2314     else static if (overload == HasToStringResult.customPutWriter)
2315     {
2316         static if (!noop) val.toString(w);
2317     }
2318     else static if (overload == HasToStringResult.constCharSinkFormatSpec)
2319     {
2320         static if (!noop) val.toString((scope const(char)[] s) { put(w, s); }, f);
2321     }
2322     else static if (overload == HasToStringResult.constCharSinkFormatString)
2323     {
2324         static if (!noop) val.toString((scope const(char)[] s) { put(w, s); }, f.getCurFmtStr());
2325     }
2326     else static if (overload == HasToStringResult.constCharSink)
2327     {
2328         static if (!noop) val.toString((scope const(char)[] s) { put(w, s); });
2329     }
2330     else static if (overload == HasToStringResult.inCharSinkFormatSpec)
2331     {
2332         static if (!noop) val.toString((in char[] s) { put(w, s); }, f);
2333     }
2334     else static if (overload == HasToStringResult.inCharSinkFormatString)
2335     {
2336         static if (!noop) val.toString((in char[] s) { put(w, s); }, f.getCurFmtStr());
2337     }
2338     else static if (overload == HasToStringResult.inCharSink)
2339     {
2340         static if (!noop) val.toString((in char[] s) { put(w, s); });
2341     }
2342     else static if (overload == HasToStringResult.hasSomeToString)
2343     {
2344         static if (!noop) put(w, val.toString());
2345     }
2346     else
2347     {
2348         static assert(0, "No way found to format " ~ T.stringof ~ " as string");
2349     }
2350 }
2351 
2352 @system unittest
2353 {
2354     import std.exception : assertThrown;
2355     import std.format : FormatException;
2356 
2357     static interface IF1 { }
2358     class CIF1 : IF1 { }
2359     static struct SF1 { }
2360     static union UF1 { }
2361     static class CF1 { }
2362 
2363     static interface IF2 { string toString(); }
2364     static class CIF2 : IF2 { override string toString() { return ""; } }
2365     static struct SF2 { string toString() { return ""; } }
2366     static union UF2 { string toString() { return ""; } }
2367     static class CF2 { override string toString() { return ""; } }
2368 
2369     static interface IK1 { void toString(scope void delegate(scope const(char)[]) sink,
2370                            FormatSpec!char) const; }
2371     static class CIK1 : IK1 { override void toString(scope void delegate(scope const(char)[]) sink,
2372                               FormatSpec!char) const { sink("CIK1"); } }
2373     static struct KS1 { void toString(scope void delegate(scope const(char)[]) sink,
2374                         FormatSpec!char) const { sink("KS1"); } }
2375 
2376     static union KU1 { void toString(scope void delegate(scope const(char)[]) sink,
2377                        FormatSpec!char) const { sink("KU1"); } }
2378 
2379     static class KC1 { void toString(scope void delegate(scope const(char)[]) sink,
2380                        FormatSpec!char) const { sink("KC1"); } }
2381 
2382     IF1 cif1 = new CIF1;
2383     assertThrown!FormatException(format("%f", cif1));
2384     assertThrown!FormatException(format("%f", SF1()));
2385     assertThrown!FormatException(format("%f", UF1()));
2386     assertThrown!FormatException(format("%f", new CF1()));
2387 
2388     IF2 cif2 = new CIF2;
2389     assertThrown!FormatException(format("%f", cif2));
2390     assertThrown!FormatException(format("%f", SF2()));
2391     assertThrown!FormatException(format("%f", UF2()));
2392     assertThrown!FormatException(format("%f", new CF2()));
2393 
2394     IK1 cik1 = new CIK1;
2395     assert(format("%f", cik1) == "CIK1");
2396     assert(format("%f", KS1()) == "KS1");
2397     assert(format("%f", KU1()) == "KU1");
2398     assert(format("%f", new KC1()) == "KC1");
2399 }
2400 
2401 /*
2402     Aggregates
2403  */
2404 void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f)
2405 if (is(T == class) && !is(T == enum))
2406 {
2407     import std.range.primitives : put;
2408 
2409     enforceValidFormatSpec!(T, Char)(f);
2410 
2411     // TODO: remove this check once `@disable override` deprecation cycle is finished
2412     static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString))
2413         static assert(!__traits(isDisabled, T.toString), T.stringof ~
2414             " cannot be formatted because its `toString` is marked with `@disable`");
2415 
2416     if (val is null)
2417         put(w, "null");
2418     else
2419     {
2420         import std.algorithm.comparison : among;
2421         enum overload = hasToString!(T, Char);
2422         with(HasToStringResult)
2423         static if ((is(T == immutable) || is(T == const) || is(T == shared)) && overload == none)
2424         {
2425             // Remove this when Object gets const toString
2426             // https://issues.dlang.org/show_bug.cgi?id=7879
2427             static if (is(T == immutable))
2428                 put(w, "immutable(");
2429             else static if (is(T == const))
2430                 put(w, "const(");
2431             else static if (is(T == shared))
2432                 put(w, "shared(");
2433 
2434             put(w, typeid(Unqual!T).name);
2435             put(w, ')');
2436         }
2437         else static if (overload.among(constCharSink, constCharSinkFormatString, constCharSinkFormatSpec) ||
2438                        (!isInputRange!T && !is(BuiltinTypeOf!T)))
2439         {
2440             formatObject!(Writer, T, Char)(w, val, f);
2441         }
2442         else
2443         {
2444             static if (!is(__traits(parent, T.toString) == Object)) // not inherited Object.toString
2445             {
2446                 formatObject(w, val, f);
2447             }
2448             else static if (isInputRange!T)
2449             {
2450                 formatRange(w, val, f);
2451             }
2452             else static if (is(BuiltinTypeOf!T X))
2453             {
2454                 X x = val;
2455                 formatValueImpl(w, x, f);
2456             }
2457             else
2458             {
2459                 formatObject(w, val, f);
2460             }
2461         }
2462     }
2463 }
2464 
2465 @system unittest
2466 {
2467     import std.array : appender;
2468     import std.range.interfaces : inputRangeObject;
2469 
2470     // class range (https://issues.dlang.org/show_bug.cgi?id=5154)
2471     auto c = inputRangeObject([1,2,3,4]);
2472     formatTest(c, "[1, 2, 3, 4]");
2473     assert(c.empty);
2474     c = null;
2475     formatTest(c, "null");
2476 }
2477 
2478 @system unittest
2479 {
2480     // https://issues.dlang.org/show_bug.cgi?id=5354
2481     // If the class has both range I/F and custom toString, the use of custom
2482     // toString routine is prioritized.
2483 
2484     // Enable the use of custom toString that gets a sink delegate
2485     // for class formatting.
2486 
2487     enum inputRangeCode =
2488     q{
2489         int[] arr;
2490         this(int[] a){ arr = a; }
2491         @property int front() const { return arr[0]; }
2492         @property bool empty() const { return arr.length == 0; }
2493         void popFront(){ arr = arr[1 .. $]; }
2494     };
2495 
2496     class C1
2497     {
2498         mixin(inputRangeCode);
2499         void toString(scope void delegate(scope const(char)[]) dg,
2500                       scope const ref FormatSpec!char f) const
2501         {
2502             dg("[012]");
2503         }
2504     }
2505     class C2
2506     {
2507         mixin(inputRangeCode);
2508         void toString(scope void delegate(const(char)[]) dg, string f) const { dg("[012]"); }
2509     }
2510     class C3
2511     {
2512         mixin(inputRangeCode);
2513         void toString(scope void delegate(const(char)[]) dg) const { dg("[012]"); }
2514     }
2515     class C4
2516     {
2517         mixin(inputRangeCode);
2518         override string toString() const { return "[012]"; }
2519     }
2520     class C5
2521     {
2522         mixin(inputRangeCode);
2523     }
2524 
2525     formatTest(new C1([0, 1, 2]), "[012]");
2526     formatTest(new C2([0, 1, 2]), "[012]");
2527     formatTest(new C3([0, 1, 2]), "[012]");
2528     formatTest(new C4([0, 1, 2]), "[012]");
2529     formatTest(new C5([0, 1, 2]), "[0, 1, 2]");
2530 }
2531 
2532 // outside the unittest block, otherwise the FQN of the
2533 // class contains the line number of the unittest
2534 version (StdUnittest)
2535 {
2536     private class C {}
2537 }
2538 
2539 // https://issues.dlang.org/show_bug.cgi?id=7879
2540 @safe unittest
2541 {
2542     const(C) c;
2543     auto s = format("%s", c);
2544     assert(s == "null");
2545 
2546     immutable(C) c2 = new C();
2547     s = format("%s", c2);
2548     assert(s == "immutable(std.format.internal.write.C)");
2549 
2550     const(C) c3 = new C();
2551     s = format("%s", c3);
2552     assert(s == "const(std.format.internal.write.C)");
2553 
2554     shared(C) c4 = new C();
2555     s = format("%s", c4);
2556     assert(s == "shared(std.format.internal.write.C)");
2557 }
2558 
2559 // https://issues.dlang.org/show_bug.cgi?id=7879
2560 @safe unittest
2561 {
2562     class F
2563     {
2564         override string toString() const @safe
2565         {
2566             return "Foo";
2567         }
2568     }
2569 
2570     const(F) c;
2571     auto s = format("%s", c);
2572     assert(s == "null");
2573 
2574     const(F) c2 = new F();
2575     s = format("%s", c2);
2576     assert(s == "Foo", s);
2577 }
2578 
2579 void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f)
2580 if (is(T == interface) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is(T == enum))
2581 {
2582     import std.range.primitives : put;
2583 
2584     enforceValidFormatSpec!(T, Char)(f);
2585     if (val is null)
2586         put(w, "null");
2587     else
2588     {
2589         static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString))
2590             static assert(!__traits(isDisabled, T.toString), T.stringof ~
2591                 " cannot be formatted because its `toString` is marked with `@disable`");
2592 
2593         static if (hasToString!(T, Char) != HasToStringResult.none)
2594         {
2595             formatObject(w, val, f);
2596         }
2597         else static if (isInputRange!T)
2598         {
2599             formatRange(w, val, f);
2600         }
2601         else
2602         {
2603             version (Windows)
2604             {
2605                 import core.sys.windows.com : IUnknown;
2606                 static if (is(T : IUnknown))
2607                 {
2608                     formatValueImpl(w, *cast(void**)&val, f);
2609                 }
2610                 else
2611                 {
2612                     formatValueImpl(w, cast(Object) val, f);
2613                 }
2614             }
2615             else
2616             {
2617                 formatValueImpl(w, cast(Object) val, f);
2618             }
2619         }
2620     }
2621 }
2622 
2623 @system unittest
2624 {
2625     import std.range.interfaces : InputRange, inputRangeObject;
2626 
2627     // interface
2628     InputRange!int i = inputRangeObject([1,2,3,4]);
2629     formatTest(i, "[1, 2, 3, 4]");
2630     assert(i.empty);
2631     i = null;
2632     formatTest(i, "null");
2633 
2634     // interface (downcast to Object)
2635     interface Whatever {}
2636     class C : Whatever
2637     {
2638         override @property string toString() const { return "ab"; }
2639     }
2640     Whatever val = new C;
2641     formatTest(val, "ab");
2642 
2643     // https://issues.dlang.org/show_bug.cgi?id=11175
2644     version (Windows)
2645     {
2646         import core.sys.windows.com : IID, IUnknown;
2647         import core.sys.windows.windef : HRESULT;
2648 
2649         interface IUnknown2 : IUnknown { }
2650 
2651         class D : IUnknown2
2652         {
2653             extern(Windows) HRESULT QueryInterface(const(IID)* riid, void** pvObject) { return typeof(return).init; }
2654             extern(Windows) uint AddRef() { return 0; }
2655             extern(Windows) uint Release() { return 0; }
2656         }
2657 
2658         IUnknown2 d = new D;
2659         string expected = format("%X", cast(void*) d);
2660         formatTest(d, expected);
2661     }
2662 }
2663 
2664 // Maybe T is noncopyable struct, so receive it by 'auto ref'.
2665 void formatValueImpl(Writer, T, Char)(auto ref Writer w, auto ref T val,
2666     scope const ref FormatSpec!Char f)
2667 if ((is(T == struct) || is(T == union)) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T))
2668     && !is(T == enum))
2669 {
2670     import std.range.primitives : put;
2671 
2672     static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString))
2673         static assert(!__traits(isDisabled, T.toString), T.stringof ~
2674             " cannot be formatted because its `toString` is marked with `@disable`");
2675 
2676     enforceValidFormatSpec!(T, Char)(f);
2677     static if (hasToString!(T, Char))
2678     {
2679         formatObject(w, val, f);
2680     }
2681     else static if (isInputRange!T)
2682     {
2683         formatRange(w, val, f);
2684     }
2685     else static if (is(T == struct))
2686     {
2687         enum left = T.stringof~"(";
2688         enum separator = ", ";
2689         enum right = ")";
2690 
2691         put(w, left);
2692         foreach (i, e; val.tupleof)
2693         {
2694             static if (__traits(identifier, val.tupleof[i]) == "this")
2695                 continue;
2696             else static if (0 < i && val.tupleof[i-1].offsetof == val.tupleof[i].offsetof)
2697             {
2698                 static if (i == val.tupleof.length - 1 || val.tupleof[i].offsetof != val.tupleof[i+1].offsetof)
2699                 {
2700                     enum el = separator ~ val.tupleof[i].stringof[4 .. $] ~ "}";
2701                     put(w, el);
2702                 }
2703                 else
2704                 {
2705                     enum el = separator ~ val.tupleof[i].stringof[4 .. $];
2706                     put(w, el);
2707                 }
2708             }
2709             else static if (i+1 < val.tupleof.length && val.tupleof[i].offsetof == val.tupleof[i+1].offsetof)
2710             {
2711                 enum el = (i > 0 ? separator : "") ~ "#{overlap " ~ val.tupleof[i].stringof[4 .. $];
2712                 put(w, el);
2713             }
2714             else
2715             {
2716                 static if (i > 0)
2717                     put(w, separator);
2718                 formatElement(w, e, f);
2719             }
2720         }
2721         put(w, right);
2722     }
2723     else
2724     {
2725         put(w, T.stringof);
2726     }
2727 }
2728 
2729 // https://issues.dlang.org/show_bug.cgi?id=9588
2730 @safe pure unittest
2731 {
2732     struct S { int x; bool empty() { return false; } }
2733     formatTest(S(), "S(0)");
2734 }
2735 
2736 // https://issues.dlang.org/show_bug.cgi?id=4638
2737 @safe unittest
2738 {
2739     struct U8  {  string toString() const { return "blah"; } }
2740     struct U16 { wstring toString() const { return "blah"; } }
2741     struct U32 { dstring toString() const { return "blah"; } }
2742     formatTest(U8(), "blah");
2743     formatTest(U16(), "blah");
2744     formatTest(U32(), "blah");
2745 }
2746 
2747 // https://issues.dlang.org/show_bug.cgi?id=3890
2748 @safe unittest
2749 {
2750     struct Int{ int n; }
2751     struct Pair{ string s; Int i; }
2752     formatTest(Pair("hello", Int(5)),
2753                `Pair("hello", Int(5))`);
2754 }
2755 
2756 // https://issues.dlang.org/show_bug.cgi?id=9117
2757 @safe unittest
2758 {
2759     import std.format : formattedWrite;
2760 
2761     static struct Frop {}
2762 
2763     static struct Foo
2764     {
2765         int n = 0;
2766         alias n this;
2767         T opCast(T) () if (is(T == Frop))
2768         {
2769             return Frop();
2770         }
2771         string toString()
2772         {
2773             return "Foo";
2774         }
2775     }
2776 
2777     static struct Bar
2778     {
2779         Foo foo;
2780         alias foo this;
2781         string toString()
2782         {
2783             return "Bar";
2784         }
2785     }
2786 
2787     const(char)[] result;
2788     void put(scope const char[] s) { result ~= s; }
2789 
2790     Foo foo;
2791     formattedWrite(&put, "%s", foo);    // OK
2792     assert(result == "Foo");
2793 
2794     result = null;
2795 
2796     Bar bar;
2797     formattedWrite(&put, "%s", bar);    // NG
2798     assert(result == "Bar");
2799 
2800     result = null;
2801 
2802     int i = 9;
2803     formattedWrite(&put, "%s", 9);
2804     assert(result == "9");
2805 }
2806 
2807 @safe unittest
2808 {
2809     // union formatting without toString
2810     union U1
2811     {
2812         int n;
2813         string s;
2814     }
2815     U1 u1;
2816     formatTest(u1, "U1");
2817 
2818     // union formatting with toString
2819     union U2
2820     {
2821         int n;
2822         string s;
2823         string toString() const { return s; }
2824     }
2825     U2 u2;
2826     () @trusted { u2.s = "hello"; } ();
2827     formatTest(u2, "hello");
2828 }
2829 
2830 @safe unittest
2831 {
2832     import std.array : appender;
2833     import std.format : formatValue;
2834 
2835     // https://issues.dlang.org/show_bug.cgi?id=7230
2836     static struct Bug7230
2837     {
2838         string s = "hello";
2839         union {
2840             string a;
2841             int b;
2842             double c;
2843         }
2844         long x = 10;
2845     }
2846 
2847     Bug7230 bug;
2848     bug.b = 123;
2849 
2850     FormatSpec!char f;
2851     auto w = appender!(char[])();
2852     formatValue(w, bug, f);
2853     assert(w.data == `Bug7230("hello", #{overlap a, b, c}, 10)`);
2854 }
2855 
2856 @safe unittest
2857 {
2858     import std.array : appender;
2859     import std.format : formatValue;
2860 
2861     static struct S{ @disable this(this); }
2862     S s;
2863 
2864     FormatSpec!char f;
2865     auto w = appender!string();
2866     formatValue(w, s, f);
2867     assert(w.data == "S()");
2868 }
2869 
2870 @safe unittest
2871 {
2872     import std.array : appender;
2873     import std.format : formatValue;
2874 
2875     //struct Foo { @disable string toString(); }
2876     //Foo foo;
2877 
2878     interface Bar { @disable string toString(); }
2879     Bar bar;
2880 
2881     auto w = appender!(char[])();
2882     FormatSpec!char f;
2883 
2884     // NOTE: structs cant be tested : the assertion is correct so compilation
2885     // continues and fails when trying to link the unimplemented toString.
2886     //static assert(!__traits(compiles, formatValue(w, foo, f)));
2887     static assert(!__traits(compiles, formatValue(w, bar, f)));
2888 }
2889 
2890 // https://issues.dlang.org/show_bug.cgi?id=21722
2891 @safe unittest
2892 {
2893     struct Bar
2894     {
2895         void toString (scope void delegate (scope const(char)[]) sink, string fmt)
2896         {
2897             sink("Hello");
2898         }
2899     }
2900 
2901     Bar b;
2902     auto result = () @trusted { return format("%b", b); } ();
2903     assert(result == "Hello");
2904 
2905     static if (hasPreviewIn)
2906     {
2907         struct Foo
2908         {
2909             void toString(scope void delegate(in char[]) sink, in FormatSpec!char fmt)
2910             {
2911                 sink("Hello");
2912             }
2913         }
2914 
2915         Foo f;
2916         assert(format("%b", f) == "Hello");
2917 
2918         struct Foo2
2919         {
2920             void toString(scope void delegate(in char[]) sink, string fmt)
2921             {
2922                 sink("Hello");
2923             }
2924         }
2925 
2926         Foo2 f2;
2927         assert(format("%b", f2) == "Hello");
2928     }
2929 }
2930 
2931 @safe unittest
2932 {
2933     import std.array : appender;
2934     import std.format : singleSpec;
2935 
2936     // Bug #17269. Behavior similar to `struct A { Nullable!string B; }`
2937     struct StringAliasThis
2938     {
2939         @property string value() const { assert(0); }
2940         alias value this;
2941         string toString() { return "helloworld"; }
2942         private string _value;
2943     }
2944     struct TestContainer
2945     {
2946         StringAliasThis testVar;
2947     }
2948 
2949     auto w = appender!string();
2950     auto spec = singleSpec("%s");
2951     formatElement(w, TestContainer(), spec);
2952 
2953     assert(w.data == "TestContainer(helloworld)", w.data);
2954 }
2955 
2956 // https://issues.dlang.org/show_bug.cgi?id=17269
2957 @safe unittest
2958 {
2959     import std.typecons : Nullable;
2960 
2961     struct Foo
2962     {
2963         Nullable!string bar;
2964     }
2965 
2966     Foo f;
2967     formatTest(f, "Foo(Nullable.null)");
2968 }
2969 
2970 // https://issues.dlang.org/show_bug.cgi?id=19003
2971 @safe unittest
2972 {
2973     struct S
2974     {
2975         int i;
2976 
2977         @disable this();
2978 
2979         invariant { assert(this.i); }
2980 
2981         this(int i) @safe in { assert(i); } do { this.i = i; }
2982 
2983         string toString() { return "S"; }
2984     }
2985 
2986     S s = S(1);
2987 
2988     format!"%s"(s);
2989 }
2990 
2991 void enforceValidFormatSpec(T, Char)(scope const ref FormatSpec!Char f)
2992 {
2993     import std.format : enforceFmt;
2994     import std.range : isInputRange;
2995     import std.format.internal.write : hasToString, HasToStringResult;
2996 
2997     enum overload = hasToString!(T, Char);
2998     static if (
2999             overload != HasToStringResult.constCharSinkFormatSpec &&
3000             overload != HasToStringResult.constCharSinkFormatString &&
3001             overload != HasToStringResult.inCharSinkFormatSpec &&
3002             overload != HasToStringResult.inCharSinkFormatString &&
3003             overload != HasToStringResult.customPutWriterFormatSpec &&
3004             !isInputRange!T)
3005     {
3006         enforceFmt(f.spec == 's',
3007             "Expected '%s' format specifier for type '" ~ T.stringof ~ "'");
3008     }
3009 }
3010 
3011 /*
3012     `enum`s are formatted like their base value
3013  */
3014 void formatValueImpl(Writer, T, Char)(auto ref Writer w, const(T) val, scope const ref FormatSpec!Char f)
3015 if (is(T == enum))
3016 {
3017     import std.array : appender;
3018     import std.range.primitives : put;
3019 
3020     if (f.spec == 's')
3021     {
3022         foreach (i, e; EnumMembers!T)
3023         {
3024             if (val == e)
3025             {
3026                 formatValueImpl(w, __traits(allMembers, T)[i], f);
3027                 return;
3028             }
3029         }
3030 
3031         auto w2 = appender!string();
3032 
3033         // val is not a member of T, output cast(T) rawValue instead.
3034         put(w2, "cast(");
3035         put(w2, T.stringof);
3036         put(w2, ")");
3037         static assert(!is(OriginalType!T == T), "OriginalType!" ~ T.stringof ~
3038             "must not be equal to " ~ T.stringof);
3039 
3040         FormatSpec!Char f2 = f;
3041         f2.width = 0;
3042         formatValueImpl(w2, cast(OriginalType!T) val, f2);
3043         writeAligned(w, w2.data, f);
3044         return;
3045     }
3046     formatValueImpl(w, cast(OriginalType!T) val, f);
3047 }
3048 
3049 @safe unittest
3050 {
3051     enum A { first, second, third }
3052     formatTest(A.second, "second");
3053     formatTest(cast(A) 72, "cast(A)72");
3054 }
3055 @safe unittest
3056 {
3057     enum A : string { one = "uno", two = "dos", three = "tres" }
3058     formatTest(A.three, "three");
3059     formatTest(cast(A)"mill\&oacute;n", "cast(A)mill\&oacute;n");
3060 }
3061 @safe unittest
3062 {
3063     enum A : bool { no, yes }
3064     formatTest(A.yes, "yes");
3065     formatTest(A.no, "no");
3066 }
3067 @safe unittest
3068 {
3069     // Test for bug 6892
3070     enum Foo { A = 10 }
3071     formatTest("%s",    Foo.A, "A");
3072     formatTest(">%4s<", Foo.A, ">   A<");
3073     formatTest("%04d",  Foo.A, "0010");
3074     formatTest("%+2u",  Foo.A, "10");
3075     formatTest("%02x",  Foo.A, "0a");
3076     formatTest("%3o",   Foo.A, " 12");
3077     formatTest("%b",    Foo.A, "1010");
3078 }
3079 
3080 @safe pure unittest
3081 {
3082     enum A { one, two, three }
3083 
3084     string t1 = format("[%6s] [%-6s]", A.one, A.one);
3085     assert(t1 == "[   one] [one   ]");
3086     string t2 = format("[%10s] [%-10s]", cast(A) 10, cast(A) 10);
3087     assert(t2 == "[ cast(A)" ~ "10] [cast(A)" ~ "10 ]"); // due to bug in style checker
3088 }
3089 
3090 // https://issues.dlang.org/show_bug.cgi?id=8921
3091 @safe unittest
3092 {
3093     enum E : char { A = 'a', B = 'b', C = 'c' }
3094     E[3] e = [E.A, E.B, E.C];
3095     formatTest(e, "[A, B, C]");
3096 
3097     E[] e2 = [E.A, E.B, E.C];
3098     formatTest(e2, "[A, B, C]");
3099 }
3100 
3101 /*
3102     Pointers are formatted as hex integers.
3103  */
3104 void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope const(T) val, scope const ref FormatSpec!Char f)
3105 if (isPointer!T && !is(T == enum) && !hasToString!(T, Char))
3106 {
3107     static if (is(typeof({ shared const void* p = val; })))
3108         alias SharedOf(T) = shared(T);
3109     else
3110         alias SharedOf(T) = T;
3111 
3112     const SharedOf!(void*) p = val;
3113     const pnum = () @trusted { return cast(ulong) p; }();
3114 
3115     if (f.spec == 's')
3116     {
3117         if (p is null)
3118         {
3119             writeAligned(w, "null", f);
3120             return;
3121         }
3122         FormatSpec!Char fs = f; // fs is copy for change its values.
3123         fs.spec = 'X';
3124         formatValueImpl(w, pnum, fs);
3125     }
3126     else
3127     {
3128         import std.format : enforceFmt;
3129         enforceFmt(f.spec == 'X' || f.spec == 'x',
3130             "Expected one of %s, %x or %X for pointer type.");
3131         formatValueImpl(w, pnum, f);
3132     }
3133 }
3134 
3135 @safe pure unittest
3136 {
3137     int* p;
3138 
3139     string t1 = format("[%6s] [%-6s]", p, p);
3140     assert(t1 == "[  null] [null  ]");
3141 }
3142 
3143 @safe pure unittest
3144 {
3145     int* p = null;
3146     formatTest(p, "null");
3147 
3148     auto q = () @trusted { return cast(void*) 0xFFEECCAA; }();
3149     formatTest(q, "FFEECCAA");
3150 }
3151 
3152 // https://issues.dlang.org/show_bug.cgi?id=11782
3153 @safe pure unittest
3154 {
3155     import std.range : iota;
3156 
3157     auto a = iota(0, 10);
3158     auto b = iota(0, 10);
3159     auto p = () @trusted { auto p = &a; return p; }();
3160 
3161     assert(format("%s",p) != format("%s",b));
3162 }
3163 
3164 @safe pure unittest
3165 {
3166     // Test for https://issues.dlang.org/show_bug.cgi?id=7869
3167     struct S
3168     {
3169         string toString() const { return ""; }
3170     }
3171     S* p = null;
3172     formatTest(p, "null");
3173 
3174     S* q = () @trusted { return cast(S*) 0xFFEECCAA; } ();
3175     formatTest(q, "FFEECCAA");
3176 }
3177 
3178 // https://issues.dlang.org/show_bug.cgi?id=8186
3179 @system unittest
3180 {
3181     class B
3182     {
3183         int* a;
3184         this() { a = new int; }
3185         alias a this;
3186     }
3187     formatTest(B.init, "null");
3188 }
3189 
3190 // https://issues.dlang.org/show_bug.cgi?id=9336
3191 @system pure unittest
3192 {
3193     shared int i;
3194     format("%s", &i);
3195 }
3196 
3197 // https://issues.dlang.org/show_bug.cgi?id=11778
3198 @safe pure unittest
3199 {
3200     import std.exception : assertThrown;
3201     import std.format : FormatException;
3202 
3203     int* p = null;
3204     assertThrown!FormatException(format("%d", p));
3205     assertThrown!FormatException(format("%04d", () @trusted { return p + 2; } ()));
3206 }
3207 
3208 // https://issues.dlang.org/show_bug.cgi?id=12505
3209 @safe pure unittest
3210 {
3211     void* p = null;
3212     formatTest("%08X", p, "00000000");
3213 }
3214 
3215 /*
3216     SIMD vectors are formatted as arrays.
3217  */
3218 void formatValueImpl(Writer, V, Char)(auto ref Writer w, const(V) val, scope const ref FormatSpec!Char f)
3219 if (isSIMDVector!V)
3220 {
3221     formatValueImpl(w, val.array, f);
3222 }
3223 
3224 @safe unittest
3225 {
3226     import core.simd; // cannot be selective, because float4 might not be defined
3227 
3228     static if (is(float4))
3229     {
3230         version (X86)
3231         {
3232             version (OSX) {/* https://issues.dlang.org/show_bug.cgi?id=17823 */}
3233         }
3234         else
3235         {
3236             float4 f;
3237             f.array[0] = 1;
3238             f.array[1] = 2;
3239             f.array[2] = 3;
3240             f.array[3] = 4;
3241             formatTest(f, "[1, 2, 3, 4]");
3242         }
3243     }
3244 }
3245 
3246 /*
3247     Delegates are formatted by `ReturnType delegate(Parameters) FunctionAttributes`
3248 
3249     Known bug: Because of issue https://issues.dlang.org/show_bug.cgi?id=18269
3250                the FunctionAttributes might be wrong.
3251  */
3252 void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope const(T), scope const ref FormatSpec!Char f)
3253 if (isDelegate!T)
3254 {
3255     formatValueImpl(w, T.stringof, f);
3256 }
3257 
3258 @safe unittest
3259 {
3260     import std.array : appender;
3261     import std.format : formatValue;
3262 
3263     void func() @system { __gshared int x; ++x; throw new Exception("msg"); }
3264     version (linux)
3265     {
3266         FormatSpec!char f;
3267         auto w = appender!string();
3268         formatValue(w, &func, f);
3269         assert(w.data.length >= 15 && w.data[0 .. 15] == "void delegate()");
3270     }
3271 }
3272 
3273 // string elements are formatted like UTF-8 string literals.
3274 void formatElement(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f)
3275 if (is(StringTypeOf!T) && !hasToString!(T, Char) && !is(T == enum))
3276 {
3277     import std.array : appender;
3278     import std.format.write : formattedWrite, formatValue;
3279     import std.range.primitives : put;
3280     import std.utf : decode, UTFException;
3281 
3282     StringTypeOf!T str = val;   // https://issues.dlang.org/show_bug.cgi?id=8015
3283 
3284     if (f.spec == 's')
3285     {
3286         try
3287         {
3288             // ignore other specifications and quote
3289             for (size_t i = 0; i < str.length; )
3290             {
3291                 auto c = decode(str, i);
3292                 // \uFFFE and \uFFFF are considered valid by isValidDchar,
3293                 // so need checking for interchange.
3294                 if (c == 0xFFFE || c == 0xFFFF)
3295                     goto LinvalidSeq;
3296             }
3297             put(w, '\"');
3298             for (size_t i = 0; i < str.length; )
3299             {
3300                 auto c = decode(str, i);
3301                 formatChar(w, c, '"');
3302             }
3303             put(w, '\"');
3304             return;
3305         }
3306         catch (UTFException)
3307         {
3308         }
3309 
3310         // If val contains invalid UTF sequence, formatted like HexString literal
3311     LinvalidSeq:
3312         static if (is(typeof(str[0]) : const(char)))
3313         {
3314             enum type = "";
3315             alias IntArr = const(ubyte)[];
3316         }
3317         else static if (is(typeof(str[0]) : const(wchar)))
3318         {
3319             enum type = "w";
3320             alias IntArr = const(ushort)[];
3321         }
3322         else static if (is(typeof(str[0]) : const(dchar)))
3323         {
3324             enum type = "d";
3325             alias IntArr = const(uint)[];
3326         }
3327         formattedWrite(w, "[%(cast(" ~ type ~ "char) 0x%X%|, %)]", cast(IntArr) str);
3328     }
3329     else
3330         formatValue(w, str, f);
3331 }
3332 
3333 @safe pure unittest
3334 {
3335     import std.array : appender;
3336     import std.format.spec : singleSpec;
3337 
3338     auto w = appender!string();
3339     auto spec = singleSpec("%s");
3340     formatElement(w, "Hello World", spec);
3341 
3342     assert(w.data == "\"Hello World\"");
3343 }
3344 
3345 @safe unittest
3346 {
3347     import std.array : appender;
3348     import std.format.spec : singleSpec;
3349 
3350     auto w = appender!string();
3351     auto spec = singleSpec("%s");
3352     formatElement(w, "H", spec);
3353 
3354     assert(w.data == "\"H\"", w.data);
3355 }
3356 
3357 // https://issues.dlang.org/show_bug.cgi?id=15888
3358 @safe pure unittest
3359 {
3360     import std.array : appender;
3361     import std.format.spec : singleSpec;
3362 
3363     ushort[] a = [0xFF_FE, 0x42];
3364     auto w = appender!string();
3365     auto spec = singleSpec("%s");
3366     formatElement(w, cast(wchar[]) a, spec);
3367     assert(w.data == `[cast(wchar) 0xFFFE, cast(wchar) 0x42]`);
3368 
3369     uint[] b = [0x0F_FF_FF_FF, 0x42];
3370     w = appender!string();
3371     spec = singleSpec("%s");
3372     formatElement(w, cast(dchar[]) b, spec);
3373     assert(w.data == `[cast(dchar) 0xFFFFFFF, cast(dchar) 0x42]`);
3374 }
3375 
3376 // Character elements are formatted like UTF-8 character literals.
3377 void formatElement(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f)
3378 if (is(CharTypeOf!T) && !is(T == enum))
3379 {
3380     import std.range.primitives : put;
3381     import std.format.write : formatValue;
3382 
3383     if (f.spec == 's')
3384     {
3385         put(w, '\'');
3386         formatChar(w, val, '\'');
3387         put(w, '\'');
3388     }
3389     else
3390         formatValue(w, val, f);
3391 }
3392 
3393 // Maybe T is noncopyable struct, so receive it by 'auto ref'.
3394 void formatElement(Writer, T, Char)(auto ref Writer w, auto ref T val, scope const ref FormatSpec!Char f)
3395 if ((!is(StringTypeOf!T) || hasToString!(T, Char)) && !is(CharTypeOf!T) || is(T == enum))
3396 {
3397     import std.format.write : formatValue;
3398 
3399     formatValue(w, val, f);
3400 }
3401 
3402 // Fix for https://issues.dlang.org/show_bug.cgi?id=1591
3403 int getNthInt(string kind, A...)(uint index, A args)
3404 {
3405     return getNth!(kind, isIntegral, int)(index, args);
3406 }
3407 
3408 T getNth(string kind, alias Condition, T, A...)(uint index, A args)
3409 {
3410     import std.conv : text, to;
3411     import std.format : FormatException;
3412 
3413     switch (index)
3414     {
3415         foreach (n, _; A)
3416         {
3417             case n:
3418                 static if (Condition!(typeof(args[n])))
3419                 {
3420                     return to!T(args[n]);
3421                 }
3422                 else
3423                 {
3424                     throw new FormatException(
3425                         text(kind, " expected, not ", typeof(args[n]).stringof,
3426                             " for argument #", index + 1));
3427                 }
3428         }
3429         default:
3430             throw new FormatException(text("Missing ", kind, " argument"));
3431     }
3432 }
3433 
3434 private bool needToSwapEndianess(Char)(scope const ref FormatSpec!Char f)
3435 {
3436     import std.system : endian, Endian;
3437 
3438     return endian == Endian.littleEndian && f.flPlus
3439         || endian == Endian.bigEndian && f.flDash;
3440 }
3441 
3442 void writeAligned(Writer, T, Char)(auto ref Writer w, T s, scope const ref FormatSpec!Char f)
3443 if (isSomeString!T)
3444 {
3445     FormatSpec!Char fs = f;
3446     fs.flZero = false;
3447     writeAligned(w, "", "", s, fs);
3448 }
3449 
3450 @safe pure unittest
3451 {
3452     import std.array : appender;
3453     import std.format : singleSpec;
3454 
3455     auto w = appender!string();
3456     auto spec = singleSpec("%s");
3457     writeAligned(w, "a本Ä", spec);
3458     assert(w.data == "a本Ä", w.data);
3459 }
3460 
3461 @safe pure unittest
3462 {
3463     import std.array : appender;
3464     import std.format : singleSpec;
3465 
3466     auto w = appender!string();
3467     auto spec = singleSpec("%10s");
3468     writeAligned(w, "a本Ä", spec);
3469     assert(w.data == "       a本Ä", "|" ~ w.data ~ "|");
3470 }
3471 
3472 @safe pure unittest
3473 {
3474     import std.array : appender;
3475     import std.format : singleSpec;
3476 
3477     auto w = appender!string();
3478     auto spec = singleSpec("%-10s");
3479     writeAligned(w, "a本Ä", spec);
3480     assert(w.data == "a本Ä       ", w.data);
3481 }
3482 
3483 enum PrecisionType
3484 {
3485     none,
3486     integer,
3487     fractionalDigits,
3488     allDigits,
3489 }
3490 
3491 void writeAligned(Writer, T1, T2, T3, Char)(auto ref Writer w,
3492     T1 prefix, T2 grouped, T3 suffix, scope const ref FormatSpec!Char f,
3493     bool integer_precision = false)
3494 if (isSomeString!T1 && isSomeString!T2 && isSomeString!T3)
3495 {
3496     writeAligned(w, prefix, grouped, "", suffix, f,
3497                  integer_precision ? PrecisionType.integer : PrecisionType.none);
3498 }
3499 
3500 void writeAligned(Writer, T1, T2, T3, T4, Char)(auto ref Writer w,
3501     T1 prefix, T2 grouped, T3 fracts, T4 suffix, scope const ref FormatSpec!Char f,
3502     PrecisionType p = PrecisionType.none)
3503 if (isSomeString!T1 && isSomeString!T2 && isSomeString!T3 && isSomeString!T4)
3504 {
3505     // writes: left padding, prefix, leading zeros, grouped, fracts, suffix, right padding
3506 
3507     if (p == PrecisionType.integer && f.precision == f.UNSPECIFIED)
3508         p = PrecisionType.none;
3509 
3510     import std.range.primitives : put;
3511 
3512     long prefixWidth;
3513     long groupedWidth = grouped.length; // TODO: does not take graphemes into account
3514     long fractsWidth = fracts.length; // TODO: does not take graphemes into account
3515     long suffixWidth;
3516 
3517     // TODO: remove this workaround which hides issue 21815
3518     if (f.width > 0)
3519     {
3520         prefixWidth = getWidth(prefix);
3521         suffixWidth = getWidth(suffix);
3522     }
3523 
3524     auto doGrouping = f.flSeparator && groupedWidth > 0
3525                       && f.separators > 0 && f.separators != f.UNSPECIFIED;
3526     // front = number of symbols left of the leftmost separator
3527     long front = doGrouping ? (groupedWidth - 1) % f.separators + 1 : 0;
3528     // sepCount = number of separators to be inserted
3529     long sepCount = doGrouping ? (groupedWidth - 1) / f.separators : 0;
3530 
3531     long trailingZeros = 0;
3532     if (p == PrecisionType.fractionalDigits)
3533         trailingZeros = f.precision - (fractsWidth - 1);
3534     if (p == PrecisionType.allDigits && f.flHash)
3535     {
3536         if (grouped != "0")
3537             trailingZeros = f.precision - (fractsWidth - 1) - groupedWidth;
3538         else
3539         {
3540             trailingZeros = f.precision - fractsWidth;
3541             foreach (i;0 .. fracts.length)
3542                 if (fracts[i] != '0' && fracts[i] != '.')
3543                 {
3544                     trailingZeros = f.precision - (fracts.length - i);
3545                     break;
3546                 }
3547         }
3548     }
3549 
3550     auto nodot = fracts == "." && trailingZeros == 0 && !f.flHash;
3551 
3552     if (nodot) fractsWidth = 0;
3553 
3554     long width = prefixWidth + sepCount + groupedWidth + fractsWidth + trailingZeros + suffixWidth;
3555     long delta = f.width - width;
3556 
3557     // with integers, precision is considered the minimum number of digits;
3558     // if digits are missing, we have to recalculate everything
3559     long pregrouped = 0;
3560     if (p == PrecisionType.integer && groupedWidth < f.precision)
3561     {
3562         pregrouped = f.precision - groupedWidth;
3563         delta -= pregrouped;
3564         if (doGrouping)
3565         {
3566             front = ((front - 1) + pregrouped) % f.separators + 1;
3567             delta -= (f.precision - 1) / f.separators - sepCount;
3568         }
3569     }
3570 
3571     // left padding
3572     if ((!f.flZero || p == PrecisionType.integer) && delta > 0)
3573     {
3574         if (f.flEqual)
3575         {
3576             foreach (i ; 0 .. delta / 2 + ((delta % 2 == 1 && !f.flDash) ? 1 : 0))
3577                 put(w, ' ');
3578         }
3579         else if (!f.flDash)
3580         {
3581             foreach (i ; 0 .. delta)
3582                 put(w, ' ');
3583         }
3584     }
3585 
3586     // prefix
3587     put(w, prefix);
3588 
3589     // leading grouped zeros
3590     if (f.flZero && p != PrecisionType.integer && !f.flDash && delta > 0)
3591     {
3592         if (doGrouping)
3593         {
3594             // front2 and sepCount2 are the same as above for the leading zeros
3595             long front2 = (delta + front - 1) % (f.separators + 1) + 1;
3596             long sepCount2 = (delta + front - 1) / (f.separators + 1);
3597             delta -= sepCount2;
3598 
3599             // according to POSIX: if the first symbol is a separator,
3600             // an additional zero is put left of it, even if that means, that
3601             // the total width is one more then specified
3602             if (front2 > f.separators) { front2 = 1; }
3603 
3604             foreach (i ; 0 .. delta)
3605             {
3606                 if (front2 == 0)
3607                 {
3608                     put(w, f.separatorChar);
3609                     front2 = f.separators;
3610                 }
3611                 front2--;
3612 
3613                 put(w, '0');
3614             }
3615 
3616             // separator between zeros and grouped
3617             if (front == f.separators)
3618                 put(w, f.separatorChar);
3619         }
3620         else
3621             foreach (i ; 0 .. delta)
3622                 put(w, '0');
3623     }
3624 
3625     // grouped content
3626     if (doGrouping)
3627     {
3628         // TODO: this does not take graphemes into account
3629         foreach (i;0 .. pregrouped + grouped.length)
3630         {
3631             if (front == 0)
3632             {
3633                 put(w, f.separatorChar);
3634                 front = f.separators;
3635             }
3636             front--;
3637 
3638             put(w, i < pregrouped ? '0' : grouped[cast(size_t) (i - pregrouped)]);
3639         }
3640     }
3641     else
3642     {
3643         foreach (i;0 .. pregrouped)
3644             put(w, '0');
3645         put(w, grouped);
3646     }
3647 
3648     // fracts
3649     if (!nodot)
3650         put(w, fracts);
3651 
3652     // trailing zeros
3653     foreach (i ; 0 .. trailingZeros)
3654         put(w, '0');
3655 
3656     // suffix
3657     put(w, suffix);
3658 
3659     // right padding
3660     if (delta > 0)
3661     {
3662         if (f.flEqual)
3663         {
3664             foreach (i ; 0 .. delta / 2 + ((delta % 2 == 1 && f.flDash) ? 1 : 0))
3665                 put(w, ' ');
3666         }
3667         else if (f.flDash)
3668         {
3669             foreach (i ; 0 .. delta)
3670                 put(w, ' ');
3671         }
3672     }
3673 }
3674 
3675 @safe pure unittest
3676 {
3677     import std.array : appender;
3678     import std.format : singleSpec;
3679 
3680     auto w = appender!string();
3681     auto spec = singleSpec("%s");
3682     writeAligned(w, "pre", "grouping", "suf", spec);
3683     assert(w.data == "pregroupingsuf", w.data);
3684 
3685     w = appender!string();
3686     spec = singleSpec("%20s");
3687     writeAligned(w, "pre", "grouping", "suf", spec);
3688     assert(w.data == "      pregroupingsuf", w.data);
3689 
3690     w = appender!string();
3691     spec = singleSpec("%-20s");
3692     writeAligned(w, "pre", "grouping", "suf", spec);
3693     assert(w.data == "pregroupingsuf      ", w.data);
3694 
3695     w = appender!string();
3696     spec = singleSpec("%020s");
3697     writeAligned(w, "pre", "grouping", "suf", spec);
3698     assert(w.data == "pre000000groupingsuf", w.data);
3699 
3700     w = appender!string();
3701     spec = singleSpec("%-020s");
3702     writeAligned(w, "pre", "grouping", "suf", spec);
3703     assert(w.data == "pregroupingsuf      ", w.data);
3704 
3705     w = appender!string();
3706     spec = singleSpec("%20,1s");
3707     writeAligned(w, "pre", "grouping", "suf", spec);
3708     assert(w.data == "preg,r,o,u,p,i,n,gsuf", w.data);
3709 
3710     w = appender!string();
3711     spec = singleSpec("%20,2s");
3712     writeAligned(w, "pre", "grouping", "suf", spec);
3713     assert(w.data == "   pregr,ou,pi,ngsuf", w.data);
3714 
3715     w = appender!string();
3716     spec = singleSpec("%20,3s");
3717     writeAligned(w, "pre", "grouping", "suf", spec);
3718     assert(w.data == "    pregr,oup,ingsuf", w.data);
3719 
3720     w = appender!string();
3721     spec = singleSpec("%20,10s");
3722     writeAligned(w, "pre", "grouping", "suf", spec);
3723     assert(w.data == "      pregroupingsuf", w.data);
3724 
3725     w = appender!string();
3726     spec = singleSpec("%020,1s");
3727     writeAligned(w, "pre", "grouping", "suf", spec);
3728     assert(w.data == "preg,r,o,u,p,i,n,gsuf", w.data);
3729 
3730     w = appender!string();
3731     spec = singleSpec("%020,2s");
3732     writeAligned(w, "pre", "grouping", "suf", spec);
3733     assert(w.data == "pre00,gr,ou,pi,ngsuf", w.data);
3734 
3735     w = appender!string();
3736     spec = singleSpec("%020,3s");
3737     writeAligned(w, "pre", "grouping", "suf", spec);
3738     assert(w.data == "pre00,0gr,oup,ingsuf", w.data);
3739 
3740     w = appender!string();
3741     spec = singleSpec("%020,10s");
3742     writeAligned(w, "pre", "grouping", "suf", spec);
3743     assert(w.data == "pre000,00groupingsuf", w.data);
3744 
3745     w = appender!string();
3746     spec = singleSpec("%021,3s");
3747     writeAligned(w, "pre", "grouping", "suf", spec);
3748     assert(w.data == "pre000,0gr,oup,ingsuf", w.data);
3749 
3750     // According to https://github.com/dlang/phobos/pull/7112 this
3751     // is defined by POSIX standard:
3752     w = appender!string();
3753     spec = singleSpec("%022,3s");
3754     writeAligned(w, "pre", "grouping", "suf", spec);
3755     assert(w.data == "pre0,000,0gr,oup,ingsuf", w.data);
3756 
3757     w = appender!string();
3758     spec = singleSpec("%023,3s");
3759     writeAligned(w, "pre", "grouping", "suf", spec);
3760     assert(w.data == "pre0,000,0gr,oup,ingsuf", w.data);
3761 
3762     w = appender!string();
3763     spec = singleSpec("%,3s");
3764     writeAligned(w, "pre", "grouping", "suf", spec);
3765     assert(w.data == "pregr,oup,ingsuf", w.data);
3766 }
3767 
3768 @safe pure unittest
3769 {
3770     import std.array : appender;
3771     import std.format : singleSpec;
3772 
3773     auto w = appender!string();
3774     auto spec = singleSpec("%.10s");
3775     writeAligned(w, "pre", "grouping", "suf", spec, true);
3776     assert(w.data == "pre00groupingsuf", w.data);
3777 
3778     w = appender!string();
3779     spec = singleSpec("%.10,3s");
3780     writeAligned(w, "pre", "grouping", "suf", spec, true);
3781     assert(w.data == "pre0,0gr,oup,ingsuf", w.data);
3782 
3783     w = appender!string();
3784     spec = singleSpec("%25.10,3s");
3785     writeAligned(w, "pre", "grouping", "suf", spec, true);
3786     assert(w.data == "      pre0,0gr,oup,ingsuf", w.data);
3787 
3788     // precision has precedence over zero flag
3789     w = appender!string();
3790     spec = singleSpec("%025.12,3s");
3791     writeAligned(w, "pre", "grouping", "suf", spec, true);
3792     assert(w.data == "    pre000,0gr,oup,ingsuf", w.data);
3793 
3794     w = appender!string();
3795     spec = singleSpec("%025.13,3s");
3796     writeAligned(w, "pre", "grouping", "suf", spec, true);
3797     assert(w.data == "  pre0,000,0gr,oup,ingsuf", w.data);
3798 }
3799 
3800 @safe unittest
3801 {
3802     assert(format("%,d", 1000) == "1,000");
3803     assert(format("%,f", 1234567.891011) == "1,234,567.891011");
3804     assert(format("%,?d", '?', 1000) == "1?000");
3805     assert(format("%,1d", 1000) == "1,0,0,0", format("%,1d", 1000));
3806     assert(format("%,*d", 4, -12345) == "-1,2345");
3807     assert(format("%,*?d", 4, '_', -12345) == "-1_2345");
3808     assert(format("%,6?d", '_', -12345678) == "-12_345678");
3809     assert(format("%12,3.3f", 1234.5678) == "   1,234.568", "'" ~
3810            format("%12,3.3f", 1234.5678) ~ "'");
3811 }
3812 
3813 private long getWidth(T)(T s)
3814 {
3815     import std.algorithm.searching : all;
3816     import std.uni : graphemeStride;
3817 
3818     // check for non-ascii character
3819     if (s.all!(a => a <= 0x7F)) return s.length;
3820 
3821     //TODO: optimize this
3822     long width = 0;
3823     for (size_t i; i < s.length; i += graphemeStride(s, i))
3824         ++width;
3825     return width;
3826 }
3827 
3828 enum RoundingClass { ZERO, LOWER, FIVE, UPPER }
3829 enum RoundingMode { up, down, toZero, toNearestTiesToEven, toNearestTiesAwayFromZero }
3830 
3831 bool round(T)(ref T sequence, size_t left, size_t right, RoundingClass type, bool negative, char max = '9')
3832 in (left >= 0) // should be left > 0, but if you know ahead, that there's no carry, left == 0 is fine
3833 in (left < sequence.length)
3834 in (right >= 0)
3835 in (right <= sequence.length)
3836 in (right >= left)
3837 in (max == '9' || max == 'f' || max == 'F')
3838 {
3839     import std.math.hardware;
3840 
3841     auto mode = RoundingMode.toNearestTiesToEven;
3842 
3843     if (!__ctfe)
3844     {
3845         // std.math's FloatingPointControl isn't available on all target platforms
3846         static if (is(FloatingPointControl))
3847         {
3848             switch (FloatingPointControl.rounding)
3849             {
3850             case FloatingPointControl.roundUp:
3851                 mode = RoundingMode.up;
3852                 break;
3853             case FloatingPointControl.roundDown:
3854                 mode = RoundingMode.down;
3855                 break;
3856             case FloatingPointControl.roundToZero:
3857                 mode = RoundingMode.toZero;
3858                 break;
3859             case FloatingPointControl.roundToNearest:
3860                 mode = RoundingMode.toNearestTiesToEven;
3861                 break;
3862             default: assert(false, "Unknown floating point rounding mode");
3863             }
3864         }
3865     }
3866 
3867     bool roundUp = false;
3868     if (mode == RoundingMode.up)
3869         roundUp = type != RoundingClass.ZERO && !negative;
3870     else if (mode == RoundingMode.down)
3871         roundUp = type != RoundingClass.ZERO && negative;
3872     else if (mode == RoundingMode.toZero)
3873         roundUp = false;
3874     else
3875     {
3876         roundUp = type == RoundingClass.UPPER;
3877 
3878         if (type == RoundingClass.FIVE)
3879         {
3880             // IEEE754 allows for two different ways of implementing roundToNearest:
3881 
3882             if (mode == RoundingMode.toNearestTiesAwayFromZero)
3883                 roundUp = true;
3884             else
3885             {
3886                 // Round to nearest, ties to even
3887                 auto last = sequence[right - 1];
3888                 if (last == '.') last = sequence[right - 2];
3889                 roundUp = (last <= '9' && last % 2 != 0) || (last > '9' && last % 2 == 0);
3890             }
3891         }
3892     }
3893 
3894     if (!roundUp) return false;
3895 
3896     foreach_reverse (i;left .. right)
3897     {
3898         if (sequence[i] == '.') continue;
3899         if (sequence[i] == max)
3900             sequence[i] = '0';
3901         else
3902         {
3903             if (max != '9' && sequence[i] == '9')
3904                 sequence[i] = max == 'f' ? 'a' : 'A';
3905             else
3906                 sequence[i]++;
3907             return false;
3908         }
3909     }
3910 
3911     sequence[left - 1] = '1';
3912     return true;
3913 }
3914 
3915 @safe unittest
3916 {
3917     char[10] c;
3918     size_t left = 5;
3919     size_t right = 8;
3920 
3921     c[4 .. 8] = "x.99";
3922     assert(round(c, left, right, RoundingClass.UPPER, false) == true);
3923     assert(c[4 .. 8] == "1.00");
3924 
3925     c[4 .. 8] = "x.99";
3926     assert(round(c, left, right, RoundingClass.FIVE, false) == true);
3927     assert(c[4 .. 8] == "1.00");
3928 
3929     c[4 .. 8] = "x.99";
3930     assert(round(c, left, right, RoundingClass.LOWER, false) == false);
3931     assert(c[4 .. 8] == "x.99");
3932 
3933     c[4 .. 8] = "x.99";
3934     assert(round(c, left, right, RoundingClass.ZERO, false) == false);
3935     assert(c[4 .. 8] == "x.99");
3936 
3937     import std.math.hardware;
3938     static if (is(FloatingPointControl))
3939     {
3940         FloatingPointControl fpctrl;
3941 
3942         fpctrl.rounding = FloatingPointControl.roundUp;
3943 
3944         c[4 .. 8] = "x.99";
3945         assert(round(c, left, right, RoundingClass.UPPER, false) == true);
3946         assert(c[4 .. 8] == "1.00");
3947 
3948         c[4 .. 8] = "x.99";
3949         assert(round(c, left, right, RoundingClass.FIVE, false) == true);
3950         assert(c[4 .. 8] == "1.00");
3951 
3952         c[4 .. 8] = "x.99";
3953         assert(round(c, left, right, RoundingClass.LOWER, false) == true);
3954         assert(c[4 .. 8] == "1.00");
3955 
3956         c[4 .. 8] = "x.99";
3957         assert(round(c, left, right, RoundingClass.ZERO, false) == false);
3958         assert(c[4 .. 8] == "x.99");
3959 
3960         fpctrl.rounding = FloatingPointControl.roundDown;
3961 
3962         c[4 .. 8] = "x.99";
3963         assert(round(c, left, right, RoundingClass.UPPER, false) == false);
3964         assert(c[4 .. 8] == "x.99");
3965 
3966         c[4 .. 8] = "x.99";
3967         assert(round(c, left, right, RoundingClass.FIVE, false) == false);
3968         assert(c[4 .. 8] == "x.99");
3969 
3970         c[4 .. 8] = "x.99";
3971         assert(round(c, left, right, RoundingClass.LOWER, false) == false);
3972         assert(c[4 .. 8] == "x.99");
3973 
3974         c[4 .. 8] = "x.99";
3975         assert(round(c, left, right, RoundingClass.ZERO, false) == false);
3976         assert(c[4 .. 8] == "x.99");
3977 
3978         fpctrl.rounding = FloatingPointControl.roundToZero;
3979 
3980         c[4 .. 8] = "x.99";
3981         assert(round(c, left, right, RoundingClass.UPPER, false) == false);
3982         assert(c[4 .. 8] == "x.99");
3983 
3984         c[4 .. 8] = "x.99";
3985         assert(round(c, left, right, RoundingClass.FIVE, false) == false);
3986         assert(c[4 .. 8] == "x.99");
3987 
3988         c[4 .. 8] = "x.99";
3989         assert(round(c, left, right, RoundingClass.LOWER, false) == false);
3990         assert(c[4 .. 8] == "x.99");
3991 
3992         c[4 .. 8] = "x.99";
3993         assert(round(c, left, right, RoundingClass.ZERO, false) == false);
3994         assert(c[4 .. 8] == "x.99");
3995     }
3996 }
3997 
3998 @safe unittest
3999 {
4000     char[10] c;
4001     size_t left = 5;
4002     size_t right = 8;
4003 
4004     c[4 .. 8] = "x8.5";
4005     assert(round(c, left, right, RoundingClass.UPPER, true) == false);
4006     assert(c[4 .. 8] == "x8.6");
4007 
4008     c[4 .. 8] = "x8.5";
4009     assert(round(c, left, right, RoundingClass.FIVE, true) == false);
4010     assert(c[4 .. 8] == "x8.6");
4011 
4012     c[4 .. 8] = "x8.4";
4013     assert(round(c, left, right, RoundingClass.FIVE, true) == false);
4014     assert(c[4 .. 8] == "x8.4");
4015 
4016     c[4 .. 8] = "x8.5";
4017     assert(round(c, left, right, RoundingClass.LOWER, true) == false);
4018     assert(c[4 .. 8] == "x8.5");
4019 
4020     c[4 .. 8] = "x8.5";
4021     assert(round(c, left, right, RoundingClass.ZERO, true) == false);
4022     assert(c[4 .. 8] == "x8.5");
4023 
4024     import std.math.hardware;
4025     static if (is(FloatingPointControl))
4026     {
4027         FloatingPointControl fpctrl;
4028 
4029         fpctrl.rounding = FloatingPointControl.roundUp;
4030 
4031         c[4 .. 8] = "x8.5";
4032         assert(round(c, left, right, RoundingClass.UPPER, true) == false);
4033         assert(c[4 .. 8] == "x8.5");
4034 
4035         c[4 .. 8] = "x8.5";
4036         assert(round(c, left, right, RoundingClass.FIVE, true) == false);
4037         assert(c[4 .. 8] == "x8.5");
4038 
4039         c[4 .. 8] = "x8.5";
4040         assert(round(c, left, right, RoundingClass.LOWER, true) == false);
4041         assert(c[4 .. 8] == "x8.5");
4042 
4043         c[4 .. 8] = "x8.5";
4044         assert(round(c, left, right, RoundingClass.ZERO, true) == false);
4045         assert(c[4 .. 8] == "x8.5");
4046 
4047         fpctrl.rounding = FloatingPointControl.roundDown;
4048 
4049         c[4 .. 8] = "x8.5";
4050         assert(round(c, left, right, RoundingClass.UPPER, true) == false);
4051         assert(c[4 .. 8] == "x8.6");
4052 
4053         c[4 .. 8] = "x8.5";
4054         assert(round(c, left, right, RoundingClass.FIVE, true) == false);
4055         assert(c[4 .. 8] == "x8.6");
4056 
4057         c[4 .. 8] = "x8.5";
4058         assert(round(c, left, right, RoundingClass.LOWER, true) == false);
4059         assert(c[4 .. 8] == "x8.6");
4060 
4061         c[4 .. 8] = "x8.5";
4062         assert(round(c, left, right, RoundingClass.ZERO, true) == false);
4063         assert(c[4 .. 8] == "x8.5");
4064 
4065         fpctrl.rounding = FloatingPointControl.roundToZero;
4066 
4067         c[4 .. 8] = "x8.5";
4068         assert(round(c, left, right, RoundingClass.UPPER, true) == false);
4069         assert(c[4 .. 8] == "x8.5");
4070 
4071         c[4 .. 8] = "x8.5";
4072         assert(round(c, left, right, RoundingClass.FIVE, true) == false);
4073         assert(c[4 .. 8] == "x8.5");
4074 
4075         c[4 .. 8] = "x8.5";
4076         assert(round(c, left, right, RoundingClass.LOWER, true) == false);
4077         assert(c[4 .. 8] == "x8.5");
4078 
4079         c[4 .. 8] = "x8.5";
4080         assert(round(c, left, right, RoundingClass.ZERO, true) == false);
4081         assert(c[4 .. 8] == "x8.5");
4082     }
4083 }
4084 
4085 @safe unittest
4086 {
4087     char[10] c;
4088     size_t left = 5;
4089     size_t right = 8;
4090 
4091     c[4 .. 8] = "x8.9";
4092     assert(round(c, left, right, RoundingClass.UPPER, true, 'f') == false);
4093     assert(c[4 .. 8] == "x8.a");
4094 
4095     c[4 .. 8] = "x8.9";
4096     assert(round(c, left, right, RoundingClass.UPPER, true, 'F') == false);
4097     assert(c[4 .. 8] == "x8.A");
4098 
4099     c[4 .. 8] = "x8.f";
4100     assert(round(c, left, right, RoundingClass.UPPER, true, 'f') == false);
4101     assert(c[4 .. 8] == "x9.0");
4102 }
4103 
4104 version (StdUnittest)
4105 private void formatTest(T)(T val, string expected, size_t ln = __LINE__, string fn = __FILE__)
4106 {
4107     formatTest(val, [expected], ln, fn);
4108 }
4109 
4110 version (StdUnittest)
4111 private void formatTest(T)(string fmt, T val, string expected, size_t ln = __LINE__, string fn = __FILE__) @safe
4112 {
4113     formatTest(fmt, val, [expected], ln, fn);
4114 }
4115 
4116 version (StdUnittest)
4117 private void formatTest(T)(T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__)
4118 {
4119     import core.exception : AssertError;
4120     import std.algorithm.searching : canFind;
4121     import std.array : appender;
4122     import std.conv : text;
4123     import std.exception : enforce;
4124     import std.format.write : formatValue;
4125 
4126     FormatSpec!char f;
4127     auto w = appender!string();
4128     formatValue(w, val, f);
4129     enforce!AssertError(expected.canFind(w.data),
4130         text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln);
4131 }
4132 
4133 version (StdUnittest)
4134 private void formatTest(T)(string fmt, T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__) @safe
4135 {
4136     import core.exception : AssertError;
4137     import std.algorithm.searching : canFind;
4138     import std.array : appender;
4139     import std.conv : text;
4140     import std.exception : enforce;
4141     import std.format.write : formattedWrite;
4142 
4143     auto w = appender!string();
4144     formattedWrite(w, fmt, val);
4145     enforce!AssertError(expected.canFind(w.data),
4146         text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln);
4147 }
4148