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     // log2 is broken for x87-reals on some computers in CTFE
906     // the following test excludes these computers from the test
907     // (https://issues.dlang.org/show_bug.cgi?id=21757)
908     enum test = cast(int) log2(3.05e2312L);
909     static if (real.mant_dig == 64 && test == 7681) // 80 bit reals
910     {
911         static assert(format!"%e"(real.max) == "1.189731e+4932");
912     }
913 }
914 
915 // https://issues.dlang.org/show_bug.cgi?id=21842
916 @safe pure unittest
917 {
918     assert(format!"%-+05,g"(1.0) == "+1   ");
919 }
920 
921 // https://issues.dlang.org/show_bug.cgi?id=20536
922 @safe pure unittest
923 {
924     real r = .00000095367431640625L;
925     assert(format("%a", r) == "0x1p-20");
926 }
927 
928 // https://issues.dlang.org/show_bug.cgi?id=21840
929 @safe pure unittest
930 {
931     assert(format!"% 0,e"(0.0) == " 0.000000e+00");
932 }
933 
934 // https://issues.dlang.org/show_bug.cgi?id=21841
935 @safe pure unittest
936 {
937     assert(format!"%0.0,e"(0.0) == "0e+00");
938 }
939 
940 // https://issues.dlang.org/show_bug.cgi?id=21836
941 @safe pure unittest
942 {
943     assert(format!"%-5,1g"(0.0) == "0    ");
944 }
945 
946 // https://issues.dlang.org/show_bug.cgi?id=21838
947 @safe pure unittest
948 {
949     assert(format!"%#,a"(0.0) == "0x0.p+0");
950 }
951 
952 /*
953     Formatting a `creal` is deprecated but still kept around for a while.
954  */
955 deprecated("Use of complex types is deprecated. Use std.complex")
956 void formatValueImpl(Writer, T, Char)(auto ref Writer w, const(T) obj, scope const ref FormatSpec!Char f)
957 if (is(immutable T : immutable creal) && !is(T == enum) && !hasToString!(T, Char))
958 {
959     import std.range.primitives : put;
960 
961     immutable creal val = obj;
962 
963     formatValueImpl(w, val.re, f);
964     if (val.im >= 0)
965     {
966         put(w, '+');
967     }
968     formatValueImpl(w, val.im, f);
969     put(w, 'i');
970 }
971 
972 /*
973     Formatting an `ireal` is deprecated but still kept around for a while.
974  */
975 deprecated("Use of imaginary types is deprecated. Use std.complex")
976 void formatValueImpl(Writer, T, Char)(auto ref Writer w, const(T) obj, scope const ref FormatSpec!Char f)
977 if (is(immutable T : immutable ireal) && !is(T == enum) && !hasToString!(T, Char))
978 {
979     import std.range.primitives : put;
980 
981     immutable ireal val = obj;
982 
983     formatValueImpl(w, val.im, f);
984     put(w, 'i');
985 }
986 
987 /*
988     Individual characters are formatted as Unicode characters with `%s`
989     and as integers with integral-specific format specs
990  */
991 void formatValueImpl(Writer, T, Char)(auto ref Writer w, const(T) obj, scope const ref FormatSpec!Char f)
992 if (is(CharTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
993 {
994     import std.meta : AliasSeq;
995 
996     CharTypeOf!T[1] val = obj;
997 
998     if (f.spec == 's' || f.spec == 'c')
999         writeAligned(w, val[], f);
1000     else
1001     {
1002         alias U = AliasSeq!(ubyte, ushort, uint)[CharTypeOf!T.sizeof/2];
1003         formatValueImpl(w, cast(U) val[0], f);
1004     }
1005 }
1006 
1007 @safe pure unittest
1008 {
1009     assertCTFEable!(
1010     {
1011         formatTest('c', "c");
1012     });
1013 }
1014 
1015 @safe unittest
1016 {
1017     struct S1
1018     {
1019         char val;
1020         alias val this;
1021     }
1022 
1023     struct S2
1024     {
1025         char val;
1026         alias val this;
1027         string toString() const { return "S"; }
1028     }
1029 
1030     formatTest(S1('c'), "c");
1031     formatTest(S2('c'), "S");
1032 }
1033 
1034 @safe unittest
1035 {
1036     //Little Endian
1037     formatTest("%-r", cast( char)'c', ['c'         ]);
1038     formatTest("%-r", cast(wchar)'c', ['c', 0      ]);
1039     formatTest("%-r", cast(dchar)'c', ['c', 0, 0, 0]);
1040     formatTest("%-r", '本', ['\x2c', '\x67'] );
1041 
1042     //Big Endian
1043     formatTest("%+r", cast( char)'c', [         'c']);
1044     formatTest("%+r", cast(wchar)'c', [0,       'c']);
1045     formatTest("%+r", cast(dchar)'c', [0, 0, 0, 'c']);
1046     formatTest("%+r", '本', ['\x67', '\x2c']);
1047 }
1048 
1049 
1050 @safe pure unittest
1051 {
1052     string t1 = format("[%6s] [%-6s]", 'A', 'A');
1053     assert(t1 == "[     A] [A     ]");
1054     string t2 = format("[%6s] [%-6s]", '本', '本');
1055     assert(t2 == "[     本] [本     ]");
1056 }
1057 
1058 /*
1059     Strings are formatted like $(REF printf, core, stdc, stdio)
1060  */
1061 void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope const(T) obj,
1062     scope const ref FormatSpec!Char f)
1063 if (is(StringTypeOf!T) && !is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
1064 {
1065     Unqual!(const(StringTypeOf!T)) val = obj;  // for `alias this`, see bug5371
1066     formatRange(w, val, f);
1067 }
1068 
1069 @safe unittest
1070 {
1071     formatTest("abc", "abc");
1072 }
1073 
1074 @safe pure unittest
1075 {
1076     import std.exception : collectExceptionMsg;
1077     import std.range.primitives : back;
1078 
1079     assert(collectExceptionMsg(format("%d", "hi")).back == 'd');
1080 }
1081 
1082 @safe unittest
1083 {
1084     // Test for bug 5371 for structs
1085     struct S1
1086     {
1087         const string var;
1088         alias var this;
1089     }
1090 
1091     struct S2
1092     {
1093         string var;
1094         alias var this;
1095     }
1096 
1097     formatTest(S1("s1"), "s1");
1098     formatTest(S2("s2"), "s2");
1099 }
1100 
1101 @safe unittest
1102 {
1103     struct S3
1104     {
1105         string val; alias val this;
1106         string toString() const { return "S"; }
1107     }
1108 
1109     formatTest(S3("s3"), "S");
1110 }
1111 
1112 @safe pure unittest
1113 {
1114     //Little Endian
1115     formatTest("%-r", "ab"c, ['a'         , 'b'         ]);
1116     formatTest("%-r", "ab"w, ['a', 0      , 'b', 0      ]);
1117     formatTest("%-r", "ab"d, ['a', 0, 0, 0, 'b', 0, 0, 0]);
1118     formatTest("%-r", "日本語"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac',
1119                                   '\xe8', '\xaa', '\x9e']);
1120     formatTest("%-r", "日本語"w, ['\xe5', '\x65', '\x2c', '\x67', '\x9e', '\x8a']);
1121     formatTest("%-r", "日本語"d, ['\xe5', '\x65', '\x00', '\x00', '\x2c', '\x67',
1122                                   '\x00', '\x00', '\x9e', '\x8a', '\x00', '\x00']);
1123 
1124     //Big Endian
1125     formatTest("%+r", "ab"c, [         'a',          'b']);
1126     formatTest("%+r", "ab"w, [      0, 'a',       0, 'b']);
1127     formatTest("%+r", "ab"d, [0, 0, 0, 'a', 0, 0, 0, 'b']);
1128     formatTest("%+r", "日本語"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac',
1129                                   '\xe8', '\xaa', '\x9e']);
1130     formatTest("%+r", "日本語"w, ['\x65', '\xe5', '\x67', '\x2c', '\x8a', '\x9e']);
1131     formatTest("%+r", "日本語"d, ['\x00', '\x00', '\x65', '\xe5', '\x00', '\x00',
1132                                   '\x67', '\x2c', '\x00', '\x00', '\x8a', '\x9e']);
1133 }
1134 
1135 @safe pure unittest
1136 {
1137     string t1 = format("[%6s] [%-6s]", "AB", "AB");
1138     assert(t1 == "[    AB] [AB    ]");
1139     string t2 = format("[%6s] [%-6s]", "本Ä", "本Ä");
1140     assert(t2 == "[    本Ä] [本Ä    ]");
1141 }
1142 
1143 // https://issues.dlang.org/show_bug.cgi?id=6640
1144 @safe unittest
1145 {
1146     import std.range.primitives : front, popFront;
1147 
1148     struct Range
1149     {
1150         @safe:
1151 
1152         string value;
1153         @property bool empty() const { return !value.length; }
1154         @property dchar front() const { return value.front; }
1155         void popFront() { value.popFront(); }
1156 
1157         @property size_t length() const { return value.length; }
1158     }
1159     immutable table =
1160     [
1161         ["[%s]", "[string]"],
1162         ["[%10s]", "[    string]"],
1163         ["[%-10s]", "[string    ]"],
1164         ["[%(%02x %)]", "[73 74 72 69 6e 67]"],
1165         ["[%(%c %)]", "[s t r i n g]"],
1166     ];
1167     foreach (e; table)
1168     {
1169         formatTest(e[0], "string", e[1]);
1170         formatTest(e[0], Range("string"), e[1]);
1171     }
1172 }
1173 
1174 @safe unittest
1175 {
1176     import std.meta : AliasSeq;
1177 
1178     // string literal from valid UTF sequence is encoding free.
1179     static foreach (StrType; AliasSeq!(string, wstring, dstring))
1180     {
1181         // Valid and printable (ASCII)
1182         formatTest([cast(StrType)"hello"],
1183                    `["hello"]`);
1184 
1185         // 1 character escape sequences (' is not escaped in strings)
1186         formatTest([cast(StrType)"\"'\0\\\a\b\f\n\r\t\v"],
1187                    `["\"'\0\\\a\b\f\n\r\t\v"]`);
1188 
1189         // 1 character optional escape sequences
1190         formatTest([cast(StrType)"\'\?"],
1191                    `["'?"]`);
1192 
1193         // Valid and non-printable code point (<= U+FF)
1194         formatTest([cast(StrType)"\x10\x1F\x20test"],
1195                    `["\x10\x1F test"]`);
1196 
1197         // Valid and non-printable code point (<= U+FFFF)
1198         formatTest([cast(StrType)"\u200B..\u200F"],
1199                    `["\u200B..\u200F"]`);
1200 
1201         // Valid and non-printable code point (<= U+10FFFF)
1202         formatTest([cast(StrType)"\U000E0020..\U000E007F"],
1203                    `["\U000E0020..\U000E007F"]`);
1204     }
1205 
1206     // invalid UTF sequence needs hex-string literal postfix (c/w/d)
1207     () @trusted
1208     {
1209         // U+FFFF with UTF-8 (Invalid code point for interchange)
1210         formatTest([cast(string)[0xEF, 0xBF, 0xBF]],
1211                    `[[cast(char) 0xEF, cast(char) 0xBF, cast(char) 0xBF]]`);
1212 
1213         // U+FFFF with UTF-16 (Invalid code point for interchange)
1214         formatTest([cast(wstring)[0xFFFF]],
1215                    `[[cast(wchar) 0xFFFF]]`);
1216 
1217         // U+FFFF with UTF-32 (Invalid code point for interchange)
1218         formatTest([cast(dstring)[0xFFFF]],
1219                    `[[cast(dchar) 0xFFFF]]`);
1220     } ();
1221 }
1222 
1223 /*
1224     Static-size arrays are formatted as dynamic arrays.
1225  */
1226 void formatValueImpl(Writer, T, Char)(auto ref Writer w, auto ref T obj,
1227     scope const ref FormatSpec!Char f)
1228 if (is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
1229 {
1230     formatValueImpl(w, obj[], f);
1231 }
1232 
1233 // Test for https://issues.dlang.org/show_bug.cgi?id=8310
1234 @safe unittest
1235 {
1236     import std.array : appender;
1237     import std.format : formatValue;
1238 
1239     FormatSpec!char f;
1240     auto w = appender!string();
1241 
1242     char[2] two = ['a', 'b'];
1243     formatValue(w, two, f);
1244 
1245     char[2] getTwo() { return two; }
1246     formatValue(w, getTwo(), f);
1247 }
1248 
1249 // https://issues.dlang.org/show_bug.cgi?id=18205
1250 @safe pure unittest
1251 {
1252     assert("|%8s|".format("abc")       == "|     abc|");
1253     assert("|%8s|".format("αβγ")       == "|     αβγ|");
1254     assert("|%8s|".format("   ")       == "|        |");
1255     assert("|%8s|".format("été"d)      == "|     été|");
1256     assert("|%8s|".format("été 2018"w) == "|été 2018|");
1257 
1258     assert("%2s".format("e\u0301"w) == " e\u0301");
1259     assert("%2s".format("a\u0310\u0337"d) == " a\u0310\u0337");
1260 }
1261 
1262 /*
1263     Dynamic arrays are formatted as input ranges.
1264  */
1265 void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
1266 if (is(DynamicArrayTypeOf!T) && !is(StringTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
1267 {
1268     static if (is(immutable(ArrayTypeOf!T) == immutable(void[])))
1269     {
1270         formatValueImpl(w, cast(const ubyte[]) obj, f);
1271     }
1272     else static if (!isInputRange!T)
1273     {
1274         alias U = Unqual!(ArrayTypeOf!T);
1275         static assert(isInputRange!U, U.stringof ~ " must be an InputRange");
1276         U val = obj;
1277         formatValueImpl(w, val, f);
1278     }
1279     else
1280     {
1281         formatRange(w, obj, f);
1282     }
1283 }
1284 
1285 // https://issues.dlang.org/show_bug.cgi?id=20848
1286 @safe unittest
1287 {
1288     class C
1289     {
1290         immutable(void)[] data;
1291     }
1292 
1293     import std.typecons : Nullable;
1294     Nullable!C c;
1295 }
1296 
1297 // alias this, input range I/F, and toString()
1298 @safe unittest
1299 {
1300     struct S(int flags)
1301     {
1302         int[] arr;
1303         static if (flags & 1)
1304             alias arr this;
1305 
1306         static if (flags & 2)
1307         {
1308             @property bool empty() const { return arr.length == 0; }
1309             @property int front() const { return arr[0] * 2; }
1310             void popFront() { arr = arr[1 .. $]; }
1311         }
1312 
1313         static if (flags & 4)
1314             string toString() const { return "S"; }
1315     }
1316 
1317     formatTest(S!0b000([0, 1, 2]), "S!0([0, 1, 2])");
1318     formatTest(S!0b001([0, 1, 2]), "[0, 1, 2]");        // Test for bug 7628
1319     formatTest(S!0b010([0, 1, 2]), "[0, 2, 4]");
1320     formatTest(S!0b011([0, 1, 2]), "[0, 2, 4]");
1321     formatTest(S!0b100([0, 1, 2]), "S");
1322     formatTest(S!0b101([0, 1, 2]), "S");                // Test for bug 7628
1323     formatTest(S!0b110([0, 1, 2]), "S");
1324     formatTest(S!0b111([0, 1, 2]), "S");
1325 }
1326 
1327 @safe unittest
1328 {
1329     // void[]
1330     void[] val0;
1331     formatTest(val0, "[]");
1332 
1333     void[] val = cast(void[]) cast(ubyte[])[1, 2, 3];
1334     formatTest(val, "[1, 2, 3]");
1335 
1336     void[0] sval0 = [];
1337     formatTest(sval0, "[]");
1338 
1339     void[3] sval = () @trusted { return cast(void[3]) cast(ubyte[3])[1, 2, 3]; } ();
1340     formatTest(sval, "[1, 2, 3]");
1341 }
1342 
1343 @safe unittest
1344 {
1345     // const(T[]) -> const(T)[]
1346     const short[] a = [1, 2, 3];
1347     formatTest(a, "[1, 2, 3]");
1348 
1349     struct S
1350     {
1351         const(int[]) arr;
1352         alias arr this;
1353     }
1354 
1355     auto s = S([1,2,3]);
1356     formatTest(s, "[1, 2, 3]");
1357 }
1358 
1359 @safe unittest
1360 {
1361     // nested range formatting with array of string
1362     formatTest("%({%(%02x %)}%| %)", ["test", "msg"],
1363                `{74 65 73 74} {6d 73 67}`);
1364 }
1365 
1366 @safe unittest
1367 {
1368     // stop auto escaping inside range formatting
1369     auto arr = ["hello", "world"];
1370     formatTest("%(%s, %)",  arr, `"hello", "world"`);
1371     formatTest("%-(%s, %)", arr, `hello, world`);
1372 
1373     auto aa1 = [1:"hello", 2:"world"];
1374     formatTest("%(%s:%s, %)",  aa1, [`1:"hello", 2:"world"`, `2:"world", 1:"hello"`]);
1375     formatTest("%-(%s:%s, %)", aa1, [`1:hello, 2:world`, `2:world, 1:hello`]);
1376 
1377     auto aa2 = [1:["ab", "cd"], 2:["ef", "gh"]];
1378     formatTest("%-(%s:%s, %)",        aa2, [`1:["ab", "cd"], 2:["ef", "gh"]`, `2:["ef", "gh"], 1:["ab", "cd"]`]);
1379     formatTest("%-(%s:%(%s%), %)",    aa2, [`1:"ab""cd", 2:"ef""gh"`, `2:"ef""gh", 1:"ab""cd"`]);
1380     formatTest("%-(%s:%-(%s%)%|, %)", aa2, [`1:abcd, 2:efgh`, `2:efgh, 1:abcd`]);
1381 }
1382 
1383 // https://issues.dlang.org/show_bug.cgi?id=18778
1384 @safe pure unittest
1385 {
1386     assert(format("%-(%1$s - %1$s, %)", ["A", "B", "C"]) == "A - A, B - B, C - C");
1387 }
1388 
1389 @safe pure unittest
1390 {
1391     int[] a = [ 1, 3, 2 ];
1392     formatTest("testing %(%s & %) embedded", a,
1393                "testing 1 & 3 & 2 embedded");
1394     formatTest("testing %((%s) %)) wyda3", a,
1395                "testing (1) (3) (2) wyda3");
1396 
1397     int[0] empt = [];
1398     formatTest("(%s)", empt, "([])");
1399 }
1400 
1401 // input range formatting
1402 private void formatRange(Writer, T, Char)(ref Writer w, ref T val, scope const ref FormatSpec!Char f)
1403 if (isInputRange!T)
1404 {
1405     import std.conv : text;
1406     import std.format : FormatException, formatValue, NoOpSink;
1407     import std.range.primitives : ElementType, empty, front, hasLength,
1408         walkLength, isForwardRange, isInfinite, popFront, put;
1409 
1410     // in this mode, we just want to do a representative print to discover
1411     // if the format spec is valid
1412     enum formatTestMode = is(Writer == NoOpSink);
1413 
1414     // Formatting character ranges like string
1415     if (f.spec == 's')
1416     {
1417         alias E = ElementType!T;
1418 
1419         static if (!is(E == enum) && is(CharTypeOf!E))
1420         {
1421             static if (is(StringTypeOf!T))
1422                 writeAligned(w, val[0 .. f.precision < $ ? f.precision : $], f);
1423             else
1424             {
1425                 if (!f.flDash)
1426                 {
1427                     static if (hasLength!T)
1428                     {
1429                         // right align
1430                         auto len = val.length;
1431                     }
1432                     else static if (isForwardRange!T && !isInfinite!T)
1433                     {
1434                         auto len = walkLength(val.save);
1435                     }
1436                     else
1437                     {
1438                         import std.format : enforceFmt;
1439                         enforceFmt(f.width == 0, "Cannot right-align a range without length");
1440                         size_t len = 0;
1441                     }
1442                     if (f.precision != f.UNSPECIFIED && len > f.precision)
1443                         len = f.precision;
1444 
1445                     if (f.width > len)
1446                         foreach (i ; 0 .. f.width - len)
1447                             put(w, ' ');
1448                     if (f.precision == f.UNSPECIFIED)
1449                         put(w, val);
1450                     else
1451                     {
1452                         size_t printed = 0;
1453                         for (; !val.empty && printed < f.precision; val.popFront(), ++printed)
1454                             put(w, val.front);
1455                     }
1456                 }
1457                 else
1458                 {
1459                     size_t printed = void;
1460 
1461                     // left align
1462                     if (f.precision == f.UNSPECIFIED)
1463                     {
1464                         static if (hasLength!T)
1465                         {
1466                             printed = val.length;
1467                             put(w, val);
1468                         }
1469                         else
1470                         {
1471                             printed = 0;
1472                             for (; !val.empty; val.popFront(), ++printed)
1473                             {
1474                                 put(w, val.front);
1475                                 static if (formatTestMode) break; // one is enough to test
1476                             }
1477                         }
1478                     }
1479                     else
1480                     {
1481                         printed = 0;
1482                         for (; !val.empty && printed < f.precision; val.popFront(), ++printed)
1483                             put(w, val.front);
1484                     }
1485 
1486                     if (f.width > printed)
1487                         foreach (i ; 0 .. f.width - printed)
1488                             put(w, ' ');
1489                 }
1490             }
1491         }
1492         else
1493         {
1494             put(w, f.seqBefore);
1495             if (!val.empty)
1496             {
1497                 formatElement(w, val.front, f);
1498                 val.popFront();
1499                 for (size_t i; !val.empty; val.popFront(), ++i)
1500                 {
1501                     put(w, f.seqSeparator);
1502                     formatElement(w, val.front, f);
1503                     static if (formatTestMode) break; // one is enough to test
1504                 }
1505             }
1506             static if (!isInfinite!T) put(w, f.seqAfter);
1507         }
1508     }
1509     else if (f.spec == 'r')
1510     {
1511         static if (is(DynamicArrayTypeOf!T))
1512         {
1513             alias ARR = DynamicArrayTypeOf!T;
1514             scope a = cast(ARR) val;
1515             foreach (e ; a)
1516             {
1517                 formatValue(w, e, f);
1518                 static if (formatTestMode) break; // one is enough to test
1519             }
1520         }
1521         else
1522         {
1523             for (size_t i; !val.empty; val.popFront(), ++i)
1524             {
1525                 formatValue(w, val.front, f);
1526                 static if (formatTestMode) break; // one is enough to test
1527             }
1528         }
1529     }
1530     else if (f.spec == '(')
1531     {
1532         if (val.empty)
1533             return;
1534         // Nested specifier is to be used
1535         for (;;)
1536         {
1537             auto fmt = FormatSpec!Char(f.nested);
1538             w: while (true)
1539             {
1540                 immutable r = fmt.writeUpToNextSpec(w);
1541                 // There was no format specifier, so break
1542                 if (!r)
1543                     break;
1544                 if (f.flDash)
1545                     formatValue(w, val.front, fmt);
1546                 else
1547                     formatElement(w, val.front, fmt);
1548                 // Check if there will be a format specifier farther on in the
1549                 // string. If so, continue the loop, otherwise break. This
1550                 // prevents extra copies of the `sep` from showing up.
1551                 foreach (size_t i; 0 .. fmt.trailing.length)
1552                     if (fmt.trailing[i] == '%')
1553                         continue w;
1554                 break w;
1555             }
1556             static if (formatTestMode)
1557             {
1558                 break; // one is enough to test
1559             }
1560             else
1561             {
1562                 if (f.sep !is null)
1563                 {
1564                     put(w, fmt.trailing);
1565                     val.popFront();
1566                     if (val.empty)
1567                         break;
1568                     put(w, f.sep);
1569                 }
1570                 else
1571                 {
1572                     val.popFront();
1573                     if (val.empty)
1574                         break;
1575                     put(w, fmt.trailing);
1576                 }
1577             }
1578         }
1579     }
1580     else
1581         throw new FormatException(text("Incorrect format specifier for range: %", f.spec));
1582 }
1583 
1584 // https://issues.dlang.org/show_bug.cgi?id=20218
1585 @safe pure unittest
1586 {
1587     void notCalled()
1588     {
1589         import std.range : repeat;
1590 
1591         auto value = 1.repeat;
1592 
1593         // test that range is not evaluated to completion at compiletime
1594         format!"%s"(value);
1595     }
1596 }
1597 
1598 // character formatting with ecaping
1599 void formatChar(Writer)(ref Writer w, in dchar c, in char quote)
1600 {
1601     import std.format : formattedWrite;
1602     import std.range.primitives : put;
1603     import std.uni : isGraphical;
1604 
1605     string fmt;
1606     if (isGraphical(c))
1607     {
1608         if (c == quote || c == '\\')
1609             put(w, '\\');
1610         put(w, c);
1611         return;
1612     }
1613     else if (c <= 0xFF)
1614     {
1615         if (c < 0x20)
1616         {
1617             foreach (i, k; "\n\r\t\a\b\f\v\0")
1618             {
1619                 if (c == k)
1620                 {
1621                     put(w, '\\');
1622                     put(w, "nrtabfv0"[i]);
1623                     return;
1624                 }
1625             }
1626         }
1627         fmt = "\\x%02X";
1628     }
1629     else if (c <= 0xFFFF)
1630         fmt = "\\u%04X";
1631     else
1632         fmt = "\\U%08X";
1633 
1634     formattedWrite(w, fmt, cast(uint) c);
1635 }
1636 
1637 /*
1638     Associative arrays are formatted by using `':'` and $(D ", ") as
1639     separators, and enclosed by `'['` and `']'`.
1640  */
1641 void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f)
1642 if (is(AssocArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char))
1643 {
1644     import std.format : enforceFmt, formatValue;
1645     import std.range.primitives : put;
1646 
1647     AssocArrayTypeOf!T val = obj;
1648     const spec = f.spec;
1649 
1650     enforceFmt(spec == 's' || spec == '(',
1651         "incompatible format character for associative array argument: %" ~ spec);
1652 
1653     enum const(Char)[] defSpec = "%s" ~ f.keySeparator ~ "%s" ~ f.seqSeparator;
1654     auto fmtSpec = spec == '(' ? f.nested : defSpec;
1655 
1656     auto key_first = true;
1657 
1658     // testing correct nested format spec
1659     import std.format : NoOpSink;
1660     auto noop = NoOpSink();
1661     auto test = FormatSpec!Char(fmtSpec);
1662     enforceFmt(test.writeUpToNextSpec(noop),
1663         "nested format string for associative array contains no format specifier");
1664     enforceFmt(test.indexStart <= 2,
1665         "positional parameter in nested format string for associative array may only be 1 or 2");
1666     if (test.indexStart == 2)
1667         key_first = false;
1668 
1669     enforceFmt(test.writeUpToNextSpec(noop),
1670         "nested format string for associative array contains only one format specifier");
1671     enforceFmt(test.indexStart <= 2,
1672         "positional parameter in nested format string for associative array may only be 1 or 2");
1673     enforceFmt(test.indexStart == 0 || ((test.indexStart == 2) == key_first),
1674         "wrong combination of positional parameters in nested format string");
1675 
1676     enforceFmt(!test.writeUpToNextSpec(noop),
1677         "nested format string for associative array contains more than two format specifiers");
1678 
1679     size_t i = 0;
1680     immutable end = val.length;
1681 
1682     if (spec == 's')
1683         put(w, f.seqBefore);
1684     foreach (k, ref v; val)
1685     {
1686         auto fmt = FormatSpec!Char(fmtSpec);
1687 
1688         foreach (pos; 1 .. 3)
1689         {
1690             fmt.writeUpToNextSpec(w);
1691 
1692             if (key_first == (pos == 1))
1693             {
1694                 if (f.flDash)
1695                     formatValue(w, k, fmt);
1696                 else
1697                     formatElement(w, k, fmt);
1698             }
1699             else
1700             {
1701                 if (f.flDash)
1702                     formatValue(w, v, fmt);
1703                 else
1704                     formatElement(w, v, fmt);
1705             }
1706         }
1707 
1708         if (f.sep !is null)
1709         {
1710             fmt.writeUpToNextSpec(w);
1711             if (++i != end)
1712                 put(w, f.sep);
1713         }
1714         else
1715         {
1716             if (++i != end)
1717                 fmt.writeUpToNextSpec(w);
1718         }
1719     }
1720     if (spec == 's')
1721         put(w, f.seqAfter);
1722 }
1723 
1724 @safe unittest
1725 {
1726     import std.exception : collectExceptionMsg;
1727     import std.format : FormatException;
1728     import std.range.primitives : back;
1729 
1730     assert(collectExceptionMsg!FormatException(format("%d", [0:1])).back == 'd');
1731 
1732     int[string] aa0;
1733     formatTest(aa0, `[]`);
1734 
1735     // elements escaping
1736     formatTest(["aaa":1, "bbb":2],
1737                [`["aaa":1, "bbb":2]`, `["bbb":2, "aaa":1]`]);
1738     formatTest(['c':"str"],
1739                `['c':"str"]`);
1740     formatTest(['"':"\"", '\'':"'"],
1741                [`['"':"\"", '\'':"'"]`, `['\'':"'", '"':"\""]`]);
1742 
1743     // range formatting for AA
1744     auto aa3 = [1:"hello", 2:"world"];
1745     // escape
1746     formatTest("{%(%s:%s $ %)}", aa3,
1747                [`{1:"hello" $ 2:"world"}`, `{2:"world" $ 1:"hello"}`]);
1748     // use range formatting for key and value, and use %|
1749     formatTest("{%([%04d->%(%c.%)]%| $ %)}", aa3,
1750                [`{[0001->h.e.l.l.o] $ [0002->w.o.r.l.d]}`,
1751                 `{[0002->w.o.r.l.d] $ [0001->h.e.l.l.o]}`]);
1752 
1753     // https://issues.dlang.org/show_bug.cgi?id=12135
1754     formatTest("%(%s:<%s>%|,%)", [1:2], "1:<2>");
1755     formatTest("%(%s:<%s>%|%)" , [1:2], "1:<2>");
1756 }
1757 
1758 @safe unittest
1759 {
1760     struct S1
1761     {
1762         int[char] val;
1763         alias val this;
1764     }
1765 
1766     struct S2
1767     {
1768         int[char] val;
1769         alias val this;
1770         string toString() const { return "S"; }
1771     }
1772 
1773     formatTest(S1(['c':1, 'd':2]), [`['c':1, 'd':2]`, `['d':2, 'c':1]`]);
1774     formatTest(S2(['c':1, 'd':2]), "S");
1775 }
1776 
1777 // https://issues.dlang.org/show_bug.cgi?id=21875
1778 @safe unittest
1779 {
1780     import std.exception : assertThrown;
1781     import std.format : FormatException;
1782 
1783     auto aa = [ 1 : "x", 2 : "y", 3 : "z" ];
1784 
1785     assertThrown!FormatException(format("%(%)", aa));
1786     assertThrown!FormatException(format("%(%s%)", aa));
1787     assertThrown!FormatException(format("%(%s%s%s%)", aa));
1788 }
1789 
1790 @safe unittest
1791 {
1792     import std.exception : assertThrown;
1793     import std.format : FormatException;
1794 
1795     auto aa = [ 1 : "x", 2 : "y", 3 : "z" ];
1796 
1797     assertThrown!FormatException(format("%(%3$s%s%)", aa));
1798     assertThrown!FormatException(format("%(%s%3$s%)", aa));
1799     assertThrown!FormatException(format("%(%1$s%1$s%)", aa));
1800     assertThrown!FormatException(format("%(%2$s%2$s%)", aa));
1801     assertThrown!FormatException(format("%(%s%1$s%)", aa));
1802 }
1803 
1804 // https://issues.dlang.org/show_bug.cgi?id=21808
1805 @safe unittest
1806 {
1807     auto spelled = [ 1 : "one" ];
1808     assert(format("%-(%2$s (%1$s)%|, %)", spelled) == "one (1)");
1809 
1810     spelled[2] = "two";
1811     auto result = format("%-(%2$s (%1$s)%|, %)", spelled);
1812     assert(result == "one (1), two (2)" || result == "two (2), one (1)");
1813 }
1814 
1815 enum HasToStringResult
1816 {
1817     none,
1818     hasSomeToString,
1819     inCharSink,
1820     inCharSinkFormatString,
1821     inCharSinkFormatSpec,
1822     constCharSink,
1823     constCharSinkFormatString,
1824     constCharSinkFormatSpec,
1825     customPutWriter,
1826     customPutWriterFormatSpec,
1827 }
1828 
1829 private alias DScannerBug895 = int[256];
1830 private immutable bool hasPreviewIn = ((in DScannerBug895 a) { return __traits(isRef, a); })(DScannerBug895.init);
1831 
1832 template hasToString(T, Char)
1833 {
1834     static if (isPointer!T)
1835     {
1836         // X* does not have toString, even if X is aggregate type has toString.
1837         enum hasToString = HasToStringResult.none;
1838     }
1839     else static if (is(typeof(
1840         (T val) {
1841             const FormatSpec!Char f;
1842             static struct S {void put(scope Char s){}}
1843             S s;
1844             val.toString(s, f);
1845             static assert(!__traits(compiles, val.toString(s, FormatSpec!Char())),
1846                           "force toString to take parameters by ref");
1847             static assert(!__traits(compiles, val.toString(S(), f)),
1848                           "force toString to take parameters by ref");
1849         })))
1850     {
1851         enum hasToString = HasToStringResult.customPutWriterFormatSpec;
1852     }
1853     else static if (is(typeof(
1854         (T val) {
1855             static struct S {void put(scope Char s){}}
1856             S s;
1857             val.toString(s);
1858             static assert(!__traits(compiles, val.toString(S())),
1859                           "force toString to take parameters by ref");
1860         })))
1861     {
1862         enum hasToString = HasToStringResult.customPutWriter;
1863     }
1864     else static if (is(typeof((T val) { FormatSpec!Char f; val.toString((scope const(char)[] s){}, f); })))
1865     {
1866         enum hasToString = HasToStringResult.constCharSinkFormatSpec;
1867     }
1868     else static if (is(typeof((T val) { val.toString((scope const(char)[] s){}, "%s"); })))
1869     {
1870         enum hasToString = HasToStringResult.constCharSinkFormatString;
1871     }
1872     else static if (is(typeof((T val) { val.toString((scope const(char)[] s){}); })))
1873     {
1874         enum hasToString = HasToStringResult.constCharSink;
1875     }
1876 
1877     else static if (hasPreviewIn &&
1878                     is(typeof((T val) { FormatSpec!Char f; val.toString((in char[] s){}, f); })))
1879     {
1880         enum hasToString = HasToStringResult.inCharSinkFormatSpec;
1881     }
1882     else static if (hasPreviewIn &&
1883                     is(typeof((T val) { val.toString((in char[] s){}, "%s"); })))
1884     {
1885         enum hasToString = HasToStringResult.inCharSinkFormatString;
1886     }
1887     else static if (hasPreviewIn &&
1888                     is(typeof((T val) { val.toString((in char[] s){}); })))
1889     {
1890         enum hasToString = HasToStringResult.inCharSink;
1891     }
1892 
1893     else static if (is(ReturnType!((T val) { return val.toString(); }) S) && isSomeString!S)
1894     {
1895         enum hasToString = HasToStringResult.hasSomeToString;
1896     }
1897     else
1898     {
1899         enum hasToString = HasToStringResult.none;
1900     }
1901 }
1902 
1903 @safe unittest
1904 {
1905     import std.range.primitives : isOutputRange;
1906 
1907     static struct A
1908     {
1909         void toString(Writer)(ref Writer w)
1910         if (isOutputRange!(Writer, string))
1911         {}
1912     }
1913     static struct B
1914     {
1915         void toString(scope void delegate(scope const(char)[]) sink, scope FormatSpec!char fmt) {}
1916     }
1917     static struct C
1918     {
1919         void toString(scope void delegate(scope const(char)[]) sink, string fmt) {}
1920     }
1921     static struct D
1922     {
1923         void toString(scope void delegate(scope const(char)[]) sink) {}
1924     }
1925     static struct E
1926     {
1927         string toString() {return "";}
1928     }
1929     static struct F
1930     {
1931         void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt)
1932         if (isOutputRange!(Writer, string))
1933         {}
1934     }
1935     static struct G
1936     {
1937         string toString() {return "";}
1938         void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, string)) {}
1939     }
1940     static struct H
1941     {
1942         string toString() {return "";}
1943         void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt)
1944         if (isOutputRange!(Writer, string))
1945         {}
1946     }
1947     static struct I
1948     {
1949         void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, string)) {}
1950         void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt)
1951         if (isOutputRange!(Writer, string))
1952         {}
1953     }
1954     static struct J
1955     {
1956         string toString() {return "";}
1957         void toString(Writer)(ref Writer w, scope ref FormatSpec!char fmt)
1958         if (isOutputRange!(Writer, string))
1959         {}
1960     }
1961     static struct K
1962     {
1963         void toString(Writer)(Writer w, scope const ref FormatSpec!char fmt)
1964         if (isOutputRange!(Writer, string))
1965         {}
1966     }
1967     static struct L
1968     {
1969         void toString(Writer)(ref Writer w, scope const FormatSpec!char fmt)
1970         if (isOutputRange!(Writer, string))
1971         {}
1972     }
1973     static struct M
1974     {
1975         void toString(scope void delegate(in char[]) sink, in FormatSpec!char fmt) {}
1976     }
1977     static struct N
1978     {
1979         void toString(scope void delegate(in char[]) sink, string fmt) {}
1980     }
1981     static struct O
1982     {
1983         void toString(scope void delegate(in char[]) sink) {}
1984     }
1985 
1986     with(HasToStringResult)
1987     {
1988         static assert(hasToString!(A, char) == customPutWriter);
1989         static assert(hasToString!(B, char) == constCharSinkFormatSpec);
1990         static assert(hasToString!(C, char) == constCharSinkFormatString);
1991         static assert(hasToString!(D, char) == constCharSink);
1992         static assert(hasToString!(E, char) == hasSomeToString);
1993         static assert(hasToString!(F, char) == customPutWriterFormatSpec);
1994         static assert(hasToString!(G, char) == customPutWriter);
1995         static assert(hasToString!(H, char) == customPutWriterFormatSpec);
1996         static assert(hasToString!(I, char) == customPutWriterFormatSpec);
1997         static assert(hasToString!(J, char) == hasSomeToString);
1998         static assert(hasToString!(K, char) == constCharSinkFormatSpec);
1999         static assert(hasToString!(L, char) == none);
2000         static if (hasPreviewIn)
2001         {
2002             static assert(hasToString!(M, char) == inCharSinkFormatSpec);
2003             static assert(hasToString!(N, char) == inCharSinkFormatString);
2004             static assert(hasToString!(O, char) == inCharSink);
2005         }
2006     }
2007 }
2008 
2009 // const toString methods
2010 @safe unittest
2011 {
2012     import std.range.primitives : isOutputRange;
2013 
2014     static struct A
2015     {
2016         void toString(Writer)(ref Writer w) const
2017         if (isOutputRange!(Writer, string))
2018         {}
2019     }
2020     static struct B
2021     {
2022         void toString(scope void delegate(scope const(char)[]) sink, scope FormatSpec!char fmt) const {}
2023     }
2024     static struct C
2025     {
2026         void toString(scope void delegate(scope const(char)[]) sink, string fmt) const {}
2027     }
2028     static struct D
2029     {
2030         void toString(scope void delegate(scope const(char)[]) sink) const {}
2031     }
2032     static struct E
2033     {
2034         string toString() const {return "";}
2035     }
2036     static struct F
2037     {
2038         void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt) const
2039         if (isOutputRange!(Writer, string))
2040         {}
2041     }
2042     static struct G
2043     {
2044         string toString() const {return "";}
2045         void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, string)) {}
2046     }
2047     static struct H
2048     {
2049         string toString() const {return "";}
2050         void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt) const
2051         if (isOutputRange!(Writer, string))
2052         {}
2053     }
2054     static struct I
2055     {
2056         void toString(Writer)(ref Writer w) const if (isOutputRange!(Writer, string)) {}
2057         void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt) const
2058         if (isOutputRange!(Writer, string))
2059         {}
2060     }
2061     static struct J
2062     {
2063         string toString() const {return "";}
2064         void toString(Writer)(ref Writer w, scope ref FormatSpec!char fmt) const
2065         if (isOutputRange!(Writer, string))
2066         {}
2067     }
2068     static struct K
2069     {
2070         void toString(Writer)(Writer w, scope const ref FormatSpec!char fmt) const
2071         if (isOutputRange!(Writer, string))
2072         {}
2073     }
2074     static struct L
2075     {
2076         void toString(Writer)(ref Writer w, scope const FormatSpec!char fmt) const
2077         if (isOutputRange!(Writer, string))
2078         {}
2079     }
2080     static struct M
2081     {
2082         void toString(scope void delegate(in char[]) sink, in FormatSpec!char fmt) const {}
2083     }
2084     static struct N
2085     {
2086         void toString(scope void delegate(in char[]) sink, string fmt) const {}
2087     }
2088     static struct O
2089     {
2090         void toString(scope void delegate(in char[]) sink) const {}
2091     }
2092 
2093     with(HasToStringResult)
2094     {
2095         static assert(hasToString!(A, char) == customPutWriter);
2096         static assert(hasToString!(B, char) == constCharSinkFormatSpec);
2097         static assert(hasToString!(C, char) == constCharSinkFormatString);
2098         static assert(hasToString!(D, char) == constCharSink);
2099         static assert(hasToString!(E, char) == hasSomeToString);
2100         static assert(hasToString!(F, char) == customPutWriterFormatSpec);
2101         static assert(hasToString!(G, char) == customPutWriter);
2102         static assert(hasToString!(H, char) == customPutWriterFormatSpec);
2103         static assert(hasToString!(I, char) == customPutWriterFormatSpec);
2104         static assert(hasToString!(J, char) == hasSomeToString);
2105         static assert(hasToString!(K, char) == constCharSinkFormatSpec);
2106         static assert(hasToString!(L, char) == none);
2107         static if (hasPreviewIn)
2108         {
2109             static assert(hasToString!(M, char) == inCharSinkFormatSpec);
2110             static assert(hasToString!(N, char) == inCharSinkFormatString);
2111             static assert(hasToString!(O, char) == inCharSink);
2112         }
2113 
2114         // https://issues.dlang.org/show_bug.cgi?id=22873
2115         static assert(hasToString!(inout(A), char) == customPutWriter);
2116         static assert(hasToString!(inout(B), char) == constCharSinkFormatSpec);
2117         static assert(hasToString!(inout(C), char) == constCharSinkFormatString);
2118         static assert(hasToString!(inout(D), char) == constCharSink);
2119         static assert(hasToString!(inout(E), char) == hasSomeToString);
2120         static assert(hasToString!(inout(F), char) == customPutWriterFormatSpec);
2121         static assert(hasToString!(inout(G), char) == customPutWriter);
2122         static assert(hasToString!(inout(H), char) == customPutWriterFormatSpec);
2123         static assert(hasToString!(inout(I), char) == customPutWriterFormatSpec);
2124         static assert(hasToString!(inout(J), char) == hasSomeToString);
2125         static assert(hasToString!(inout(K), char) == constCharSinkFormatSpec);
2126         static assert(hasToString!(inout(L), char) == none);
2127         static if (hasPreviewIn)
2128         {
2129             static assert(hasToString!(inout(M), char) == inCharSinkFormatSpec);
2130             static assert(hasToString!(inout(N), char) == inCharSinkFormatString);
2131             static assert(hasToString!(inout(O), char) == inCharSink);
2132         }
2133     }
2134 }
2135 
2136 // object formatting with toString
2137 private void formatObject(Writer, T, Char)(ref Writer w, ref T val, scope const ref FormatSpec!Char f)
2138 if (hasToString!(T, Char))
2139 {
2140     import std.format : NoOpSink;
2141     import std.range.primitives : put;
2142 
2143     enum overload = hasToString!(T, Char);
2144 
2145     enum noop = is(Writer == NoOpSink);
2146 
2147     static if (overload == HasToStringResult.customPutWriterFormatSpec)
2148     {
2149         static if (!noop) val.toString(w, f);
2150     }
2151     else static if (overload == HasToStringResult.customPutWriter)
2152     {
2153         static if (!noop) val.toString(w);
2154     }
2155     else static if (overload == HasToStringResult.constCharSinkFormatSpec)
2156     {
2157         static if (!noop) val.toString((scope const(char)[] s) { put(w, s); }, f);
2158     }
2159     else static if (overload == HasToStringResult.constCharSinkFormatString)
2160     {
2161         static if (!noop) val.toString((scope const(char)[] s) { put(w, s); }, f.getCurFmtStr());
2162     }
2163     else static if (overload == HasToStringResult.constCharSink)
2164     {
2165         static if (!noop) val.toString((scope const(char)[] s) { put(w, s); });
2166     }
2167     else static if (overload == HasToStringResult.inCharSinkFormatSpec)
2168     {
2169         static if (!noop) val.toString((in char[] s) { put(w, s); }, f);
2170     }
2171     else static if (overload == HasToStringResult.inCharSinkFormatString)
2172     {
2173         static if (!noop) val.toString((in char[] s) { put(w, s); }, f.getCurFmtStr());
2174     }
2175     else static if (overload == HasToStringResult.inCharSink)
2176     {
2177         static if (!noop) val.toString((in char[] s) { put(w, s); });
2178     }
2179     else static if (overload == HasToStringResult.hasSomeToString)
2180     {
2181         static if (!noop) put(w, val.toString());
2182     }
2183     else
2184     {
2185         static assert(0, "No way found to format " ~ T.stringof ~ " as string");
2186     }
2187 }
2188 
2189 @system unittest
2190 {
2191     import std.exception : assertThrown;
2192     import std.format : FormatException;
2193 
2194     static interface IF1 { }
2195     class CIF1 : IF1 { }
2196     static struct SF1 { }
2197     static union UF1 { }
2198     static class CF1 { }
2199 
2200     static interface IF2 { string toString(); }
2201     static class CIF2 : IF2 { override string toString() { return ""; } }
2202     static struct SF2 { string toString() { return ""; } }
2203     static union UF2 { string toString() { return ""; } }
2204     static class CF2 { override string toString() { return ""; } }
2205 
2206     static interface IK1 { void toString(scope void delegate(scope const(char)[]) sink,
2207                            FormatSpec!char) const; }
2208     static class CIK1 : IK1 { override void toString(scope void delegate(scope const(char)[]) sink,
2209                               FormatSpec!char) const { sink("CIK1"); } }
2210     static struct KS1 { void toString(scope void delegate(scope const(char)[]) sink,
2211                         FormatSpec!char) const { sink("KS1"); } }
2212 
2213     static union KU1 { void toString(scope void delegate(scope const(char)[]) sink,
2214                        FormatSpec!char) const { sink("KU1"); } }
2215 
2216     static class KC1 { void toString(scope void delegate(scope const(char)[]) sink,
2217                        FormatSpec!char) const { sink("KC1"); } }
2218 
2219     IF1 cif1 = new CIF1;
2220     assertThrown!FormatException(format("%f", cif1));
2221     assertThrown!FormatException(format("%f", SF1()));
2222     assertThrown!FormatException(format("%f", UF1()));
2223     assertThrown!FormatException(format("%f", new CF1()));
2224 
2225     IF2 cif2 = new CIF2;
2226     assertThrown!FormatException(format("%f", cif2));
2227     assertThrown!FormatException(format("%f", SF2()));
2228     assertThrown!FormatException(format("%f", UF2()));
2229     assertThrown!FormatException(format("%f", new CF2()));
2230 
2231     IK1 cik1 = new CIK1;
2232     assert(format("%f", cik1) == "CIK1");
2233     assert(format("%f", KS1()) == "KS1");
2234     assert(format("%f", KU1()) == "KU1");
2235     assert(format("%f", new KC1()) == "KC1");
2236 }
2237 
2238 /*
2239     Aggregates
2240  */
2241 void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f)
2242 if (is(T == class) && !is(T == enum))
2243 {
2244     import std.range.primitives : put;
2245 
2246     enforceValidFormatSpec!(T, Char)(f);
2247 
2248     // TODO: remove this check once `@disable override` deprecation cycle is finished
2249     static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString))
2250         static assert(!__traits(isDisabled, T.toString), T.stringof ~
2251             " cannot be formatted because its `toString` is marked with `@disable`");
2252 
2253     if (val is null)
2254         put(w, "null");
2255     else
2256     {
2257         import std.algorithm.comparison : among;
2258         enum overload = hasToString!(T, Char);
2259         with(HasToStringResult)
2260         static if ((is(T == immutable) || is(T == const) || is(T == shared)) && overload == none)
2261         {
2262             // Remove this when Object gets const toString
2263             // https://issues.dlang.org/show_bug.cgi?id=7879
2264             static if (is(T == immutable))
2265                 put(w, "immutable(");
2266             else static if (is(T == const))
2267                 put(w, "const(");
2268             else static if (is(T == shared))
2269                 put(w, "shared(");
2270 
2271             put(w, typeid(Unqual!T).name);
2272             put(w, ')');
2273         }
2274         else static if (overload.among(constCharSink, constCharSinkFormatString, constCharSinkFormatSpec) ||
2275                        (!isInputRange!T && !is(BuiltinTypeOf!T)))
2276         {
2277             formatObject!(Writer, T, Char)(w, val, f);
2278         }
2279         else
2280         {
2281             static if (!is(__traits(parent, T.toString) == Object)) // not inherited Object.toString
2282             {
2283                 formatObject(w, val, f);
2284             }
2285             else static if (isInputRange!T)
2286             {
2287                 formatRange(w, val, f);
2288             }
2289             else static if (is(BuiltinTypeOf!T X))
2290             {
2291                 X x = val;
2292                 formatValueImpl(w, x, f);
2293             }
2294             else
2295             {
2296                 formatObject(w, val, f);
2297             }
2298         }
2299     }
2300 }
2301 
2302 @system unittest
2303 {
2304     import std.array : appender;
2305     import std.range.interfaces : inputRangeObject;
2306 
2307     // class range (https://issues.dlang.org/show_bug.cgi?id=5154)
2308     auto c = inputRangeObject([1,2,3,4]);
2309     formatTest(c, "[1, 2, 3, 4]");
2310     assert(c.empty);
2311     c = null;
2312     formatTest(c, "null");
2313 }
2314 
2315 @system unittest
2316 {
2317     // https://issues.dlang.org/show_bug.cgi?id=5354
2318     // If the class has both range I/F and custom toString, the use of custom
2319     // toString routine is prioritized.
2320 
2321     // Enable the use of custom toString that gets a sink delegate
2322     // for class formatting.
2323 
2324     enum inputRangeCode =
2325     q{
2326         int[] arr;
2327         this(int[] a){ arr = a; }
2328         @property int front() const { return arr[0]; }
2329         @property bool empty() const { return arr.length == 0; }
2330         void popFront(){ arr = arr[1 .. $]; }
2331     };
2332 
2333     class C1
2334     {
2335         mixin(inputRangeCode);
2336         void toString(scope void delegate(scope const(char)[]) dg,
2337                       scope const ref FormatSpec!char f) const
2338         {
2339             dg("[012]");
2340         }
2341     }
2342     class C2
2343     {
2344         mixin(inputRangeCode);
2345         void toString(scope void delegate(const(char)[]) dg, string f) const { dg("[012]"); }
2346     }
2347     class C3
2348     {
2349         mixin(inputRangeCode);
2350         void toString(scope void delegate(const(char)[]) dg) const { dg("[012]"); }
2351     }
2352     class C4
2353     {
2354         mixin(inputRangeCode);
2355         override string toString() const { return "[012]"; }
2356     }
2357     class C5
2358     {
2359         mixin(inputRangeCode);
2360     }
2361 
2362     formatTest(new C1([0, 1, 2]), "[012]");
2363     formatTest(new C2([0, 1, 2]), "[012]");
2364     formatTest(new C3([0, 1, 2]), "[012]");
2365     formatTest(new C4([0, 1, 2]), "[012]");
2366     formatTest(new C5([0, 1, 2]), "[0, 1, 2]");
2367 }
2368 
2369 // outside the unittest block, otherwise the FQN of the
2370 // class contains the line number of the unittest
2371 version (StdUnittest)
2372 {
2373     private class C {}
2374 }
2375 
2376 // https://issues.dlang.org/show_bug.cgi?id=7879
2377 @safe unittest
2378 {
2379     const(C) c;
2380     auto s = format("%s", c);
2381     assert(s == "null");
2382 
2383     immutable(C) c2 = new C();
2384     s = format("%s", c2);
2385     assert(s == "immutable(std.format.internal.write.C)");
2386 
2387     const(C) c3 = new C();
2388     s = format("%s", c3);
2389     assert(s == "const(std.format.internal.write.C)");
2390 
2391     shared(C) c4 = new C();
2392     s = format("%s", c4);
2393     assert(s == "shared(std.format.internal.write.C)");
2394 }
2395 
2396 // https://issues.dlang.org/show_bug.cgi?id=7879
2397 @safe unittest
2398 {
2399     class F
2400     {
2401         override string toString() const @safe
2402         {
2403             return "Foo";
2404         }
2405     }
2406 
2407     const(F) c;
2408     auto s = format("%s", c);
2409     assert(s == "null");
2410 
2411     const(F) c2 = new F();
2412     s = format("%s", c2);
2413     assert(s == "Foo", s);
2414 }
2415 
2416 void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f)
2417 if (is(T == interface) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is(T == enum))
2418 {
2419     import std.range.primitives : put;
2420 
2421     enforceValidFormatSpec!(T, Char)(f);
2422     if (val is null)
2423         put(w, "null");
2424     else
2425     {
2426         static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString))
2427             static assert(!__traits(isDisabled, T.toString), T.stringof ~
2428                 " cannot be formatted because its `toString` is marked with `@disable`");
2429 
2430         static if (hasToString!(T, Char) != HasToStringResult.none)
2431         {
2432             formatObject(w, val, f);
2433         }
2434         else static if (isInputRange!T)
2435         {
2436             formatRange(w, val, f);
2437         }
2438         else
2439         {
2440             version (Windows)
2441             {
2442                 import core.sys.windows.com : IUnknown;
2443                 static if (is(T : IUnknown))
2444                 {
2445                     formatValueImpl(w, *cast(void**)&val, f);
2446                 }
2447                 else
2448                 {
2449                     formatValueImpl(w, cast(Object) val, f);
2450                 }
2451             }
2452             else
2453             {
2454                 formatValueImpl(w, cast(Object) val, f);
2455             }
2456         }
2457     }
2458 }
2459 
2460 @system unittest
2461 {
2462     import std.range.interfaces : InputRange, inputRangeObject;
2463 
2464     // interface
2465     InputRange!int i = inputRangeObject([1,2,3,4]);
2466     formatTest(i, "[1, 2, 3, 4]");
2467     assert(i.empty);
2468     i = null;
2469     formatTest(i, "null");
2470 
2471     // interface (downcast to Object)
2472     interface Whatever {}
2473     class C : Whatever
2474     {
2475         override @property string toString() const { return "ab"; }
2476     }
2477     Whatever val = new C;
2478     formatTest(val, "ab");
2479 
2480     // https://issues.dlang.org/show_bug.cgi?id=11175
2481     version (Windows)
2482     {
2483         import core.sys.windows.com : IID, IUnknown;
2484         import core.sys.windows.windef : HRESULT;
2485 
2486         interface IUnknown2 : IUnknown { }
2487 
2488         class D : IUnknown2
2489         {
2490             extern(Windows) HRESULT QueryInterface(const(IID)* riid, void** pvObject) { return typeof(return).init; }
2491             extern(Windows) uint AddRef() { return 0; }
2492             extern(Windows) uint Release() { return 0; }
2493         }
2494 
2495         IUnknown2 d = new D;
2496         string expected = format("%X", cast(void*) d);
2497         formatTest(d, expected);
2498     }
2499 }
2500 
2501 // Maybe T is noncopyable struct, so receive it by 'auto ref'.
2502 void formatValueImpl(Writer, T, Char)(auto ref Writer w, auto ref T val,
2503     scope const ref FormatSpec!Char f)
2504 if ((is(T == struct) || is(T == union)) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T))
2505     && !is(T == enum))
2506 {
2507     import std.range.primitives : put;
2508 
2509     static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString))
2510         static assert(!__traits(isDisabled, T.toString), T.stringof ~
2511             " cannot be formatted because its `toString` is marked with `@disable`");
2512 
2513     enforceValidFormatSpec!(T, Char)(f);
2514     static if (hasToString!(T, Char))
2515     {
2516         formatObject(w, val, f);
2517     }
2518     else static if (isInputRange!T)
2519     {
2520         formatRange(w, val, f);
2521     }
2522     else static if (is(T == struct))
2523     {
2524         enum left = T.stringof~"(";
2525         enum separator = ", ";
2526         enum right = ")";
2527 
2528         put(w, left);
2529         static foreach (i; 0 .. T.tupleof.length)
2530         {{
2531             static if (__traits(identifier, val.tupleof[i]) == "this")
2532             {
2533                 // ignore hidden context pointer
2534             }
2535             else static if (0 < i && T.tupleof[i-1].offsetof == T.tupleof[i].offsetof)
2536             {
2537                 static if (i == T.tupleof.length - 1 || T.tupleof[i].offsetof != T.tupleof[i+1].offsetof)
2538                 {
2539                     enum el = separator ~ __traits(identifier, T.tupleof[i]) ~ "}";
2540                     put(w, el);
2541                 }
2542                 else
2543                 {
2544                     enum el = separator ~ __traits(identifier, T.tupleof[i]);
2545                     put(w, el);
2546                 }
2547             }
2548             else static if (i+1 < T.tupleof.length && T.tupleof[i].offsetof == T.tupleof[i+1].offsetof)
2549             {
2550                 enum el = (i > 0 ? separator : "") ~ "#{overlap " ~ __traits(identifier, T.tupleof[i]);
2551                 put(w, el);
2552             }
2553             else
2554             {
2555                 static if (i > 0)
2556                     put(w, separator);
2557                 formatElement(w, val.tupleof[i], f);
2558             }
2559         }}
2560         put(w, right);
2561     }
2562     else
2563     {
2564         put(w, T.stringof);
2565     }
2566 }
2567 
2568 // https://issues.dlang.org/show_bug.cgi?id=9588
2569 @safe pure unittest
2570 {
2571     struct S { int x; bool empty() { return false; } }
2572     formatTest(S(), "S(0)");
2573 }
2574 
2575 // https://issues.dlang.org/show_bug.cgi?id=4638
2576 @safe unittest
2577 {
2578     struct U8  {  string toString() const { return "blah"; } }
2579     struct U16 { wstring toString() const { return "blah"; } }
2580     struct U32 { dstring toString() const { return "blah"; } }
2581     formatTest(U8(), "blah");
2582     formatTest(U16(), "blah");
2583     formatTest(U32(), "blah");
2584 }
2585 
2586 // https://issues.dlang.org/show_bug.cgi?id=3890
2587 @safe unittest
2588 {
2589     struct Int{ int n; }
2590     struct Pair{ string s; Int i; }
2591     formatTest(Pair("hello", Int(5)),
2592                `Pair("hello", Int(5))`);
2593 }
2594 
2595 // https://issues.dlang.org/show_bug.cgi?id=9117
2596 @safe unittest
2597 {
2598     import std.format : formattedWrite;
2599 
2600     static struct Frop {}
2601 
2602     static struct Foo
2603     {
2604         int n = 0;
2605         alias n this;
2606         T opCast(T) () if (is(T == Frop))
2607         {
2608             return Frop();
2609         }
2610         string toString()
2611         {
2612             return "Foo";
2613         }
2614     }
2615 
2616     static struct Bar
2617     {
2618         Foo foo;
2619         alias foo this;
2620         string toString()
2621         {
2622             return "Bar";
2623         }
2624     }
2625 
2626     const(char)[] result;
2627     void put(scope const char[] s) { result ~= s; }
2628 
2629     Foo foo;
2630     formattedWrite(&put, "%s", foo);    // OK
2631     assert(result == "Foo");
2632 
2633     result = null;
2634 
2635     Bar bar;
2636     formattedWrite(&put, "%s", bar);    // NG
2637     assert(result == "Bar");
2638 
2639     result = null;
2640 
2641     int i = 9;
2642     formattedWrite(&put, "%s", 9);
2643     assert(result == "9");
2644 }
2645 
2646 @safe unittest
2647 {
2648     // union formatting without toString
2649     union U1
2650     {
2651         int n;
2652         string s;
2653     }
2654     U1 u1;
2655     formatTest(u1, "U1");
2656 
2657     // union formatting with toString
2658     union U2
2659     {
2660         int n;
2661         string s;
2662         string toString() @trusted const { return s; }
2663     }
2664     U2 u2;
2665     () @trusted { u2.s = "hello"; } ();
2666     formatTest(u2, "hello");
2667 }
2668 
2669 @safe unittest
2670 {
2671     import std.array : appender;
2672     import std.format : formatValue;
2673 
2674     // https://issues.dlang.org/show_bug.cgi?id=7230
2675     static struct Bug7230
2676     {
2677         string s = "hello";
2678         union {
2679             string a;
2680             int b;
2681             double c;
2682         }
2683         long x = 10;
2684     }
2685 
2686     Bug7230 bug;
2687     bug.b = 123;
2688 
2689     FormatSpec!char f;
2690     auto w = appender!(char[])();
2691     formatValue(w, bug, f);
2692     assert(w.data == `Bug7230("hello", #{overlap a, b, c}, 10)`);
2693 }
2694 
2695 @safe unittest
2696 {
2697     import std.array : appender;
2698     import std.format : formatValue;
2699 
2700     static struct S{ @disable this(this); }
2701     S s;
2702 
2703     FormatSpec!char f;
2704     auto w = appender!string();
2705     formatValue(w, s, f);
2706     assert(w.data == "S()");
2707 }
2708 
2709 @safe unittest
2710 {
2711     import std.array : appender;
2712     import std.format : formatValue;
2713 
2714     //struct Foo { @disable string toString(); }
2715     //Foo foo;
2716 
2717     interface Bar { @disable string toString(); }
2718     Bar bar;
2719 
2720     auto w = appender!(char[])();
2721     FormatSpec!char f;
2722 
2723     // NOTE: structs cant be tested : the assertion is correct so compilation
2724     // continues and fails when trying to link the unimplemented toString.
2725     //static assert(!__traits(compiles, formatValue(w, foo, f)));
2726     static assert(!__traits(compiles, formatValue(w, bar, f)));
2727 }
2728 
2729 // https://issues.dlang.org/show_bug.cgi?id=21722
2730 @safe unittest
2731 {
2732     struct Bar
2733     {
2734         void toString (scope void delegate (scope const(char)[]) sink, string fmt)
2735         {
2736             sink("Hello");
2737         }
2738     }
2739 
2740     Bar b;
2741     auto result = () @trusted { return format("%b", b); } ();
2742     assert(result == "Hello");
2743 
2744     static if (hasPreviewIn)
2745     {
2746         struct Foo
2747         {
2748             void toString(scope void delegate(in char[]) sink, in FormatSpec!char fmt)
2749             {
2750                 sink("Hello");
2751             }
2752         }
2753 
2754         Foo f;
2755         assert(format("%b", f) == "Hello");
2756 
2757         struct Foo2
2758         {
2759             void toString(scope void delegate(in char[]) sink, string fmt)
2760             {
2761                 sink("Hello");
2762             }
2763         }
2764 
2765         Foo2 f2;
2766         assert(format("%b", f2) == "Hello");
2767     }
2768 }
2769 
2770 @safe unittest
2771 {
2772     import std.array : appender;
2773     import std.format : singleSpec;
2774 
2775     // Bug #17269. Behavior similar to `struct A { Nullable!string B; }`
2776     struct StringAliasThis
2777     {
2778         @property string value() const { assert(0); }
2779         alias value this;
2780         string toString() { return "helloworld"; }
2781         private string _value;
2782     }
2783     struct TestContainer
2784     {
2785         StringAliasThis testVar;
2786     }
2787 
2788     auto w = appender!string();
2789     auto spec = singleSpec("%s");
2790     formatElement(w, TestContainer(), spec);
2791 
2792     assert(w.data == "TestContainer(helloworld)", w.data);
2793 }
2794 
2795 // https://issues.dlang.org/show_bug.cgi?id=17269
2796 @safe unittest
2797 {
2798     import std.typecons : Nullable;
2799 
2800     struct Foo
2801     {
2802         Nullable!string bar;
2803     }
2804 
2805     Foo f;
2806     formatTest(f, "Foo(Nullable.null)");
2807 }
2808 
2809 // https://issues.dlang.org/show_bug.cgi?id=19003
2810 @safe unittest
2811 {
2812     struct S
2813     {
2814         int i;
2815 
2816         @disable this();
2817 
2818         invariant { assert(this.i); }
2819 
2820         this(int i) @safe in { assert(i); } do { this.i = i; }
2821 
2822         string toString() { return "S"; }
2823     }
2824 
2825     S s = S(1);
2826 
2827     format!"%s"(s);
2828 }
2829 
2830 void enforceValidFormatSpec(T, Char)(scope const ref FormatSpec!Char f)
2831 {
2832     import std.format : enforceFmt;
2833     import std.range : isInputRange;
2834     import std.format.internal.write : hasToString, HasToStringResult;
2835 
2836     enum overload = hasToString!(T, Char);
2837     static if (
2838             overload != HasToStringResult.constCharSinkFormatSpec &&
2839             overload != HasToStringResult.constCharSinkFormatString &&
2840             overload != HasToStringResult.inCharSinkFormatSpec &&
2841             overload != HasToStringResult.inCharSinkFormatString &&
2842             overload != HasToStringResult.customPutWriterFormatSpec &&
2843             !isInputRange!T)
2844     {
2845         enforceFmt(f.spec == 's',
2846             "Expected '%s' format specifier for type '" ~ T.stringof ~ "'");
2847     }
2848 }
2849 
2850 /*
2851     `enum`s are formatted like their base value
2852  */
2853 void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f)
2854 if (is(T == enum))
2855 {
2856     import std.array : appender;
2857     import std.range.primitives : put;
2858 
2859     if (f.spec != 's')
2860         return formatValueImpl(w, cast(OriginalType!T) val, f);
2861 
2862     foreach (immutable member; __traits(allMembers, T))
2863         if (val == __traits(getMember, T, member))
2864             return formatValueImpl(w, member, f);
2865 
2866     auto w2 = appender!string();
2867 
2868     // val is not a member of T, output cast(T) rawValue instead.
2869     enum prefix = "cast(" ~ T.stringof ~ ")";
2870     put(w2, prefix);
2871     static assert(!is(OriginalType!T == T), "OriginalType!" ~ T.stringof ~
2872                   "must not be equal to " ~ T.stringof);
2873 
2874     FormatSpec!Char f2 = f;
2875     f2.width = 0;
2876     formatValueImpl(w2, cast(OriginalType!T) val, f2);
2877     writeAligned(w, w2.data, f);
2878 }
2879 
2880 @safe unittest
2881 {
2882     enum A { first, second, third }
2883     formatTest(A.second, "second");
2884     formatTest(cast(A) 72, "cast(A)72");
2885 }
2886 @safe unittest
2887 {
2888     enum A : string { one = "uno", two = "dos", three = "tres" }
2889     formatTest(A.three, "three");
2890     formatTest(cast(A)"mill\&oacute;n", "cast(A)mill\&oacute;n");
2891 }
2892 @safe unittest
2893 {
2894     enum A : bool { no, yes }
2895     formatTest(A.yes, "yes");
2896     formatTest(A.no, "no");
2897 }
2898 @safe unittest
2899 {
2900     // Test for bug 6892
2901     enum Foo { A = 10 }
2902     formatTest("%s",    Foo.A, "A");
2903     formatTest(">%4s<", Foo.A, ">   A<");
2904     formatTest("%04d",  Foo.A, "0010");
2905     formatTest("%+2u",  Foo.A, "10");
2906     formatTest("%02x",  Foo.A, "0a");
2907     formatTest("%3o",   Foo.A, " 12");
2908     formatTest("%b",    Foo.A, "1010");
2909 }
2910 
2911 @safe pure unittest
2912 {
2913     enum A { one, two, three }
2914 
2915     string t1 = format("[%6s] [%-6s]", A.one, A.one);
2916     assert(t1 == "[   one] [one   ]");
2917     string t2 = format("[%10s] [%-10s]", cast(A) 10, cast(A) 10);
2918     assert(t2 == "[ cast(A)" ~ "10] [cast(A)" ~ "10 ]"); // due to bug in style checker
2919 }
2920 
2921 // https://issues.dlang.org/show_bug.cgi?id=8921
2922 @safe unittest
2923 {
2924     enum E : char { A = 'a', B = 'b', C = 'c' }
2925     E[3] e = [E.A, E.B, E.C];
2926     formatTest(e, "[A, B, C]");
2927 
2928     E[] e2 = [E.A, E.B, E.C];
2929     formatTest(e2, "[A, B, C]");
2930 }
2931 
2932 /*
2933     Pointers are formatted as hex integers.
2934  */
2935 void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope const(T) val, scope const ref FormatSpec!Char f)
2936 if (isPointer!T && !is(T == enum) && !hasToString!(T, Char))
2937 {
2938     static if (is(typeof({ shared const void* p = val; })))
2939         alias SharedOf(T) = shared(T);
2940     else
2941         alias SharedOf(T) = T;
2942 
2943     const SharedOf!(void*) p = val;
2944     const pnum = () @trusted { return cast(ulong) p; }();
2945 
2946     if (f.spec == 's')
2947     {
2948         if (p is null)
2949         {
2950             writeAligned(w, "null", f);
2951             return;
2952         }
2953         FormatSpec!Char fs = f; // fs is copy for change its values.
2954         fs.spec = 'X';
2955         formatValueImpl(w, pnum, fs);
2956     }
2957     else
2958     {
2959         import std.format : enforceFmt;
2960         enforceFmt(f.spec == 'X' || f.spec == 'x',
2961             "Expected one of %s, %x or %X for pointer type.");
2962         formatValueImpl(w, pnum, f);
2963     }
2964 }
2965 
2966 @safe pure unittest
2967 {
2968     int* p;
2969 
2970     string t1 = format("[%6s] [%-6s]", p, p);
2971     assert(t1 == "[  null] [null  ]");
2972 }
2973 
2974 @safe pure unittest
2975 {
2976     int* p = null;
2977     formatTest(p, "null");
2978 
2979     auto q = () @trusted { return cast(void*) 0xFFEECCAA; }();
2980     formatTest(q, "FFEECCAA");
2981 }
2982 
2983 // https://issues.dlang.org/show_bug.cgi?id=11782
2984 @safe pure unittest
2985 {
2986     import std.range : iota;
2987 
2988     auto a = iota(0, 10);
2989     auto b = iota(0, 10);
2990     auto p = () @trusted { auto result = &a; return result; }();
2991 
2992     assert(format("%s",p) != format("%s",b));
2993 }
2994 
2995 @safe pure unittest
2996 {
2997     // Test for https://issues.dlang.org/show_bug.cgi?id=7869
2998     struct S
2999     {
3000         string toString() const { return ""; }
3001     }
3002     S* p = null;
3003     formatTest(p, "null");
3004 
3005     S* q = () @trusted { return cast(S*) 0xFFEECCAA; } ();
3006     formatTest(q, "FFEECCAA");
3007 }
3008 
3009 // https://issues.dlang.org/show_bug.cgi?id=9336
3010 @system pure unittest
3011 {
3012     shared int i;
3013     format("%s", &i);
3014 }
3015 
3016 // https://issues.dlang.org/show_bug.cgi?id=11778
3017 @safe pure unittest
3018 {
3019     import std.exception : assertThrown;
3020     import std.format : FormatException;
3021 
3022     int* p = null;
3023     assertThrown!FormatException(format("%d", p));
3024     assertThrown!FormatException(format("%04d", () @trusted { return p + 2; } ()));
3025 }
3026 
3027 // https://issues.dlang.org/show_bug.cgi?id=12505
3028 @safe pure unittest
3029 {
3030     void* p = null;
3031     formatTest("%08X", p, "00000000");
3032 }
3033 
3034 /*
3035     SIMD vectors are formatted as arrays.
3036  */
3037 void formatValueImpl(Writer, V, Char)(auto ref Writer w, const(V) val, scope const ref FormatSpec!Char f)
3038 if (isSIMDVector!V)
3039 {
3040     formatValueImpl(w, val.array, f);
3041 }
3042 
3043 @safe unittest
3044 {
3045     import core.simd; // cannot be selective, because float4 might not be defined
3046 
3047     static if (is(float4))
3048     {
3049         version (X86)
3050         {
3051             version (OSX) {/* https://issues.dlang.org/show_bug.cgi?id=17823 */}
3052         }
3053         else
3054         {
3055             float4 f;
3056             f.array[0] = 1;
3057             f.array[1] = 2;
3058             f.array[2] = 3;
3059             f.array[3] = 4;
3060             formatTest(f, "[1, 2, 3, 4]");
3061         }
3062     }
3063 }
3064 
3065 /*
3066     Delegates are formatted by `ReturnType delegate(Parameters) FunctionAttributes`
3067 
3068     Known bug: Because of issue https://issues.dlang.org/show_bug.cgi?id=18269
3069                the FunctionAttributes might be wrong.
3070  */
3071 void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope const(T), scope const ref FormatSpec!Char f)
3072 if (isDelegate!T)
3073 {
3074     formatValueImpl(w, T.stringof, f);
3075 }
3076 
3077 @safe unittest
3078 {
3079     import std.array : appender;
3080     import std.format : formatValue;
3081 
3082     void func() @system { __gshared int x; ++x; throw new Exception("msg"); }
3083     version (linux)
3084     {
3085         FormatSpec!char f;
3086         auto w = appender!string();
3087         formatValue(w, &func, f);
3088         assert(w.data.length >= 15 && w.data[0 .. 15] == "void delegate()");
3089     }
3090 }
3091 
3092 // string elements are formatted like UTF-8 string literals.
3093 void formatElement(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f)
3094 if (is(StringTypeOf!T) && !hasToString!(T, Char) && !is(T == enum))
3095 {
3096     import std.array : appender;
3097     import std.format.write : formattedWrite, formatValue;
3098     import std.range.primitives : put;
3099     import std.utf : decode, UTFException;
3100 
3101     StringTypeOf!T str = val;   // https://issues.dlang.org/show_bug.cgi?id=8015
3102 
3103     if (f.spec == 's')
3104     {
3105         try
3106         {
3107             // ignore other specifications and quote
3108             for (size_t i = 0; i < str.length; )
3109             {
3110                 auto c = decode(str, i);
3111                 // \uFFFE and \uFFFF are considered valid by isValidDchar,
3112                 // so need checking for interchange.
3113                 if (c == 0xFFFE || c == 0xFFFF)
3114                     goto LinvalidSeq;
3115             }
3116             put(w, '\"');
3117             for (size_t i = 0; i < str.length; )
3118             {
3119                 auto c = decode(str, i);
3120                 formatChar(w, c, '"');
3121             }
3122             put(w, '\"');
3123             return;
3124         }
3125         catch (UTFException)
3126         {
3127         }
3128 
3129         // If val contains invalid UTF sequence, formatted like HexString literal
3130     LinvalidSeq:
3131         static if (is(typeof(str[0]) : const(char)))
3132         {
3133             enum type = "";
3134             alias IntArr = const(ubyte)[];
3135         }
3136         else static if (is(typeof(str[0]) : const(wchar)))
3137         {
3138             enum type = "w";
3139             alias IntArr = const(ushort)[];
3140         }
3141         else static if (is(typeof(str[0]) : const(dchar)))
3142         {
3143             enum type = "d";
3144             alias IntArr = const(uint)[];
3145         }
3146         formattedWrite(w, "[%(cast(" ~ type ~ "char) 0x%X%|, %)]", cast(IntArr) str);
3147     }
3148     else
3149         formatValue(w, str, f);
3150 }
3151 
3152 @safe pure unittest
3153 {
3154     import std.array : appender;
3155     import std.format.spec : singleSpec;
3156 
3157     auto w = appender!string();
3158     auto spec = singleSpec("%s");
3159     formatElement(w, "Hello World", spec);
3160 
3161     assert(w.data == "\"Hello World\"");
3162 }
3163 
3164 @safe unittest
3165 {
3166     import std.array : appender;
3167     import std.format.spec : singleSpec;
3168 
3169     auto w = appender!string();
3170     auto spec = singleSpec("%s");
3171     formatElement(w, "H", spec);
3172 
3173     assert(w.data == "\"H\"", w.data);
3174 }
3175 
3176 // https://issues.dlang.org/show_bug.cgi?id=15888
3177 @safe pure unittest
3178 {
3179     import std.array : appender;
3180     import std.format.spec : singleSpec;
3181 
3182     ushort[] a = [0xFF_FE, 0x42];
3183     auto w = appender!string();
3184     auto spec = singleSpec("%s");
3185     formatElement(w, cast(wchar[]) a, spec);
3186     assert(w.data == `[cast(wchar) 0xFFFE, cast(wchar) 0x42]`);
3187 
3188     uint[] b = [0x0F_FF_FF_FF, 0x42];
3189     w = appender!string();
3190     spec = singleSpec("%s");
3191     formatElement(w, cast(dchar[]) b, spec);
3192     assert(w.data == `[cast(dchar) 0xFFFFFFF, cast(dchar) 0x42]`);
3193 }
3194 
3195 // Character elements are formatted like UTF-8 character literals.
3196 void formatElement(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f)
3197 if (is(CharTypeOf!T) && !is(T == enum))
3198 {
3199     import std.range.primitives : put;
3200     import std.format.write : formatValue;
3201 
3202     if (f.spec == 's')
3203     {
3204         put(w, '\'');
3205         formatChar(w, val, '\'');
3206         put(w, '\'');
3207     }
3208     else
3209         formatValue(w, val, f);
3210 }
3211 
3212 // Maybe T is noncopyable struct, so receive it by 'auto ref'.
3213 void formatElement(Writer, T, Char)(auto ref Writer w, auto ref T val, scope const ref FormatSpec!Char f)
3214 if ((!is(StringTypeOf!T) || hasToString!(T, Char)) && !is(CharTypeOf!T) || is(T == enum))
3215 {
3216     import std.format.write : formatValue;
3217 
3218     formatValue(w, val, f);
3219 }
3220 
3221 // Fix for https://issues.dlang.org/show_bug.cgi?id=1591
3222 int getNthInt(string kind, A...)(uint index, A args)
3223 {
3224     return getNth!(kind, isIntegral, int)(index, args);
3225 }
3226 
3227 T getNth(string kind, alias Condition, T, A...)(uint index, A args)
3228 {
3229     import std.conv : text, to;
3230     import std.format : FormatException;
3231 
3232     switch (index)
3233     {
3234         foreach (n, _; A)
3235         {
3236             case n:
3237                 static if (Condition!(typeof(args[n])))
3238                 {
3239                     return to!T(args[n]);
3240                 }
3241                 else
3242                 {
3243                     throw new FormatException(
3244                         text(kind, " expected, not ", typeof(args[n]).stringof,
3245                             " for argument #", index + 1));
3246                 }
3247         }
3248         default:
3249             throw new FormatException(text("Missing ", kind, " argument"));
3250     }
3251 }
3252 
3253 private bool needToSwapEndianess(Char)(scope const ref FormatSpec!Char f)
3254 {
3255     import std.system : endian, Endian;
3256 
3257     return endian == Endian.littleEndian && f.flPlus
3258         || endian == Endian.bigEndian && f.flDash;
3259 }
3260 
3261 void writeAligned(Writer, T, Char)(auto ref Writer w, T s, scope const ref FormatSpec!Char f)
3262 if (isSomeString!T)
3263 {
3264     FormatSpec!Char fs = f;
3265     fs.flZero = false;
3266     writeAligned(w, "", "", s, fs);
3267 }
3268 
3269 @safe pure unittest
3270 {
3271     import std.array : appender;
3272     import std.format : singleSpec;
3273 
3274     auto w = appender!string();
3275     auto spec = singleSpec("%s");
3276     writeAligned(w, "a本Ä", spec);
3277     assert(w.data == "a本Ä", w.data);
3278 }
3279 
3280 @safe pure unittest
3281 {
3282     import std.array : appender;
3283     import std.format : singleSpec;
3284 
3285     auto w = appender!string();
3286     auto spec = singleSpec("%10s");
3287     writeAligned(w, "a本Ä", spec);
3288     assert(w.data == "       a本Ä", "|" ~ w.data ~ "|");
3289 }
3290 
3291 @safe pure unittest
3292 {
3293     import std.array : appender;
3294     import std.format : singleSpec;
3295 
3296     auto w = appender!string();
3297     auto spec = singleSpec("%-10s");
3298     writeAligned(w, "a本Ä", spec);
3299     assert(w.data == "a本Ä       ", w.data);
3300 }
3301 
3302 enum PrecisionType
3303 {
3304     none,
3305     integer,
3306     fractionalDigits,
3307     allDigits,
3308 }
3309 
3310 void writeAligned(Writer, T1, T2, T3, Char)(auto ref Writer w,
3311     T1 prefix, T2 grouped, T3 suffix, scope const ref FormatSpec!Char f,
3312     bool integer_precision = false)
3313 if (isSomeString!T1 && isSomeString!T2 && isSomeString!T3)
3314 {
3315     writeAligned(w, prefix, grouped, "", suffix, f,
3316                  integer_precision ? PrecisionType.integer : PrecisionType.none);
3317 }
3318 
3319 void writeAligned(Writer, T1, T2, T3, T4, Char)(auto ref Writer w,
3320     T1 prefix, T2 grouped, T3 fracts, T4 suffix, scope const ref FormatSpec!Char f,
3321     PrecisionType p = PrecisionType.none)
3322 if (isSomeString!T1 && isSomeString!T2 && isSomeString!T3 && isSomeString!T4)
3323 {
3324     // writes: left padding, prefix, leading zeros, grouped, fracts, suffix, right padding
3325 
3326     if (p == PrecisionType.integer && f.precision == f.UNSPECIFIED)
3327         p = PrecisionType.none;
3328 
3329     import std.range.primitives : put;
3330 
3331     long prefixWidth;
3332     long groupedWidth = grouped.length; // TODO: does not take graphemes into account
3333     long fractsWidth = fracts.length; // TODO: does not take graphemes into account
3334     long suffixWidth;
3335 
3336     // TODO: remove this workaround which hides https://issues.dlang.org/show_bug.cgi?id=21815
3337     if (f.width > 0)
3338     {
3339         prefixWidth = getWidth(prefix);
3340         suffixWidth = getWidth(suffix);
3341     }
3342 
3343     auto doGrouping = f.flSeparator && groupedWidth > 0
3344                       && f.separators > 0 && f.separators != f.UNSPECIFIED;
3345     // front = number of symbols left of the leftmost separator
3346     long front = doGrouping ? (groupedWidth - 1) % f.separators + 1 : 0;
3347     // sepCount = number of separators to be inserted
3348     long sepCount = doGrouping ? (groupedWidth - 1) / f.separators : 0;
3349 
3350     long trailingZeros = 0;
3351     if (p == PrecisionType.fractionalDigits)
3352         trailingZeros = f.precision - (fractsWidth - 1);
3353     if (p == PrecisionType.allDigits && f.flHash)
3354     {
3355         if (grouped != "0")
3356             trailingZeros = f.precision - (fractsWidth - 1) - groupedWidth;
3357         else
3358         {
3359             trailingZeros = f.precision - fractsWidth;
3360             foreach (i;0 .. fracts.length)
3361                 if (fracts[i] != '0' && fracts[i] != '.')
3362                 {
3363                     trailingZeros = f.precision - (fracts.length - i);
3364                     break;
3365                 }
3366         }
3367     }
3368 
3369     auto nodot = fracts == "." && trailingZeros == 0 && !f.flHash;
3370 
3371     if (nodot) fractsWidth = 0;
3372 
3373     long width = prefixWidth + sepCount + groupedWidth + fractsWidth + trailingZeros + suffixWidth;
3374     long delta = f.width - width;
3375 
3376     // with integers, precision is considered the minimum number of digits;
3377     // if digits are missing, we have to recalculate everything
3378     long pregrouped = 0;
3379     if (p == PrecisionType.integer && groupedWidth < f.precision)
3380     {
3381         pregrouped = f.precision - groupedWidth;
3382         delta -= pregrouped;
3383         if (doGrouping)
3384         {
3385             front = ((front - 1) + pregrouped) % f.separators + 1;
3386             delta -= (f.precision - 1) / f.separators - sepCount;
3387         }
3388     }
3389 
3390     // left padding
3391     if ((!f.flZero || p == PrecisionType.integer) && delta > 0)
3392     {
3393         if (f.flEqual)
3394         {
3395             foreach (i ; 0 .. delta / 2 + ((delta % 2 == 1 && !f.flDash) ? 1 : 0))
3396                 put(w, ' ');
3397         }
3398         else if (!f.flDash)
3399         {
3400             foreach (i ; 0 .. delta)
3401                 put(w, ' ');
3402         }
3403     }
3404 
3405     // prefix
3406     put(w, prefix);
3407 
3408     // leading grouped zeros
3409     if (f.flZero && p != PrecisionType.integer && !f.flDash && delta > 0)
3410     {
3411         if (doGrouping)
3412         {
3413             // front2 and sepCount2 are the same as above for the leading zeros
3414             long front2 = (delta + front - 1) % (f.separators + 1) + 1;
3415             long sepCount2 = (delta + front - 1) / (f.separators + 1);
3416             delta -= sepCount2;
3417 
3418             // according to POSIX: if the first symbol is a separator,
3419             // an additional zero is put left of it, even if that means, that
3420             // the total width is one more then specified
3421             if (front2 > f.separators) { front2 = 1; }
3422 
3423             foreach (i ; 0 .. delta)
3424             {
3425                 if (front2 == 0)
3426                 {
3427                     put(w, f.separatorChar);
3428                     front2 = f.separators;
3429                 }
3430                 front2--;
3431 
3432                 put(w, '0');
3433             }
3434 
3435             // separator between zeros and grouped
3436             if (front == f.separators)
3437                 put(w, f.separatorChar);
3438         }
3439         else
3440             foreach (i ; 0 .. delta)
3441                 put(w, '0');
3442     }
3443 
3444     // grouped content
3445     if (doGrouping)
3446     {
3447         // TODO: this does not take graphemes into account
3448         foreach (i;0 .. pregrouped + grouped.length)
3449         {
3450             if (front == 0)
3451             {
3452                 put(w, f.separatorChar);
3453                 front = f.separators;
3454             }
3455             front--;
3456 
3457             put(w, i < pregrouped ? '0' : grouped[cast(size_t) (i - pregrouped)]);
3458         }
3459     }
3460     else
3461     {
3462         foreach (i;0 .. pregrouped)
3463             put(w, '0');
3464         put(w, grouped);
3465     }
3466 
3467     // fracts
3468     if (!nodot)
3469         put(w, fracts);
3470 
3471     // trailing zeros
3472     foreach (i ; 0 .. trailingZeros)
3473         put(w, '0');
3474 
3475     // suffix
3476     put(w, suffix);
3477 
3478     // right padding
3479     if (delta > 0)
3480     {
3481         if (f.flEqual)
3482         {
3483             foreach (i ; 0 .. delta / 2 + ((delta % 2 == 1 && f.flDash) ? 1 : 0))
3484                 put(w, ' ');
3485         }
3486         else if (f.flDash)
3487         {
3488             foreach (i ; 0 .. delta)
3489                 put(w, ' ');
3490         }
3491     }
3492 }
3493 
3494 @safe pure unittest
3495 {
3496     import std.array : appender;
3497     import std.format : singleSpec;
3498 
3499     auto w = appender!string();
3500     auto spec = singleSpec("%s");
3501     writeAligned(w, "pre", "grouping", "suf", spec);
3502     assert(w.data == "pregroupingsuf", w.data);
3503 
3504     w = appender!string();
3505     spec = singleSpec("%20s");
3506     writeAligned(w, "pre", "grouping", "suf", spec);
3507     assert(w.data == "      pregroupingsuf", w.data);
3508 
3509     w = appender!string();
3510     spec = singleSpec("%-20s");
3511     writeAligned(w, "pre", "grouping", "suf", spec);
3512     assert(w.data == "pregroupingsuf      ", w.data);
3513 
3514     w = appender!string();
3515     spec = singleSpec("%020s");
3516     writeAligned(w, "pre", "grouping", "suf", spec);
3517     assert(w.data == "pre000000groupingsuf", w.data);
3518 
3519     w = appender!string();
3520     spec = singleSpec("%-020s");
3521     writeAligned(w, "pre", "grouping", "suf", spec);
3522     assert(w.data == "pregroupingsuf      ", w.data);
3523 
3524     w = appender!string();
3525     spec = singleSpec("%20,1s");
3526     writeAligned(w, "pre", "grouping", "suf", spec);
3527     assert(w.data == "preg,r,o,u,p,i,n,gsuf", w.data);
3528 
3529     w = appender!string();
3530     spec = singleSpec("%20,2s");
3531     writeAligned(w, "pre", "grouping", "suf", spec);
3532     assert(w.data == "   pregr,ou,pi,ngsuf", w.data);
3533 
3534     w = appender!string();
3535     spec = singleSpec("%20,3s");
3536     writeAligned(w, "pre", "grouping", "suf", spec);
3537     assert(w.data == "    pregr,oup,ingsuf", w.data);
3538 
3539     w = appender!string();
3540     spec = singleSpec("%20,10s");
3541     writeAligned(w, "pre", "grouping", "suf", spec);
3542     assert(w.data == "      pregroupingsuf", w.data);
3543 
3544     w = appender!string();
3545     spec = singleSpec("%020,1s");
3546     writeAligned(w, "pre", "grouping", "suf", spec);
3547     assert(w.data == "preg,r,o,u,p,i,n,gsuf", w.data);
3548 
3549     w = appender!string();
3550     spec = singleSpec("%020,2s");
3551     writeAligned(w, "pre", "grouping", "suf", spec);
3552     assert(w.data == "pre00,gr,ou,pi,ngsuf", w.data);
3553 
3554     w = appender!string();
3555     spec = singleSpec("%020,3s");
3556     writeAligned(w, "pre", "grouping", "suf", spec);
3557     assert(w.data == "pre00,0gr,oup,ingsuf", w.data);
3558 
3559     w = appender!string();
3560     spec = singleSpec("%020,10s");
3561     writeAligned(w, "pre", "grouping", "suf", spec);
3562     assert(w.data == "pre000,00groupingsuf", w.data);
3563 
3564     w = appender!string();
3565     spec = singleSpec("%021,3s");
3566     writeAligned(w, "pre", "grouping", "suf", spec);
3567     assert(w.data == "pre000,0gr,oup,ingsuf", w.data);
3568 
3569     // According to https://github.com/dlang/phobos/pull/7112 this
3570     // is defined by POSIX standard:
3571     w = appender!string();
3572     spec = singleSpec("%022,3s");
3573     writeAligned(w, "pre", "grouping", "suf", spec);
3574     assert(w.data == "pre0,000,0gr,oup,ingsuf", w.data);
3575 
3576     w = appender!string();
3577     spec = singleSpec("%023,3s");
3578     writeAligned(w, "pre", "grouping", "suf", spec);
3579     assert(w.data == "pre0,000,0gr,oup,ingsuf", w.data);
3580 
3581     w = appender!string();
3582     spec = singleSpec("%,3s");
3583     writeAligned(w, "pre", "grouping", "suf", spec);
3584     assert(w.data == "pregr,oup,ingsuf", w.data);
3585 }
3586 
3587 @safe pure unittest
3588 {
3589     import std.array : appender;
3590     import std.format : singleSpec;
3591 
3592     auto w = appender!string();
3593     auto spec = singleSpec("%.10s");
3594     writeAligned(w, "pre", "grouping", "suf", spec, true);
3595     assert(w.data == "pre00groupingsuf", w.data);
3596 
3597     w = appender!string();
3598     spec = singleSpec("%.10,3s");
3599     writeAligned(w, "pre", "grouping", "suf", spec, true);
3600     assert(w.data == "pre0,0gr,oup,ingsuf", w.data);
3601 
3602     w = appender!string();
3603     spec = singleSpec("%25.10,3s");
3604     writeAligned(w, "pre", "grouping", "suf", spec, true);
3605     assert(w.data == "      pre0,0gr,oup,ingsuf", w.data);
3606 
3607     // precision has precedence over zero flag
3608     w = appender!string();
3609     spec = singleSpec("%025.12,3s");
3610     writeAligned(w, "pre", "grouping", "suf", spec, true);
3611     assert(w.data == "    pre000,0gr,oup,ingsuf", w.data);
3612 
3613     w = appender!string();
3614     spec = singleSpec("%025.13,3s");
3615     writeAligned(w, "pre", "grouping", "suf", spec, true);
3616     assert(w.data == "  pre0,000,0gr,oup,ingsuf", w.data);
3617 }
3618 
3619 @safe unittest
3620 {
3621     assert(format("%,d", 1000) == "1,000");
3622     assert(format("%,f", 1234567.891011) == "1,234,567.891011");
3623     assert(format("%,?d", '?', 1000) == "1?000");
3624     assert(format("%,1d", 1000) == "1,0,0,0", format("%,1d", 1000));
3625     assert(format("%,*d", 4, -12345) == "-1,2345");
3626     assert(format("%,*?d", 4, '_', -12345) == "-1_2345");
3627     assert(format("%,6?d", '_', -12345678) == "-12_345678");
3628     assert(format("%12,3.3f", 1234.5678) == "   1,234.568", "'" ~
3629            format("%12,3.3f", 1234.5678) ~ "'");
3630 }
3631 
3632 private long getWidth(T)(T s)
3633 {
3634     import std.algorithm.searching : all;
3635     import std.uni : graphemeStride;
3636 
3637     // check for non-ascii character
3638     if (s.all!(a => a <= 0x7F)) return s.length;
3639 
3640     //TODO: optimize this
3641     long width = 0;
3642     for (size_t i; i < s.length; i += graphemeStride(s, i))
3643         ++width;
3644     return width;
3645 }
3646 
3647 enum RoundingClass { ZERO, LOWER, FIVE, UPPER }
3648 enum RoundingMode { up, down, toZero, toNearestTiesToEven, toNearestTiesAwayFromZero }
3649 
3650 bool round(T)(ref T sequence, size_t left, size_t right, RoundingClass type, bool negative, char max = '9')
3651 in (left >= 0) // should be left > 0, but if you know ahead, that there's no carry, left == 0 is fine
3652 in (left < sequence.length)
3653 in (right >= 0)
3654 in (right <= sequence.length)
3655 in (right >= left)
3656 in (max == '9' || max == 'f' || max == 'F')
3657 {
3658     import std.math.hardware;
3659 
3660     auto mode = RoundingMode.toNearestTiesToEven;
3661 
3662     if (!__ctfe)
3663     {
3664         // std.math's FloatingPointControl isn't available on all target platforms
3665         static if (is(FloatingPointControl))
3666         {
3667             switch (FloatingPointControl.rounding)
3668             {
3669             case FloatingPointControl.roundUp:
3670                 mode = RoundingMode.up;
3671                 break;
3672             case FloatingPointControl.roundDown:
3673                 mode = RoundingMode.down;
3674                 break;
3675             case FloatingPointControl.roundToZero:
3676                 mode = RoundingMode.toZero;
3677                 break;
3678             case FloatingPointControl.roundToNearest:
3679                 mode = RoundingMode.toNearestTiesToEven;
3680                 break;
3681             default: assert(false, "Unknown floating point rounding mode");
3682             }
3683         }
3684     }
3685 
3686     bool roundUp = false;
3687     if (mode == RoundingMode.up)
3688         roundUp = type != RoundingClass.ZERO && !negative;
3689     else if (mode == RoundingMode.down)
3690         roundUp = type != RoundingClass.ZERO && negative;
3691     else if (mode == RoundingMode.toZero)
3692         roundUp = false;
3693     else
3694     {
3695         roundUp = type == RoundingClass.UPPER;
3696 
3697         if (type == RoundingClass.FIVE)
3698         {
3699             // IEEE754 allows for two different ways of implementing roundToNearest:
3700 
3701             if (mode == RoundingMode.toNearestTiesAwayFromZero)
3702                 roundUp = true;
3703             else
3704             {
3705                 // Round to nearest, ties to even
3706                 auto last = sequence[right - 1];
3707                 if (last == '.') last = sequence[right - 2];
3708                 roundUp = (last <= '9' && last % 2 != 0) || (last > '9' && last % 2 == 0);
3709             }
3710         }
3711     }
3712 
3713     if (!roundUp) return false;
3714 
3715     foreach_reverse (i;left .. right)
3716     {
3717         if (sequence[i] == '.') continue;
3718         if (sequence[i] == max)
3719             sequence[i] = '0';
3720         else
3721         {
3722             if (max != '9' && sequence[i] == '9')
3723                 sequence[i] = max == 'f' ? 'a' : 'A';
3724             else
3725                 sequence[i]++;
3726             return false;
3727         }
3728     }
3729 
3730     sequence[left - 1] = '1';
3731     return true;
3732 }
3733 
3734 @safe unittest
3735 {
3736     char[10] c;
3737     size_t left = 5;
3738     size_t right = 8;
3739 
3740     c[4 .. 8] = "x.99";
3741     assert(round(c, left, right, RoundingClass.UPPER, false) == true);
3742     assert(c[4 .. 8] == "1.00");
3743 
3744     c[4 .. 8] = "x.99";
3745     assert(round(c, left, right, RoundingClass.FIVE, false) == true);
3746     assert(c[4 .. 8] == "1.00");
3747 
3748     c[4 .. 8] = "x.99";
3749     assert(round(c, left, right, RoundingClass.LOWER, false) == false);
3750     assert(c[4 .. 8] == "x.99");
3751 
3752     c[4 .. 8] = "x.99";
3753     assert(round(c, left, right, RoundingClass.ZERO, false) == false);
3754     assert(c[4 .. 8] == "x.99");
3755 
3756     import std.math.hardware;
3757     static if (is(FloatingPointControl))
3758     {
3759         FloatingPointControl fpctrl;
3760 
3761         fpctrl.rounding = FloatingPointControl.roundUp;
3762 
3763         c[4 .. 8] = "x.99";
3764         assert(round(c, left, right, RoundingClass.UPPER, false) == true);
3765         assert(c[4 .. 8] == "1.00");
3766 
3767         c[4 .. 8] = "x.99";
3768         assert(round(c, left, right, RoundingClass.FIVE, false) == true);
3769         assert(c[4 .. 8] == "1.00");
3770 
3771         c[4 .. 8] = "x.99";
3772         assert(round(c, left, right, RoundingClass.LOWER, false) == true);
3773         assert(c[4 .. 8] == "1.00");
3774 
3775         c[4 .. 8] = "x.99";
3776         assert(round(c, left, right, RoundingClass.ZERO, false) == false);
3777         assert(c[4 .. 8] == "x.99");
3778 
3779         fpctrl.rounding = FloatingPointControl.roundDown;
3780 
3781         c[4 .. 8] = "x.99";
3782         assert(round(c, left, right, RoundingClass.UPPER, false) == false);
3783         assert(c[4 .. 8] == "x.99");
3784 
3785         c[4 .. 8] = "x.99";
3786         assert(round(c, left, right, RoundingClass.FIVE, false) == false);
3787         assert(c[4 .. 8] == "x.99");
3788 
3789         c[4 .. 8] = "x.99";
3790         assert(round(c, left, right, RoundingClass.LOWER, false) == false);
3791         assert(c[4 .. 8] == "x.99");
3792 
3793         c[4 .. 8] = "x.99";
3794         assert(round(c, left, right, RoundingClass.ZERO, false) == false);
3795         assert(c[4 .. 8] == "x.99");
3796 
3797         fpctrl.rounding = FloatingPointControl.roundToZero;
3798 
3799         c[4 .. 8] = "x.99";
3800         assert(round(c, left, right, RoundingClass.UPPER, false) == false);
3801         assert(c[4 .. 8] == "x.99");
3802 
3803         c[4 .. 8] = "x.99";
3804         assert(round(c, left, right, RoundingClass.FIVE, false) == false);
3805         assert(c[4 .. 8] == "x.99");
3806 
3807         c[4 .. 8] = "x.99";
3808         assert(round(c, left, right, RoundingClass.LOWER, false) == false);
3809         assert(c[4 .. 8] == "x.99");
3810 
3811         c[4 .. 8] = "x.99";
3812         assert(round(c, left, right, RoundingClass.ZERO, false) == false);
3813         assert(c[4 .. 8] == "x.99");
3814     }
3815 }
3816 
3817 @safe unittest
3818 {
3819     char[10] c;
3820     size_t left = 5;
3821     size_t right = 8;
3822 
3823     c[4 .. 8] = "x8.5";
3824     assert(round(c, left, right, RoundingClass.UPPER, true) == false);
3825     assert(c[4 .. 8] == "x8.6");
3826 
3827     c[4 .. 8] = "x8.5";
3828     assert(round(c, left, right, RoundingClass.FIVE, true) == false);
3829     assert(c[4 .. 8] == "x8.6");
3830 
3831     c[4 .. 8] = "x8.4";
3832     assert(round(c, left, right, RoundingClass.FIVE, true) == false);
3833     assert(c[4 .. 8] == "x8.4");
3834 
3835     c[4 .. 8] = "x8.5";
3836     assert(round(c, left, right, RoundingClass.LOWER, true) == false);
3837     assert(c[4 .. 8] == "x8.5");
3838 
3839     c[4 .. 8] = "x8.5";
3840     assert(round(c, left, right, RoundingClass.ZERO, true) == false);
3841     assert(c[4 .. 8] == "x8.5");
3842 
3843     import std.math.hardware;
3844     static if (is(FloatingPointControl))
3845     {
3846         FloatingPointControl fpctrl;
3847 
3848         fpctrl.rounding = FloatingPointControl.roundUp;
3849 
3850         c[4 .. 8] = "x8.5";
3851         assert(round(c, left, right, RoundingClass.UPPER, true) == false);
3852         assert(c[4 .. 8] == "x8.5");
3853 
3854         c[4 .. 8] = "x8.5";
3855         assert(round(c, left, right, RoundingClass.FIVE, true) == false);
3856         assert(c[4 .. 8] == "x8.5");
3857 
3858         c[4 .. 8] = "x8.5";
3859         assert(round(c, left, right, RoundingClass.LOWER, true) == false);
3860         assert(c[4 .. 8] == "x8.5");
3861 
3862         c[4 .. 8] = "x8.5";
3863         assert(round(c, left, right, RoundingClass.ZERO, true) == false);
3864         assert(c[4 .. 8] == "x8.5");
3865 
3866         fpctrl.rounding = FloatingPointControl.roundDown;
3867 
3868         c[4 .. 8] = "x8.5";
3869         assert(round(c, left, right, RoundingClass.UPPER, true) == false);
3870         assert(c[4 .. 8] == "x8.6");
3871 
3872         c[4 .. 8] = "x8.5";
3873         assert(round(c, left, right, RoundingClass.FIVE, true) == false);
3874         assert(c[4 .. 8] == "x8.6");
3875 
3876         c[4 .. 8] = "x8.5";
3877         assert(round(c, left, right, RoundingClass.LOWER, true) == false);
3878         assert(c[4 .. 8] == "x8.6");
3879 
3880         c[4 .. 8] = "x8.5";
3881         assert(round(c, left, right, RoundingClass.ZERO, true) == false);
3882         assert(c[4 .. 8] == "x8.5");
3883 
3884         fpctrl.rounding = FloatingPointControl.roundToZero;
3885 
3886         c[4 .. 8] = "x8.5";
3887         assert(round(c, left, right, RoundingClass.UPPER, true) == false);
3888         assert(c[4 .. 8] == "x8.5");
3889 
3890         c[4 .. 8] = "x8.5";
3891         assert(round(c, left, right, RoundingClass.FIVE, true) == false);
3892         assert(c[4 .. 8] == "x8.5");
3893 
3894         c[4 .. 8] = "x8.5";
3895         assert(round(c, left, right, RoundingClass.LOWER, true) == false);
3896         assert(c[4 .. 8] == "x8.5");
3897 
3898         c[4 .. 8] = "x8.5";
3899         assert(round(c, left, right, RoundingClass.ZERO, true) == false);
3900         assert(c[4 .. 8] == "x8.5");
3901     }
3902 }
3903 
3904 @safe unittest
3905 {
3906     char[10] c;
3907     size_t left = 5;
3908     size_t right = 8;
3909 
3910     c[4 .. 8] = "x8.9";
3911     assert(round(c, left, right, RoundingClass.UPPER, true, 'f') == false);
3912     assert(c[4 .. 8] == "x8.a");
3913 
3914     c[4 .. 8] = "x8.9";
3915     assert(round(c, left, right, RoundingClass.UPPER, true, 'F') == false);
3916     assert(c[4 .. 8] == "x8.A");
3917 
3918     c[4 .. 8] = "x8.f";
3919     assert(round(c, left, right, RoundingClass.UPPER, true, 'f') == false);
3920     assert(c[4 .. 8] == "x9.0");
3921 }
3922 
3923 version (StdUnittest)
3924 private void formatTest(T)(T val, string expected, size_t ln = __LINE__, string fn = __FILE__)
3925 {
3926     formatTest(val, [expected], ln, fn);
3927 }
3928 
3929 version (StdUnittest)
3930 private void formatTest(T)(string fmt, T val, string expected, size_t ln = __LINE__, string fn = __FILE__) @safe
3931 {
3932     formatTest(fmt, val, [expected], ln, fn);
3933 }
3934 
3935 version (StdUnittest)
3936 private void formatTest(T)(T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__)
3937 {
3938     import core.exception : AssertError;
3939     import std.algorithm.searching : canFind;
3940     import std.array : appender;
3941     import std.conv : text;
3942     import std.exception : enforce;
3943     import std.format.write : formatValue;
3944 
3945     FormatSpec!char f;
3946     auto w = appender!string();
3947     formatValue(w, val, f);
3948     enforce!AssertError(expected.canFind(w.data),
3949         text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln);
3950 }
3951 
3952 version (StdUnittest)
3953 private void formatTest(T)(string fmt, T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__) @safe
3954 {
3955     import core.exception : AssertError;
3956     import std.algorithm.searching : canFind;
3957     import std.array : appender;
3958     import std.conv : text;
3959     import std.exception : enforce;
3960     import std.format.write : formattedWrite;
3961 
3962     auto w = appender!string();
3963     formattedWrite(w, fmt, val);
3964     enforce!AssertError(expected.canFind(w.data),
3965         text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln);
3966 }