1 // Written in the D programming language.
4 Copyright: Copyright The D Language Foundation 2000-2013.
6 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
8 Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com,
9 Andrei Alexandrescu), and Kenji Hara
11 Source: $(PHOBOSSRC std/format/internal/write.d)
13 module std.format.internal.write;
15 import std.format.spec : FormatSpec;
16 import std.range.primitives : isInputRange;
21 import std.exception : assertCTFEable;
22 import std.format : format;
28 `bool`s are formatted as `"true"` or `"false"` with `%s` and as `1` or
29 `0` with integral-specific format specs.
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))
34 BooleanTypeOf!T val = obj;
37 writeAligned(w, val ? "true" : "false", f);
39 formatValueImpl(w, cast(byte) val, f);
46 formatTest(false, "false");
47 formatTest(true, "true");
57 this(bool v){ val = v; }
63 this(bool v){ val = v; }
64 override string toString() const { return "C"; }
68 formatTest(new C1(false), "false");
69 formatTest(new C1(true), "true");
70 formatTest(new C2(false), "C");
71 formatTest(new C2(true), "C");
84 string toString() const { return "S"; }
87 formatTest(S1(false), "false");
88 formatTest(S1(true), "true");
89 formatTest(S2(false), "S");
90 formatTest(S2(true), "S");
95 string t1 = format("[%6s] [%6s] [%-6s]", true, false, true);
96 assert(t1 == "[ true] [ false] [true ]");
98 string t2 = format("[%3s] [%-2s]", true, false);
99 assert(t2 == "[true] [false]");
102 // https://issues.dlang.org/show_bug.cgi?id=20534
105 assert(format("%r",false) == "\0");
110 assert(format("%07s",true) == " true");
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 ");
124 `null` literal is formatted as `"null"`
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))
129 import std.format : enforceFmt;
132 enforceFmt(spec == 's', "null literal cannot match %" ~ spec);
134 writeAligned(w, "null", f);
139 import std.exception : collectExceptionMsg;
140 import std.format : FormatException;
141 import std.range.primitives : back;
143 assert(collectExceptionMsg!FormatException(format("%p", null)).back == 'p');
147 formatTest(null, "null");
153 string t = format("[%6s] [%-6s]", null, null);
154 assert(t == "[ null] [null ]");
158 Integrals are formatted like $(REF printf, core, stdc, stdio).
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))
163 import std.range.primitives : put;
165 alias U = IntegralTypeOf!T;
166 U val = obj; // Extracting alias this may be impure/system/may-throw
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];
175 if (needToSwapEndianess(f))
177 foreach_reverse (c; raw)
188 immutable uint base =
189 f.spec == 'x' || f.spec == 'X' || f.spec == 'a' || f.spec == 'A' ? 16 :
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 :
197 import std.format : enforceFmt;
199 "incompatible format character for integral argument: %" ~ f.spec);
201 import std.math.algebraic : abs;
203 bool negative = false;
205 static if (isSigned!U)
207 if (f.spec != 'x' && f.spec != 'X' && f.spec != 'b' && f.spec != 'o' && f.spec != 'u')
212 arg = cast(ulong) abs(val);
217 arg &= Unsigned!U.max;
219 char[64] digits = void;
220 size_t pos = digits.length - 1;
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;
229 char[3] prefix = void;
234 if (f.spec != 'x' && f.spec != 'X' && f.spec != 'b' && f.spec != 'o' && f.spec != 'u')
237 prefix[right++] = '-';
239 prefix[right++] = '+';
241 prefix[right++] = ' ';
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')
248 if (f.flHash && (base == 16) && obj != 0)
250 prefix[--left] = f.spec;
251 prefix[--left] = '0';
253 if (f.flHash && (base == 8) && obj != 0
254 && (digits.length - (pos + 1) >= f.precision || f.precision == f.UNSPECIFIED))
255 prefix[--left] = '0';
257 writeAligned(w, prefix[left .. right], digits[pos + 1 .. $], "", f, true);
261 FormatSpec!Char fs = f;
262 if (f.precision == f.UNSPECIFIED)
263 fs.precision = cast(typeof(fs.precision)) (digits.length - pos - 2);
266 if (f.spec == 'f' || f.spec == 'F'
267 || ((f.spec == 'g' || f.spec == 'G') && (fs.precision >= digits.length - pos - 2)))
269 if (f.precision == f.UNSPECIFIED)
272 writeAligned(w, prefix[left .. right], digits[pos + 1 .. $], ".", "", fs,
273 (f.spec == 'g' || f.spec == 'G') ? PrecisionType.allDigits : PrecisionType.fractionalDigits);
278 import std.algorithm.searching : all;
280 // at least one digit for %g
281 if ((f.spec == 'g' || f.spec == 'G') && fs.precision == 0)
285 size_t digit_end = pos + fs.precision + ((f.spec == 'g' || f.spec == 'G') ? 1 : 2);
286 if (digit_end <= digits.length)
288 RoundingClass rt = RoundingClass.ZERO;
289 if (digit_end < digits.length)
291 auto tie = (f.spec == 'a' || f.spec == 'A') ? '8' : '5';
292 if (digits[digit_end] >= tie)
294 rt = RoundingClass.UPPER;
295 if (digits[digit_end] == tie && digits[digit_end + 1 .. $].all!(a => a == '0'))
296 rt = RoundingClass.FIVE;
300 rt = RoundingClass.LOWER;
301 if (digits[digit_end .. $].all!(a => a == '0'))
302 rt = RoundingClass.ZERO;
306 if (round(digits, pos + 1, digit_end, rt, negative,
307 f.spec == 'a' ? 'f' : (f.spec == 'A' ? 'F' : '9')))
314 // convert to scientific notation
315 char[1] int_digit = void;
316 int_digit[0] = digits[pos + 1];
317 digits[pos + 1] = '.';
319 char[4] suffix = void;
321 if (f.spec == 'e' || f.spec == 'E' || f.spec == 'g' || f.spec == 'G')
323 suffix[0] = (f.spec == 'e' || f.spec == 'g') ? 'e' : 'E';
325 suffix[2] = cast(char) ('0' + (digits.length - pos - 2) / 10);
326 suffix[3] = cast(char) ('0' + (digits.length - pos - 2) % 10);
331 prefix[0] = prefix[2];
333 prefix[2] = f.spec == 'a' ? 'x' : 'X';
335 left = right == 3 ? 0 : 1;
338 suffix[0] = f.spec == 'a' ? 'p' : 'P';
340 suffix[2] = cast(char) ('0' + ((digits.length - pos - 2) * 4) / 10);
341 suffix[3] = cast(char) ('0' + ((digits.length - pos - 2) * 4) % 10);
344 import std.algorithm.comparison : min;
346 // remove trailing zeros
347 if ((f.spec == 'g' || f.spec == 'G') && !f.flHash)
349 digit_end = min(digit_end, digits.length);
350 while (digit_end > pos + 1 &&
351 (digits[digit_end - 1] == '0' || digits[digit_end - 1] == '.'))
355 writeAligned(w, prefix[left .. right], int_digit[0 .. $],
356 digits[pos + 1 .. min(digit_end, $)],
358 (f.spec == 'g' || f.spec == 'G') ? PrecisionType.allDigits : PrecisionType.fractionalDigits);
361 // https://issues.dlang.org/show_bug.cgi?id=18838
364 assert("%12,d".format(0) == " 0");
369 import std.exception : collectExceptionMsg;
370 import std.format : FormatException;
371 import std.range.primitives : back;
373 assert(collectExceptionMsg!FormatException(format("%c", 5)).back == 'c');
378 formatTest(10, "10");
388 this(long v){ val = v; }
395 this(long v){ val = v; }
396 override string toString() const { return "C"; }
400 formatTest(new C1(10), "10");
401 formatTest(new C2(10), "C");
414 string toString() const { return "S"; }
417 formatTest(S1(10), "10");
418 formatTest(S2(10), "S");
421 // https://issues.dlang.org/show_bug.cgi?id=20064
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");
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");
459 string t1 = format("[%6s] [%-6s]", 123, 123);
460 assert(t1 == "[ 123] [123 ]");
462 string t2 = format("[%6s] [%-6s]", -123, -123);
463 assert(t2 == "[ -123] [-123 ]");
468 formatTest(byte.min, "-128");
469 formatTest(short.min, "-32768");
470 formatTest(int.min, "-2147483648");
471 formatTest(long.min, "-9223372036854775808");
474 // https://issues.dlang.org/show_bug.cgi?id=21777
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");
496 // https://issues.dlang.org/show_bug.cgi?id=21814
499 assert(format("%,0d",1000) == "1000");
502 // https://issues.dlang.org/show_bug.cgi?id=21817
505 assert(format!"%u"(-5) == "4294967291");
508 // https://issues.dlang.org/show_bug.cgi?id=21820
511 assert(format!"%#.0o"(0) == "0");
516 assert(format!"%e"(10000) == "1.0000e+04");
517 assert(format!"%.2e"(10000) == "1.00e+04");
518 assert(format!"%.10e"(10000) == "1.0000000000e+04");
520 assert(format!"%e"(9999) == "9.999e+03");
521 assert(format!"%.2e"(9999) == "1.00e+04");
522 assert(format!"%.10e"(9999) == "9.9990000000e+03");
524 assert(format!"%f"(10000) == "10000");
525 assert(format!"%.2f"(10000) == "10000.00");
527 assert(format!"%g"(10000) == "10000");
528 assert(format!"%.2g"(10000) == "1e+04");
529 assert(format!"%.10g"(10000) == "10000");
531 assert(format!"%#g"(10000) == "10000.");
532 assert(format!"%#.2g"(10000) == "1.0e+04");
533 assert(format!"%#.10g"(10000) == "10000.00000");
535 assert(format!"%g"(9999) == "9999");
536 assert(format!"%.2g"(9999) == "1e+04");
537 assert(format!"%.10g"(9999) == "9999");
539 assert(format!"%a"(0x10000) == "0x1.0000p+16");
540 assert(format!"%.2a"(0x10000) == "0x1.00p+16");
541 assert(format!"%.10a"(0x10000) == "0x1.0000000000p+16");
543 assert(format!"%a"(0xffff) == "0xf.fffp+12");
544 assert(format!"%.2a"(0xffff) == "0x1.00p+16");
545 assert(format!"%.10a"(0xffff) == "0xf.fff0000000p+12");
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");
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");
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");
568 assert(format!"%.0g"(1500) == "2e+03");
571 // https://issues.dlang.org/show_bug.cgi?id=21900#
574 assert(format!"%.1a"(472) == "0x1.ep+08");
578 Floating-point values are formatted like $(REF printf, core, stdc, stdio)
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))
583 import std.algorithm.searching : find;
584 import std.format : enforceFmt;
585 import std.range.primitives : put;
587 FloatingPointTypeOf!T val = obj;
588 const char spec = f.spec;
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];
597 if (needToSwapEndianess(f))
599 foreach_reverse (c; raw)
610 enforceFmt(find("fgFGaAeEs", spec).length,
611 "incompatible format character for floating point argument: %" ~ spec);
613 FormatSpec!Char fs = f; // fs is copy for change its values.
614 fs.spec = spec == 's' ? 'g' : spec;
616 static if (is(T == float) || is(T == double)
617 || (is(T == real) && (T.mant_dig == double.mant_dig || T.mant_dig == 64)))
623 import std.math.traits : isInfinity;
624 import std.math.operations : nextUp;
626 // reals that are not supported by printFloat are cast to double.
629 // Numbers greater than double.max are converted to double.max:
630 if (val > double.max && !isInfinity(val))
632 if (val < -double.max && !isInfinity(val))
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)
640 if (val < 0 && val > -doubleLowest)
641 tval = -doubleLowest;
644 import std.format.internal.floats : printFloat;
645 printFloat(w, tval, fs);
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.");
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;
665 assert(collectExceptionMsg!FormatException(format("%d", 5.1)).back == 'd');
667 static foreach (T; AliasSeq!(float, double, real))
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");
673 formatTest(T.nan, "nan");
679 formatTest(2.25, "2.25");
685 this(double v){ val = v; }
692 this(double v){ val = v; }
693 override string toString() const { return "C"; }
697 formatTest(new C1(2.25), "2.25");
698 formatTest(new C2(2.25), "C");
710 string toString() const { return "S"; }
713 formatTest(S1(2.25), "2.25");
714 formatTest(S2(2.25), "S");
717 // https://issues.dlang.org/show_bug.cgi?id=19939
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$");
730 // https://issues.dlang.org/show_bug.cgi?id=20069
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");
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");
763 import std.math.hardware; // cannot be selective, because FloatingPointControl might not be defined
765 // std.math's FloatingPointControl isn't available on all target platforms
766 static if (is(FloatingPointControl))
768 assert(FloatingPointControl.rounding == FloatingPointControl.roundToNearest);
774 assert(format("%.1f", a) == "0.2");
775 assert(format("%.2f", b) == "0.02");
779 assert(format("%.1f", a1) == "0.2");
780 assert(format("%.2f", b1) == "0.02");
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");
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");
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");
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.");
818 assert(format("%+010.4f",a) == "+0123.4560");
819 assert(format("% 010.4f",a) == " 0123.4560");
820 assert(format("% +010.4f",a) == "+0123.4560");
825 string t1 = format("[%6s] [%-6s]", 12.3, 12.3);
826 assert(t1 == "[ 12.3] [12.3 ]");
828 string t2 = format("[%6s] [%-6s]", -12.3, -12.3);
829 assert(t2 == "[ -12.3] [-12.3 ]");
832 // https://issues.dlang.org/show_bug.cgi?id=20396
835 import std.math.operations : nextUp;
837 assert(format!"%a"(nextUp(0.0f)) == "0x0.000002p-126");
838 assert(format!"%a"(nextUp(0.0)) == "0x0.0000000000001p-1022");
841 // https://issues.dlang.org/show_bug.cgi?id=20371
844 assert(format!"%.1000a"(1.0).length == 1007);
845 assert(format!"%.600f"(0.1).length == 602);
846 assert(format!"%.600e"(0.1L).length == 606);
851 import std.math.hardware; // cannot be selective, because FloatingPointControl might not be defined
853 // std.math's FloatingPointControl isn't available on all target platforms
854 static if (is(FloatingPointControl))
856 FloatingPointControl fpctrl;
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");
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");
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");
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");
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");
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");
908 // https://issues.dlang.org/show_bug.cgi?id=21641
911 float a = -999999.8125;
912 assert(format("%#.5g",a) == "-1.0000e+06");
913 assert(format("%#.6g",a) == "-1.00000e+06");
916 // https://issues.dlang.org/show_bug.cgi?id=8424
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");
924 // https://issues.dlang.org/show_bug.cgi?id=9297
927 static if (real.mant_dig == 64) // 80 bit reals
929 assert(format("%.25f", 1.6180339887_4989484820_4586834365L) == "1.6180339887498948482072100");
933 // https://issues.dlang.org/show_bug.cgi?id=21853
936 import std.math.exponential : log2;
938 // log2 is broken for x87-reals on some computers in CTFE
939 // the following test excludes these computers from the test
941 enum test = cast(int) log2(3.05e2312L);
942 static if (real.mant_dig == 64 && test == 7681) // 80 bit reals
944 static assert(format!"%e"(real.max) == "1.189731e+4932");
948 // https://issues.dlang.org/show_bug.cgi?id=21842
951 assert(format!"%-+05,g"(1.0) == "+1 ");
954 // https://issues.dlang.org/show_bug.cgi?id=20536
957 real r = .00000095367431640625L;
958 assert(format("%a", r) == "0x1p-20");
961 // https://issues.dlang.org/show_bug.cgi?id=21840
964 assert(format!"% 0,e"(0.0) == " 0.000000e+00");
967 // https://issues.dlang.org/show_bug.cgi?id=21841
970 assert(format!"%0.0,e"(0.0) == "0e+00");
973 // https://issues.dlang.org/show_bug.cgi?id=21836
976 assert(format!"%-5,1g"(0.0) == "0 ");
979 // https://issues.dlang.org/show_bug.cgi?id=21838
982 assert(format!"%#,a"(0.0) == "0x0.p+0");
986 Formatting a `creal` is deprecated but still kept around for a while.
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))
992 import std.range.primitives : put;
994 immutable creal val = obj;
996 formatValueImpl(w, val.re, f);
1001 formatValueImpl(w, val.im, f);
1006 Formatting an `ireal` is deprecated but still kept around for a while.
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))
1012 import std.range.primitives : put;
1014 immutable ireal val = obj;
1016 formatValueImpl(w, val.im, f);
1021 Individual characters are formatted as Unicode characters with `%s`
1022 and as integers with integral-specific format specs
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))
1027 import std.meta : AliasSeq;
1029 CharTypeOf!T[1] val = obj;
1031 if (f.spec == 's' || f.spec == 'c')
1032 writeAligned(w, val[], f);
1035 alias U = AliasSeq!(ubyte, ushort, uint)[CharTypeOf!T.sizeof/2];
1036 formatValueImpl(w, cast(U) val[0], f);
1044 formatTest('c', "c");
1054 this(char v){ val = v; }
1061 this(char v){ val = v; }
1062 override string toString() const { return "C"; }
1066 formatTest(new C1('c'), "c");
1067 formatTest(new C2('c'), "C");
1080 string toString() const { return "S"; }
1083 formatTest(S1('c'), "c");
1084 formatTest(S2('c'), "S");
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'] );
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']);
1105 string t1 = format("[%6s] [%-6s]", 'A', 'A');
1106 assert(t1 == "[ A] [A ]");
1107 string t2 = format("[%6s] [%-6s]", '本', '本');
1108 assert(t2 == "[ 本] [本 ]");
1112 Strings are formatted like $(REF printf, core, stdc, stdio)
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))
1118 Unqual!(StringTypeOf!T) val = obj; // for `alias this`, see bug5371
1119 formatRange(w, val, f);
1124 formatTest("abc", "abc");
1129 import std.exception : collectExceptionMsg;
1130 import std.range.primitives : back;
1132 assert(collectExceptionMsg(format("%d", "hi")).back == 'd');
1137 // Test for bug 5371 for classes
1142 this(string s){ var = s; }
1149 this(string s){ var = s; }
1153 formatTest(new C1("c1"), "c1");
1154 formatTest(new C2("c2"), "c2");
1157 // Test for bug 5371 for structs
1170 formatTest(S1("s1"), "s1");
1171 formatTest(S2("s2"), "s2");
1180 this(string s){ val = s; }
1181 override string toString() const { return "C"; }
1184 () @trusted { formatTest(new C3("c3"), "C"); } ();
1188 string val; alias val this;
1189 string toString() const { return "S"; }
1192 formatTest(S3("s3"), "S");
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']);
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']);
1220 string t1 = format("[%6s] [%-6s]", "AB", "AB");
1221 assert(t1 == "[ AB] [AB ]");
1222 string t2 = format("[%6s] [%-6s]", "本Ä", "本Ä");
1223 assert(t2 == "[ 本Ä] [本Ä ]");
1226 // https://issues.dlang.org/show_bug.cgi?id=6640
1229 import std.range.primitives : front, popFront;
1236 @property bool empty() const { return !value.length; }
1237 @property dchar front() const { return value.front; }
1238 void popFront() { value.popFront(); }
1240 @property size_t length() const { return value.length; }
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]"],
1252 formatTest(e[0], "string", e[1]);
1253 formatTest(e[0], Range("string"), e[1]);
1259 import std.meta : AliasSeq;
1261 // string literal from valid UTF sequence is encoding free.
1262 static foreach (StrType; AliasSeq!(string, wstring, dstring))
1264 // Valid and printable (ASCII)
1265 formatTest([cast(StrType)"hello"],
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"]`);
1272 // 1 character optional escape sequences
1273 formatTest([cast(StrType)"\'\?"],
1276 // Valid and non-printable code point (<= U+FF)
1277 formatTest([cast(StrType)"\x10\x1F\x20test"],
1278 `["\x10\x1F test"]`);
1280 // Valid and non-printable code point (<= U+FFFF)
1281 formatTest([cast(StrType)"\u200B..\u200F"],
1282 `["\u200B..\u200F"]`);
1284 // Valid and non-printable code point (<= U+10FFFF)
1285 formatTest([cast(StrType)"\U000E0020..\U000E007F"],
1286 `["\U000E0020..\U000E007F"]`);
1289 // invalid UTF sequence needs hex-string literal postfix (c/w/d)
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]]`);
1296 // U+FFFF with UTF-16 (Invalid code point for interchange)
1297 formatTest([cast(wstring)[0xFFFF]],
1298 `[[cast(wchar) 0xFFFF]]`);
1300 // U+FFFF with UTF-32 (Invalid code point for interchange)
1301 formatTest([cast(dstring)[0xFFFF]],
1302 `[[cast(dchar) 0xFFFF]]`);
1307 Static-size arrays are formatted as dynamic arrays.
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))
1313 formatValueImpl(w, obj[], f);
1316 // Test for https://issues.dlang.org/show_bug.cgi?id=8310
1319 import std.array : appender;
1320 import std.format : formatValue;
1323 auto w = appender!string();
1325 char[2] two = ['a', 'b'];
1326 formatValue(w, two, f);
1328 char[2] getTwo() { return two; }
1329 formatValue(w, getTwo(), f);
1332 // https://issues.dlang.org/show_bug.cgi?id=18205
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|");
1341 assert("%2s".format("e\u0301"w) == " e\u0301");
1342 assert("%2s".format("a\u0310\u0337"d) == " a\u0310\u0337");
1346 Dynamic arrays are formatted as input ranges.
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))
1351 static if (is(immutable(ArrayTypeOf!T) == immutable(void[])))
1353 formatValueImpl(w, cast(const ubyte[]) obj, f);
1355 else static if (!isInputRange!T)
1357 alias U = Unqual!(ArrayTypeOf!T);
1358 static assert(isInputRange!U, U.stringof ~ " must be an InputRange");
1360 formatValueImpl(w, val, f);
1364 formatRange(w, obj, f);
1368 // https://issues.dlang.org/show_bug.cgi?id=20848
1373 immutable(void)[] data;
1376 import std.typecons : Nullable;
1380 // alias this, input range I/F, and toString()
1386 static if (flags & 1)
1389 static if (flags & 2)
1391 @property bool empty() const { return arr.length == 0; }
1392 @property int front() const { return arr[0] * 2; }
1393 void popFront() { arr = arr[1 .. $]; }
1396 static if (flags & 4)
1397 string toString() const { return "S"; }
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");
1412 static if (flags & 1)
1415 this(int[] a) { arr = a; }
1417 static if (flags & 2)
1419 @property bool empty() const { return arr.length == 0; }
1420 @property int front() const { return arr[0] * 2; }
1421 void popFront() { arr = arr[1 .. $]; }
1424 static if (flags & 4)
1425 override string toString() const { return "C"; }
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");
1444 formatTest(val0, "[]");
1446 void[] val = cast(void[]) cast(ubyte[])[1, 2, 3];
1447 formatTest(val, "[1, 2, 3]");
1450 formatTest(sval0, "[]");
1452 void[3] sval = () @trusted { return cast(void[3]) cast(ubyte[3])[1, 2, 3]; } ();
1453 formatTest(sval, "[1, 2, 3]");
1458 // const(T[]) -> const(T)[]
1459 const short[] a = [1, 2, 3];
1460 formatTest(a, "[1, 2, 3]");
1468 auto s = S([1,2,3]);
1469 formatTest(s, "[1, 2, 3]");
1474 // nested range formatting with array of string
1475 formatTest("%({%(%02x %)}%| %)", ["test", "msg"],
1476 `{74 65 73 74} {6d 73 67}`);
1481 // stop auto escaping inside range formatting
1482 auto arr = ["hello", "world"];
1483 formatTest("%(%s, %)", arr, `"hello", "world"`);
1484 formatTest("%-(%s, %)", arr, `hello, world`);
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`]);
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`]);
1496 // https://issues.dlang.org/show_bug.cgi?id=18778
1499 assert(format("%-(%1$s - %1$s, %)", ["A", "B", "C"]) == "A - A, B - B, C - C");
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");
1511 formatTest("(%s)", empt, "([])");
1514 // input range formatting
1515 private void formatRange(Writer, T, Char)(ref Writer w, ref T val, scope const ref FormatSpec!Char f)
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;
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);
1527 // Formatting character ranges like string
1530 alias E = ElementType!T;
1532 static if (!is(E == enum) && is(CharTypeOf!E))
1534 static if (is(StringTypeOf!T))
1535 writeAligned(w, val[0 .. f.precision < $ ? f.precision : $], f);
1540 static if (hasLength!T)
1543 auto len = val.length;
1545 else static if (isForwardRange!T && !isInfinite!T)
1547 auto len = walkLength(val.save);
1551 import std.format : enforceFmt;
1552 enforceFmt(f.width == 0, "Cannot right-align a range without length");
1555 if (f.precision != f.UNSPECIFIED && len > f.precision)
1559 foreach (i ; 0 .. f.width - len)
1561 if (f.precision == f.UNSPECIFIED)
1566 for (; !val.empty && printed < f.precision; val.popFront(), ++printed)
1572 size_t printed = void;
1575 if (f.precision == f.UNSPECIFIED)
1577 static if (hasLength!T)
1579 printed = val.length;
1585 for (; !val.empty; val.popFront(), ++printed)
1588 static if (formatTestMode) break; // one is enough to test
1595 for (; !val.empty && printed < f.precision; val.popFront(), ++printed)
1599 if (f.width > printed)
1600 foreach (i ; 0 .. f.width - printed)
1607 put(w, f.seqBefore);
1610 formatElement(w, val.front, f);
1612 for (size_t i; !val.empty; val.popFront(), ++i)
1614 put(w, f.seqSeparator);
1615 formatElement(w, val.front, f);
1616 static if (formatTestMode) break; // one is enough to test
1619 static if (!isInfinite!T) put(w, f.seqAfter);
1622 else if (f.spec == 'r')
1624 static if (is(DynamicArrayTypeOf!T))
1626 alias ARR = DynamicArrayTypeOf!T;
1627 scope a = cast(ARR) val;
1630 formatValue(w, e, f);
1631 static if (formatTestMode) break; // one is enough to test
1636 for (size_t i; !val.empty; val.popFront(), ++i)
1638 formatValue(w, val.front, f);
1639 static if (formatTestMode) break; // one is enough to test
1643 else if (f.spec == '(')
1647 // Nested specifier is to be used
1650 auto fmt = FormatSpec!Char(f.nested);
1653 immutable r = fmt.writeUpToNextSpec(w);
1654 // There was no format specifier, so break
1658 formatValue(w, val.front, fmt);
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] == '%')
1669 static if (formatTestMode)
1671 break; // one is enough to test
1677 put(w, fmt.trailing);
1688 put(w, fmt.trailing);
1694 throw new FormatException(text("Incorrect format specifier for range: %", f.spec));
1697 // https://issues.dlang.org/show_bug.cgi?id=20218
1702 import std.range : repeat;
1704 auto value = 1.repeat;
1706 // test that range is not evaluated to completion at compiletime
1711 // character formatting with ecaping
1712 void formatChar(Writer)(ref Writer w, in dchar c, in char quote)
1714 import std.format : formattedWrite;
1715 import std.range.primitives : put;
1716 import std.uni : isGraphical;
1721 if (c == quote || c == '\\')
1730 foreach (i, k; "\n\r\t\a\b\f\v\0")
1735 put(w, "nrtabfv0"[i]);
1742 else if (c <= 0xFFFF)
1747 formattedWrite(w, fmt, cast(uint) c);
1751 Associative arrays are formatted by using `':'` and $(D ", ") as
1752 separators, and enclosed by `'['` and `']'`.
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))
1757 import std.format : enforceFmt, formatValue;
1758 import std.range.primitives : put;
1760 AssocArrayTypeOf!T val = obj;
1761 const spec = f.spec;
1763 enforceFmt(spec == 's' || spec == '(',
1764 "incompatible format character for associative array argument: %" ~ spec);
1766 enum const(Char)[] defSpec = "%s" ~ f.keySeparator ~ "%s" ~ f.seqSeparator;
1767 auto fmtSpec = spec == '(' ? f.nested : defSpec;
1769 auto key_first = true;
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)
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");
1789 enforceFmt(!test.writeUpToNextSpec(noop),
1790 "nested format string for associative array contains more than two format specifiers");
1793 immutable end = val.length;
1796 put(w, f.seqBefore);
1797 foreach (k, ref v; val)
1799 auto fmt = FormatSpec!Char(fmtSpec);
1801 foreach (pos; 1 .. 3)
1803 fmt.writeUpToNextSpec(w);
1805 if (key_first == (pos == 1))
1808 formatValue(w, k, fmt);
1810 formatElement(w, k, fmt);
1815 formatValue(w, v, fmt);
1817 formatElement(w, v, fmt);
1823 fmt.writeUpToNextSpec(w);
1830 fmt.writeUpToNextSpec(w);
1839 import std.exception : collectExceptionMsg;
1840 import std.format : FormatException;
1841 import std.range.primitives : back;
1843 assert(collectExceptionMsg!FormatException(format("%d", [0:1])).back == 'd');
1846 formatTest(aa0, `[]`);
1848 // elements escaping
1849 formatTest(["aaa":1, "bbb":2],
1850 [`["aaa":1, "bbb":2]`, `["bbb":2, "aaa":1]`]);
1851 formatTest(['c':"str"],
1853 formatTest(['"':"\"", '\'':"'"],
1854 [`['"':"\"", '\'':"'"]`, `['\'':"'", '"':"\""]`]);
1856 // range formatting for AA
1857 auto aa3 = [1:"hello", 2:"world"];
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]}`]);
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>");
1877 this(int[char] v){ val = v; }
1884 this(int[char] v){ val = v; }
1885 override string toString() const { return "C"; }
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");
1903 string toString() const { return "S"; }
1906 formatTest(S1(['c':1, 'd':2]), [`['c':1, 'd':2]`, `['d':2, 'c':1]`]);
1907 formatTest(S2(['c':1, 'd':2]), "S");
1910 // https://issues.dlang.org/show_bug.cgi?id=21875
1913 import std.exception : assertThrown;
1914 import std.format : FormatException;
1916 auto aa = [ 1 : "x", 2 : "y", 3 : "z" ];
1918 assertThrown!FormatException(format("%(%)", aa));
1919 assertThrown!FormatException(format("%(%s%)", aa));
1920 assertThrown!FormatException(format("%(%s%s%s%)", aa));
1925 import std.exception : assertThrown;
1926 import std.format : FormatException;
1928 auto aa = [ 1 : "x", 2 : "y", 3 : "z" ];
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));
1937 // https://issues.dlang.org/show_bug.cgi?id=21808
1940 auto spelled = [ 1 : "one" ];
1941 assert(format("%-(%2$s (%1$s)%|, %)", spelled) == "one (1)");
1944 auto result = format("%-(%2$s (%1$s)%|, %)", spelled);
1945 assert(result == "one (1), two (2)" || result == "two (2), one (1)");
1948 enum HasToStringResult
1953 inCharSinkFormatString,
1954 inCharSinkFormatSpec,
1956 constCharSinkFormatString,
1957 constCharSinkFormatSpec,
1959 customPutWriterFormatSpec,
1962 private enum hasPreviewIn = !is(typeof(mixin(q{(in ref int a) => a})));
1964 template hasToString(T, Char)
1966 static if (isPointer!T)
1968 // X* does not have toString, even if X is aggregate type has toString.
1969 enum hasToString = HasToStringResult.none;
1971 else static if (is(typeof(
1974 const FormatSpec!Char f;
1975 static struct S {void put(scope Char s){}}
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");
1984 enum hasToString = HasToStringResult.customPutWriterFormatSpec;
1986 else static if (is(typeof(
1989 static struct S {void put(scope Char s){}}
1992 static assert(!__traits(compiles, val.toString(S())),
1993 "force toString to take parameters by ref");
1996 enum hasToString = HasToStringResult.customPutWriter;
1998 else static if (is(typeof({ T val = void; FormatSpec!Char f; val.toString((scope const(char)[] s){}, f); })))
2000 enum hasToString = HasToStringResult.constCharSinkFormatSpec;
2002 else static if (is(typeof({ T val = void; val.toString((scope const(char)[] s){}, "%s"); })))
2004 enum hasToString = HasToStringResult.constCharSinkFormatString;
2006 else static if (is(typeof({ T val = void; val.toString((scope const(char)[] s){}); })))
2008 enum hasToString = HasToStringResult.constCharSink;
2011 else static if (hasPreviewIn &&
2012 is(typeof({ T val = void; FormatSpec!Char f; val.toString((in char[] s){}, f); })))
2014 enum hasToString = HasToStringResult.inCharSinkFormatSpec;
2016 else static if (hasPreviewIn &&
2017 is(typeof({ T val = void; val.toString((in char[] s){}, "%s"); })))
2019 enum hasToString = HasToStringResult.inCharSinkFormatString;
2021 else static if (hasPreviewIn &&
2022 is(typeof({ T val = void; val.toString((in char[] s){}); })))
2024 enum hasToString = HasToStringResult.inCharSink;
2027 else static if (is(typeof({ T val = void; return val.toString(); }()) S) && isSomeString!S)
2029 enum hasToString = HasToStringResult.hasSomeToString;
2033 enum hasToString = HasToStringResult.none;
2039 import std.range.primitives : isOutputRange;
2043 void toString(Writer)(ref Writer w)
2044 if (isOutputRange!(Writer, string))
2049 void toString(scope void delegate(scope const(char)[]) sink, scope FormatSpec!char fmt) {}
2053 void toString(scope void delegate(scope const(char)[]) sink, string fmt) {}
2057 void toString(scope void delegate(scope const(char)[]) sink) {}
2061 string toString() {return "";}
2065 void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt)
2066 if (isOutputRange!(Writer, string))
2071 string toString() {return "";}
2072 void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, string)) {}
2076 string toString() {return "";}
2077 void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt)
2078 if (isOutputRange!(Writer, string))
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))
2090 string toString() {return "";}
2091 void toString(Writer)(ref Writer w, scope ref FormatSpec!char fmt)
2092 if (isOutputRange!(Writer, string))
2097 void toString(Writer)(Writer w, scope const ref FormatSpec!char fmt)
2098 if (isOutputRange!(Writer, string))
2103 void toString(Writer)(ref Writer w, scope const FormatSpec!char fmt)
2104 if (isOutputRange!(Writer, string))
2109 void toString(scope void delegate(in char[]) sink, in FormatSpec!char fmt) {}
2113 void toString(scope void delegate(in char[]) sink, string fmt) {}
2117 void toString(scope void delegate(in char[]) sink) {}
2120 with(HasToStringResult)
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)
2136 static assert(hasToString!(M, char) == inCharSinkFormatSpec);
2137 static assert(hasToString!(N, char) == inCharSinkFormatString);
2138 static assert(hasToString!(O, char) == inCharSink);
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))
2147 import std.format : NoOpSink;
2148 import std.range.primitives : put;
2150 enum overload = hasToString!(T, Char);
2152 enum noop = is(Writer == NoOpSink);
2154 static if (overload == HasToStringResult.customPutWriterFormatSpec)
2156 static if (!noop) val.toString(w, f);
2158 else static if (overload == HasToStringResult.customPutWriter)
2160 static if (!noop) val.toString(w);
2162 else static if (overload == HasToStringResult.constCharSinkFormatSpec)
2164 static if (!noop) val.toString((scope const(char)[] s) { put(w, s); }, f);
2166 else static if (overload == HasToStringResult.constCharSinkFormatString)
2168 static if (!noop) val.toString((scope const(char)[] s) { put(w, s); }, f.getCurFmtStr());
2170 else static if (overload == HasToStringResult.constCharSink)
2172 static if (!noop) val.toString((scope const(char)[] s) { put(w, s); });
2174 else static if (overload == HasToStringResult.inCharSinkFormatSpec)
2176 static if (!noop) val.toString((in char[] s) { put(w, s); }, f);
2178 else static if (overload == HasToStringResult.inCharSinkFormatString)
2180 static if (!noop) val.toString((in char[] s) { put(w, s); }, f.getCurFmtStr());
2182 else static if (overload == HasToStringResult.inCharSink)
2184 static if (!noop) val.toString((in char[] s) { put(w, s); });
2186 else static if (overload == HasToStringResult.hasSomeToString)
2188 static if (!noop) put(w, val.toString());
2192 static assert(0, "No way found to format " ~ T.stringof ~ " as string");
2198 import std.exception : assertThrown;
2199 import std.format : FormatException;
2201 static interface IF1 { }
2202 class CIF1 : IF1 { }
2203 static struct SF1 { }
2204 static union UF1 { }
2205 static class CF1 { }
2207 static interface IF2 { string toString(); }
2208 static class CIF2 : IF2 { override string toString() { return ""; } }
2209 static struct SF2 { string toString() { return ""; } }
2210 static union UF2 { string toString() { return ""; } }
2211 static class CF2 { override string toString() { return ""; } }
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"); } }
2220 static union KU1 { void toString(scope void delegate(scope const(char)[]) sink,
2221 FormatSpec!char) const { sink("KU1"); } }
2223 static class KC1 { void toString(scope void delegate(scope const(char)[]) sink,
2224 FormatSpec!char) const { sink("KC1"); } }
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()));
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()));
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");
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))
2251 import std.range.primitives : put;
2253 enforceValidFormatSpec!(T, Char)(f);
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`");
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)
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))
2275 else static if (is(T == shared))
2278 put(w, typeid(Unqual!T).name);
2281 else static if (overload.among(constCharSink, constCharSinkFormatString, constCharSinkFormatSpec) ||
2282 (!isInputRange!T && !is(BuiltinTypeOf!T)))
2284 formatObject!(Writer, T, Char)(w, val, f);
2288 static if (!is(__traits(parent, T.toString) == Object)) // not inherited Object.toString
2290 formatObject(w, val, f);
2292 else static if (isInputRange!T)
2294 formatRange(w, val, f);
2296 else static if (is(BuiltinTypeOf!T X))
2299 formatValueImpl(w, x, f);
2303 formatObject(w, val, f);
2311 import std.array : appender;
2312 import std.range.interfaces : inputRangeObject;
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]");
2319 formatTest(c, "null");
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.
2328 // Enable the use of custom toString that gets a sink delegate
2329 // for class formatting.
2331 enum inputRangeCode =
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 .. $]; }
2342 mixin(inputRangeCode);
2343 void toString(scope void delegate(scope const(char)[]) dg,
2344 scope const ref FormatSpec!char f) const
2351 mixin(inputRangeCode);
2352 void toString(scope void delegate(const(char)[]) dg, string f) const { dg("[012]"); }
2356 mixin(inputRangeCode);
2357 void toString(scope void delegate(const(char)[]) dg) const { dg("[012]"); }
2361 mixin(inputRangeCode);
2362 override string toString() const { return "[012]"; }
2366 mixin(inputRangeCode);
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]");
2376 // outside the unittest block, otherwise the FQN of the
2377 // class contains the line number of the unittest
2378 version (StdUnittest)
2383 // https://issues.dlang.org/show_bug.cgi?id=7879
2387 auto s = format("%s", c);
2388 assert(s == "null");
2390 immutable(C) c2 = new C();
2391 s = format("%s", c2);
2392 assert(s == "immutable(std.format.internal.write.C)");
2394 const(C) c3 = new C();
2395 s = format("%s", c3);
2396 assert(s == "const(std.format.internal.write.C)");
2398 shared(C) c4 = new C();
2399 s = format("%s", c4);
2400 assert(s == "shared(std.format.internal.write.C)");
2403 // https://issues.dlang.org/show_bug.cgi?id=7879
2408 override string toString() const @safe
2415 auto s = format("%s", c);
2416 assert(s == "null");
2418 const(F) c2 = new F();
2419 s = format("%s", c2);
2420 assert(s == "Foo", s);
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))
2426 import std.range.primitives : put;
2428 enforceValidFormatSpec!(T, Char)(f);
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`");
2437 static if (hasToString!(T, Char) != HasToStringResult.none)
2439 formatObject(w, val, f);
2441 else static if (isInputRange!T)
2443 formatRange(w, val, f);
2449 import core.sys.windows.com : IUnknown;
2450 static if (is(T : IUnknown))
2452 formatValueImpl(w, *cast(void**)&val, f);
2456 formatValueImpl(w, cast(Object) val, f);
2461 formatValueImpl(w, cast(Object) val, f);
2469 import std.range.interfaces : InputRange, inputRangeObject;
2472 InputRange!int i = inputRangeObject([1,2,3,4]);
2473 formatTest(i, "[1, 2, 3, 4]");
2476 formatTest(i, "null");
2478 // interface (downcast to Object)
2479 interface Whatever {}
2482 override @property string toString() const { return "ab"; }
2484 Whatever val = new C;
2485 formatTest(val, "ab");
2487 // https://issues.dlang.org/show_bug.cgi?id=11175
2490 import core.sys.windows.com : IID, IUnknown;
2491 import core.sys.windows.windef : HRESULT;
2493 interface IUnknown2 : IUnknown { }
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; }
2502 IUnknown2 d = new D;
2503 string expected = format("%X", cast(void*) d);
2504 formatTest(d, expected);
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))
2514 import std.range.primitives : put;
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`");
2520 enforceValidFormatSpec!(T, Char)(f);
2521 static if (hasToString!(T, Char))
2523 formatObject(w, val, f);
2525 else static if (isInputRange!T)
2527 formatRange(w, val, f);
2529 else static if (is(T == struct))
2531 enum left = T.stringof~"(";
2532 enum separator = ", ";
2536 foreach (i, e; val.tupleof)
2538 static if (__traits(identifier, val.tupleof[i]) == "this")
2540 else static if (0 < i && val.tupleof[i-1].offsetof == val.tupleof[i].offsetof)
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 .. $]~"}");
2545 put(w, separator~val.tupleof[i].stringof[4 .. $]);
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 .. $]);
2553 formatElement(w, e, f);
2564 // https://issues.dlang.org/show_bug.cgi?id=9588
2567 struct S { int x; bool empty() { return false; } }
2568 formatTest(S(), "S(0)");
2571 // https://issues.dlang.org/show_bug.cgi?id=4638
2574 struct U8 { string toString() const { return "blah"; } }
2575 struct U16 { wstring toString() const { return "blah"; } }
2576 struct U32 { dstring toString() const { return "blah"; } }
2577 formatTest(U8(), "blah");
2578 formatTest(U16(), "blah");
2579 formatTest(U32(), "blah");
2582 // https://issues.dlang.org/show_bug.cgi?id=3890
2585 struct Int{ int n; }
2586 struct Pair{ string s; Int i; }
2587 formatTest(Pair("hello", Int(5)),
2588 `Pair("hello", Int(5))`);
2591 // https://issues.dlang.org/show_bug.cgi?id=9117
2594 import std.format : formattedWrite;
2596 static struct Frop {}
2602 T opCast(T) () if (is(T == Frop))
2622 const(char)[] result;
2623 void put(scope const char[] s) { result ~= s; }
2626 formattedWrite(&put, "%s", foo); // OK
2627 assert(result == "Foo");
2632 formattedWrite(&put, "%s", bar); // NG
2633 assert(result == "Bar");
2638 formattedWrite(&put, "%s", 9);
2639 assert(result == "9");
2644 // union formatting without toString
2651 formatTest(u1, "U1");
2653 // union formatting with toString
2658 string toString() const { return s; }
2661 () @trusted { u2.s = "hello"; } ();
2662 formatTest(u2, "hello");
2667 import std.array : appender;
2668 import std.format : formatValue;
2670 // https://issues.dlang.org/show_bug.cgi?id=7230
2671 static struct Bug7230
2686 auto w = appender!(char[])();
2687 formatValue(w, bug, f);
2688 assert(w.data == `Bug7230("hello", #{overlap a, b, c}, 10)`);
2693 import std.array : appender;
2694 import std.format : formatValue;
2696 static struct S{ @disable this(this); }
2700 auto w = appender!string();
2701 formatValue(w, s, f);
2702 assert(w.data == "S()");
2707 import std.array : appender;
2708 import std.format : formatValue;
2710 //struct Foo { @disable string toString(); }
2713 interface Bar { @disable string toString(); }
2716 auto w = appender!(char[])();
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)));
2725 // https://issues.dlang.org/show_bug.cgi?id=21722
2730 void toString (scope void delegate (scope const(char)[]) sink, string fmt)
2737 auto result = () @trusted { return format("%b", b); } ();
2738 assert(result == "Hello");
2740 static if (hasPreviewIn)
2744 void toString(scope void delegate(in char[]) sink, in FormatSpec!char fmt)
2751 assert(format("%b", f) == "Hello");
2755 void toString(scope void delegate(in char[]) sink, string fmt)
2762 assert(format("%b", f2) == "Hello");
2768 import std.array : appender;
2769 import std.format : singleSpec;
2771 // Bug #17269. Behavior similar to `struct A { Nullable!string B; }`
2772 struct StringAliasThis
2774 @property string value() const { assert(0); }
2776 string toString() { return "helloworld"; }
2777 private string _value;
2779 struct TestContainer
2781 StringAliasThis testVar;
2784 auto w = appender!string();
2785 auto spec = singleSpec("%s");
2786 formatElement(w, TestContainer(), spec);
2788 assert(w.data == "TestContainer(helloworld)", w.data);
2791 // https://issues.dlang.org/show_bug.cgi?id=17269
2794 import std.typecons : Nullable;
2798 Nullable!string bar;
2802 formatTest(f, "Foo(Nullable.null)");
2805 // https://issues.dlang.org/show_bug.cgi?id=19003
2814 invariant { assert(this.i); }
2816 this(int i) @safe in { assert(i); } do { this.i = i; }
2818 string toString() { return "S"; }
2826 void enforceValidFormatSpec(T, Char)(scope const ref FormatSpec!Char f)
2828 import std.format : enforceFmt;
2829 import std.range : isInputRange;
2830 import std.format.internal.write : hasToString, HasToStringResult;
2832 enum overload = hasToString!(T, Char);
2834 overload != HasToStringResult.constCharSinkFormatSpec &&
2835 overload != HasToStringResult.constCharSinkFormatString &&
2836 overload != HasToStringResult.inCharSinkFormatSpec &&
2837 overload != HasToStringResult.inCharSinkFormatString &&
2838 overload != HasToStringResult.customPutWriterFormatSpec &&
2841 enforceFmt(f.spec == 's',
2842 "Expected '%s' format specifier for type '" ~ T.stringof ~ "'");
2847 `enum`s are formatted like their base value
2849 void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f)
2852 import std.array : appender;
2853 import std.range.primitives : put;
2857 foreach (i, e; EnumMembers!T)
2861 formatValueImpl(w, __traits(allMembers, T)[i], f);
2866 auto w2 = appender!string();
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);
2873 FormatSpec!Char f2 = f;
2875 formatValueImpl(w2, cast(OriginalType!T) val, f2);
2876 writeAligned(w, w2.data, f);
2879 formatValueImpl(w, cast(OriginalType!T) val, f);
2884 enum A { first, second, third }
2885 formatTest(A.second, "second");
2886 formatTest(cast(A) 72, "cast(A)72");
2890 enum A : string { one = "uno", two = "dos", three = "tres" }
2891 formatTest(A.three, "three");
2892 formatTest(cast(A)"mill\ón", "cast(A)mill\ón");
2896 enum A : bool { no, yes }
2897 formatTest(A.yes, "yes");
2898 formatTest(A.no, "no");
2902 // Test for bug 6892
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");
2915 enum A { one, two, three }
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
2923 // https://issues.dlang.org/show_bug.cgi?id=8921
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]");
2930 E[] e2 = [E.A, E.B, E.C];
2931 formatTest(e2, "[A, B, C]");
2935 Pointers are formatted as hex integers.
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))
2940 static if (is(typeof({ shared const void* p = val; })))
2941 alias SharedOf(T) = shared(T);
2943 alias SharedOf(T) = T;
2945 const SharedOf!(void*) p = val;
2946 const pnum = () @trusted { return cast(ulong) p; }();
2952 writeAligned(w, "null", f);
2955 FormatSpec!Char fs = f; // fs is copy for change its values.
2957 formatValueImpl(w, pnum, fs);
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);
2972 string t1 = format("[%6s] [%-6s]", p, p);
2973 assert(t1 == "[ null] [null ]");
2979 formatTest(p, "null");
2981 auto q = () @trusted { return cast(void*) 0xFFEECCAA; }();
2982 formatTest(q, "FFEECCAA");
2985 // https://issues.dlang.org/show_bug.cgi?id=11782
2988 import std.range : iota;
2990 auto a = iota(0, 10);
2991 auto b = iota(0, 10);
2992 auto p = () @trusted { auto p = &a; return p; }();
2994 assert(format("%s",p) != format("%s",b));
2999 // Test for https://issues.dlang.org/show_bug.cgi?id=7869
3002 string toString() const { return ""; }
3005 formatTest(p, "null");
3007 S* q = () @trusted { return cast(S*) 0xFFEECCAA; } ();
3008 formatTest(q, "FFEECCAA");
3011 // https://issues.dlang.org/show_bug.cgi?id=8186
3017 this() { a = new int; }
3020 formatTest(B.init, "null");
3023 // https://issues.dlang.org/show_bug.cgi?id=9336
3024 @system pure unittest
3030 // https://issues.dlang.org/show_bug.cgi?id=11778
3033 import std.exception : assertThrown;
3034 import std.format : FormatException;
3037 assertThrown!FormatException(format("%d", p));
3038 assertThrown!FormatException(format("%04d", () @trusted { return p + 2; } ()));
3041 // https://issues.dlang.org/show_bug.cgi?id=12505
3045 formatTest("%08X", p, "00000000");
3049 SIMD vectors are formatted as arrays.
3051 void formatValueImpl(Writer, V, Char)(auto ref Writer w, V val, scope const ref FormatSpec!Char f)
3054 formatValueImpl(w, val.array, f);
3059 import core.simd; // cannot be selective, because float4 might not be defined
3061 static if (is(float4))
3065 version (OSX) {/* https://issues.dlang.org/show_bug.cgi?id=17823 */}
3074 formatTest(f, "[1, 2, 3, 4]");
3080 Delegates are formatted by `ReturnType delegate(Parameters) FunctionAttributes`
3082 Known bug: Because of issue https://issues.dlang.org/show_bug.cgi?id=18269
3083 the FunctionAttributes might be wrong.
3085 void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope T, scope const ref FormatSpec!Char f)
3088 formatValueImpl(w, T.stringof, f);
3093 import std.array : appender;
3094 import std.format : formatValue;
3096 void func() @system { __gshared int x; ++x; throw new Exception("msg"); }
3100 auto w = appender!string();
3101 formatValue(w, &func, f);
3102 assert(w.data.length >= 15 && w.data[0 .. 15] == "void delegate()");
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))
3110 import std.array : appender;
3111 import std.format.write : formattedWrite, formatValue;
3112 import std.range.primitives : put;
3113 import std.utf : decode, UTFException;
3115 StringTypeOf!T str = val; // https://issues.dlang.org/show_bug.cgi?id=8015
3121 // ignore other specifications and quote
3122 for (size_t i = 0; i < str.length; )
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)
3131 for (size_t i = 0; i < str.length; )
3133 auto c = decode(str, i);
3134 formatChar(w, c, '"');
3139 catch (UTFException)
3143 // If val contains invalid UTF sequence, formatted like HexString literal
3145 static if (is(typeof(str[0]) : const(char)))
3148 alias IntArr = const(ubyte)[];
3150 else static if (is(typeof(str[0]) : const(wchar)))
3153 alias IntArr = const(ushort)[];
3155 else static if (is(typeof(str[0]) : const(dchar)))
3158 alias IntArr = const(uint)[];
3160 formattedWrite(w, "[%(cast(" ~ type ~ "char) 0x%X%|, %)]", cast(IntArr) str);
3163 formatValue(w, str, f);
3168 import std.array : appender;
3169 import std.format.spec : singleSpec;
3171 auto w = appender!string();
3172 auto spec = singleSpec("%s");
3173 formatElement(w, "Hello World", spec);
3175 assert(w.data == "\"Hello World\"");
3180 import std.array : appender;
3181 import std.format.spec : singleSpec;
3183 auto w = appender!string();
3184 auto spec = singleSpec("%s");
3185 formatElement(w, "H", spec);
3187 assert(w.data == "\"H\"", w.data);
3190 // https://issues.dlang.org/show_bug.cgi?id=15888
3193 import std.array : appender;
3194 import std.format.spec : singleSpec;
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]`);
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]`);
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))
3213 import std.range.primitives : put;
3214 import std.format.write : formatValue;
3219 formatChar(w, val, '\'');
3223 formatValue(w, val, f);
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))
3230 import std.format.write : formatValue;
3232 formatValue(w, val, f);
3235 // Fix for https://issues.dlang.org/show_bug.cgi?id=1591
3236 int getNthInt(string kind, A...)(uint index, A args)
3238 return getNth!(kind, isIntegral, int)(index, args);
3241 T getNth(string kind, alias Condition, T, A...)(uint index, A args)
3243 import std.conv : text, to;
3244 import std.format : FormatException;
3251 static if (Condition!(typeof(args[n])))
3253 return to!T(args[n]);
3257 throw new FormatException(
3258 text(kind, " expected, not ", typeof(args[n]).stringof,
3259 " for argument #", index + 1));
3263 throw new FormatException(text("Missing ", kind, " argument"));
3267 private bool needToSwapEndianess(Char)(scope const ref FormatSpec!Char f)
3269 import std.system : endian, Endian;
3271 return endian == Endian.littleEndian && f.flPlus
3272 || endian == Endian.bigEndian && f.flDash;
3275 void writeAligned(Writer, T, Char)(auto ref Writer w, T s, scope const ref FormatSpec!Char f)
3278 FormatSpec!Char fs = f;
3280 writeAligned(w, "", "", s, fs);
3285 import std.array : appender;
3286 import std.format : singleSpec;
3288 auto w = appender!string();
3289 auto spec = singleSpec("%s");
3290 writeAligned(w, "a本Ä", spec);
3291 assert(w.data == "a本Ä", w.data);
3296 import std.array : appender;
3297 import std.format : singleSpec;
3299 auto w = appender!string();
3300 auto spec = singleSpec("%10s");
3301 writeAligned(w, "a本Ä", spec);
3302 assert(w.data == " a本Ä", "|" ~ w.data ~ "|");
3307 import std.array : appender;
3308 import std.format : singleSpec;
3310 auto w = appender!string();
3311 auto spec = singleSpec("%-10s");
3312 writeAligned(w, "a本Ä", spec);
3313 assert(w.data == "a本Ä ", w.data);
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)
3329 writeAligned(w, prefix, grouped, "", suffix, f,
3330 integer_precision ? PrecisionType.integer : PrecisionType.none);
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)
3338 // writes: left padding, prefix, leading zeros, grouped, fracts, suffix, right padding
3340 if (p == PrecisionType.integer && f.precision == f.UNSPECIFIED)
3341 p = PrecisionType.none;
3343 import std.range.primitives : put;
3346 long groupedWidth = grouped.length; // TODO: does not take graphemes into account
3347 long fractsWidth = fracts.length; // TODO: does not take graphemes into account
3350 // TODO: remove this workaround which hides issue 21815
3353 prefixWidth = getWidth(prefix);
3354 suffixWidth = getWidth(suffix);
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;
3364 long trailingZeros = 0;
3365 if (p == PrecisionType.fractionalDigits)
3366 trailingZeros = f.precision - (fractsWidth - 1);
3367 if (p == PrecisionType.allDigits && f.flHash)
3370 trailingZeros = f.precision - (fractsWidth - 1) - groupedWidth;
3373 trailingZeros = f.precision - fractsWidth;
3374 foreach (i;0 .. fracts.length)
3375 if (fracts[i] != '0' && fracts[i] != '.')
3377 trailingZeros = f.precision - (fracts.length - i);
3383 auto nodot = fracts == "." && trailingZeros == 0 && !f.flHash;
3385 if (nodot) fractsWidth = 0;
3387 long width = prefixWidth + sepCount + groupedWidth + fractsWidth + trailingZeros + suffixWidth;
3388 long delta = f.width - width;
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)
3395 pregrouped = f.precision - groupedWidth;
3396 delta -= pregrouped;
3399 front = ((front - 1) + pregrouped) % f.separators + 1;
3400 delta -= (f.precision - 1) / f.separators - sepCount;
3405 if ((!f.flZero || p == PrecisionType.integer) && delta > 0)
3409 foreach (i ; 0 .. delta / 2 + ((delta % 2 == 1 && !f.flDash) ? 1 : 0))
3414 foreach (i ; 0 .. delta)
3422 // leading grouped zeros
3423 if (f.flZero && p != PrecisionType.integer && !f.flDash && delta > 0)
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);
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; }
3437 foreach (i ; 0 .. delta)
3441 put(w, f.separatorChar);
3442 front2 = f.separators;
3449 // separator between zeros and grouped
3450 if (front == f.separators)
3451 put(w, f.separatorChar);
3454 foreach (i ; 0 .. delta)
3461 // TODO: this does not take graphemes into account
3462 foreach (i;0 .. pregrouped + grouped.length)
3466 put(w, f.separatorChar);
3467 front = f.separators;
3471 put(w, i < pregrouped ? '0' : grouped[cast(size_t) (i - pregrouped)]);
3476 foreach (i;0 .. pregrouped)
3486 foreach (i ; 0 .. trailingZeros)
3497 foreach (i ; 0 .. delta / 2 + ((delta % 2 == 1 && f.flDash) ? 1 : 0))
3502 foreach (i ; 0 .. delta)
3510 import std.array : appender;
3511 import std.format : singleSpec;
3513 auto w = appender!string();
3514 auto spec = singleSpec("%s");
3515 writeAligned(w, "pre", "grouping", "suf", spec);
3516 assert(w.data == "pregroupingsuf", w.data);
3518 w = appender!string();
3519 spec = singleSpec("%20s");
3520 writeAligned(w, "pre", "grouping", "suf", spec);
3521 assert(w.data == " pregroupingsuf", w.data);
3523 w = appender!string();
3524 spec = singleSpec("%-20s");
3525 writeAligned(w, "pre", "grouping", "suf", spec);
3526 assert(w.data == "pregroupingsuf ", w.data);
3528 w = appender!string();
3529 spec = singleSpec("%020s");
3530 writeAligned(w, "pre", "grouping", "suf", spec);
3531 assert(w.data == "pre000000groupingsuf", w.data);
3533 w = appender!string();
3534 spec = singleSpec("%-020s");
3535 writeAligned(w, "pre", "grouping", "suf", spec);
3536 assert(w.data == "pregroupingsuf ", w.data);
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);
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);
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);
3553 w = appender!string();
3554 spec = singleSpec("%20,10s");
3555 writeAligned(w, "pre", "grouping", "suf", spec);
3556 assert(w.data == " pregroupingsuf", w.data);
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);
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);
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);
3573 w = appender!string();
3574 spec = singleSpec("%020,10s");
3575 writeAligned(w, "pre", "grouping", "suf", spec);
3576 assert(w.data == "pre000,00groupingsuf", w.data);
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);
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);
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);
3595 w = appender!string();
3596 spec = singleSpec("%,3s");
3597 writeAligned(w, "pre", "grouping", "suf", spec);
3598 assert(w.data == "pregr,oup,ingsuf", w.data);
3603 import std.array : appender;
3604 import std.format : singleSpec;
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);
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);
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);
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);
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);
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) ~ "'");
3646 private long getWidth(T)(T s)
3648 import std.algorithm.searching : all;
3649 import std.uni : graphemeStride;
3651 // check for non-ascii character
3652 if (s.all!(a => a <= 0x7F)) return s.length;
3654 //TODO: optimize this
3656 for (size_t i; i < s.length; i += graphemeStride(s, i))
3661 enum RoundingClass { ZERO, LOWER, FIVE, UPPER }
3662 enum RoundingMode { up, down, toZero, toNearestTiesToEven, toNearestTiesAwayFromZero }
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)
3668 in (right <= sequence.length)
3670 in (max == '9' || max == 'f' || max == 'F')
3672 import std.math.hardware;
3674 auto mode = RoundingMode.toNearestTiesToEven;
3678 // std.math's FloatingPointControl isn't available on all target platforms
3679 static if (is(FloatingPointControl))
3681 switch (FloatingPointControl.rounding)
3683 case FloatingPointControl.roundUp:
3684 mode = RoundingMode.up;
3686 case FloatingPointControl.roundDown:
3687 mode = RoundingMode.down;
3689 case FloatingPointControl.roundToZero:
3690 mode = RoundingMode.toZero;
3692 case FloatingPointControl.roundToNearest:
3693 mode = RoundingMode.toNearestTiesToEven;
3695 default: assert(false, "Unknown floating point rounding mode");
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)
3709 roundUp = type == RoundingClass.UPPER;
3711 if (type == RoundingClass.FIVE)
3713 // IEEE754 allows for two different ways of implementing roundToNearest:
3715 if (mode == RoundingMode.toNearestTiesAwayFromZero)
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);
3727 if (!roundUp) return false;
3729 foreach_reverse (i;left .. right)
3731 if (sequence[i] == '.') continue;
3732 if (sequence[i] == max)
3736 if (max != '9' && sequence[i] == '9')
3737 sequence[i] = max == 'f' ? 'a' : 'A';
3744 sequence[left - 1] = '1';
3755 assert(round(c, left, right, RoundingClass.UPPER, false) == true);
3756 assert(c[4 .. 8] == "1.00");
3759 assert(round(c, left, right, RoundingClass.FIVE, false) == true);
3760 assert(c[4 .. 8] == "1.00");
3763 assert(round(c, left, right, RoundingClass.LOWER, false) == false);
3764 assert(c[4 .. 8] == "x.99");
3767 assert(round(c, left, right, RoundingClass.ZERO, false) == false);
3768 assert(c[4 .. 8] == "x.99");
3770 import std.math.hardware;
3771 static if (is(FloatingPointControl))
3773 FloatingPointControl fpctrl;
3775 fpctrl.rounding = FloatingPointControl.roundUp;
3778 assert(round(c, left, right, RoundingClass.UPPER, false) == true);
3779 assert(c[4 .. 8] == "1.00");
3782 assert(round(c, left, right, RoundingClass.FIVE, false) == true);
3783 assert(c[4 .. 8] == "1.00");
3786 assert(round(c, left, right, RoundingClass.LOWER, false) == true);
3787 assert(c[4 .. 8] == "1.00");
3790 assert(round(c, left, right, RoundingClass.ZERO, false) == false);
3791 assert(c[4 .. 8] == "x.99");
3793 fpctrl.rounding = FloatingPointControl.roundDown;
3796 assert(round(c, left, right, RoundingClass.UPPER, false) == false);
3797 assert(c[4 .. 8] == "x.99");
3800 assert(round(c, left, right, RoundingClass.FIVE, false) == false);
3801 assert(c[4 .. 8] == "x.99");
3804 assert(round(c, left, right, RoundingClass.LOWER, false) == false);
3805 assert(c[4 .. 8] == "x.99");
3808 assert(round(c, left, right, RoundingClass.ZERO, false) == false);
3809 assert(c[4 .. 8] == "x.99");
3811 fpctrl.rounding = FloatingPointControl.roundToZero;
3814 assert(round(c, left, right, RoundingClass.UPPER, false) == false);
3815 assert(c[4 .. 8] == "x.99");
3818 assert(round(c, left, right, RoundingClass.FIVE, false) == false);
3819 assert(c[4 .. 8] == "x.99");
3822 assert(round(c, left, right, RoundingClass.LOWER, false) == false);
3823 assert(c[4 .. 8] == "x.99");
3826 assert(round(c, left, right, RoundingClass.ZERO, false) == false);
3827 assert(c[4 .. 8] == "x.99");
3838 assert(round(c, left, right, RoundingClass.UPPER, true) == false);
3839 assert(c[4 .. 8] == "x8.6");
3842 assert(round(c, left, right, RoundingClass.FIVE, true) == false);
3843 assert(c[4 .. 8] == "x8.6");
3846 assert(round(c, left, right, RoundingClass.FIVE, true) == false);
3847 assert(c[4 .. 8] == "x8.4");
3850 assert(round(c, left, right, RoundingClass.LOWER, true) == false);
3851 assert(c[4 .. 8] == "x8.5");
3854 assert(round(c, left, right, RoundingClass.ZERO, true) == false);
3855 assert(c[4 .. 8] == "x8.5");
3857 import std.math.hardware;
3858 static if (is(FloatingPointControl))
3860 FloatingPointControl fpctrl;
3862 fpctrl.rounding = FloatingPointControl.roundUp;
3865 assert(round(c, left, right, RoundingClass.UPPER, true) == false);
3866 assert(c[4 .. 8] == "x8.5");
3869 assert(round(c, left, right, RoundingClass.FIVE, true) == false);
3870 assert(c[4 .. 8] == "x8.5");
3873 assert(round(c, left, right, RoundingClass.LOWER, true) == false);
3874 assert(c[4 .. 8] == "x8.5");
3877 assert(round(c, left, right, RoundingClass.ZERO, true) == false);
3878 assert(c[4 .. 8] == "x8.5");
3880 fpctrl.rounding = FloatingPointControl.roundDown;
3883 assert(round(c, left, right, RoundingClass.UPPER, true) == false);
3884 assert(c[4 .. 8] == "x8.6");
3887 assert(round(c, left, right, RoundingClass.FIVE, true) == false);
3888 assert(c[4 .. 8] == "x8.6");
3891 assert(round(c, left, right, RoundingClass.LOWER, true) == false);
3892 assert(c[4 .. 8] == "x8.6");
3895 assert(round(c, left, right, RoundingClass.ZERO, true) == false);
3896 assert(c[4 .. 8] == "x8.5");
3898 fpctrl.rounding = FloatingPointControl.roundToZero;
3901 assert(round(c, left, right, RoundingClass.UPPER, true) == false);
3902 assert(c[4 .. 8] == "x8.5");
3905 assert(round(c, left, right, RoundingClass.FIVE, true) == false);
3906 assert(c[4 .. 8] == "x8.5");
3909 assert(round(c, left, right, RoundingClass.LOWER, true) == false);
3910 assert(c[4 .. 8] == "x8.5");
3913 assert(round(c, left, right, RoundingClass.ZERO, true) == false);
3914 assert(c[4 .. 8] == "x8.5");
3925 assert(round(c, left, right, RoundingClass.UPPER, true, 'f') == false);
3926 assert(c[4 .. 8] == "x8.a");
3929 assert(round(c, left, right, RoundingClass.UPPER, true, 'F') == false);
3930 assert(c[4 .. 8] == "x8.A");
3933 assert(round(c, left, right, RoundingClass.UPPER, true, 'f') == false);
3934 assert(c[4 .. 8] == "x9.0");
3937 version (StdUnittest)
3938 private void formatTest(T)(T val, string expected, size_t ln = __LINE__, string fn = __FILE__)
3940 formatTest(val, [expected], ln, fn);
3943 version (StdUnittest)
3944 private void formatTest(T)(string fmt, T val, string expected, size_t ln = __LINE__, string fn = __FILE__) @safe
3946 formatTest(fmt, val, [expected], ln, fn);
3949 version (StdUnittest)
3950 private void formatTest(T)(T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__)
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;
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);
3966 version (StdUnittest)
3967 private void formatTest(T)(string fmt, T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__) @safe
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;
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);