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