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