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