The OpenD Programming Language

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