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