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