1 // Written in the D programming language.
2
3 /**
4 This is a submodule of $(MREF std, format).
5
6 It provides two functions for writing formatted output: $(LREF
7 formatValue) and $(LREF formattedWrite). The former writes a single
8 value. The latter writes several values at once, interspersed with
9 unformatted text.
10
11 The following combinations of format characters and types are
12 available:
13
14 $(BOOKTABLE ,
15 $(TR $(TH) $(TH s) $(TH c) $(TH d, u, b, o) $(TH x, X) $(TH e, E, f, F, g, G, a, A) $(TH r) $(TH compound))
16 $(TR $(TD `bool`) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH)))
17 $(TR $(TD `null`) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)))
18 $(TR $(TD $(I integer)) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD yes) $(TD yes) $(TD yes) $(TD $(MDASH)))
19 $(TR $(TD $(I floating point)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes) $(TD $(MDASH)))
20 $(TR $(TD $(I character)) $(TD yes) $(TD yes) $(TD yes) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH)))
21 $(TR $(TD $(I string)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes))
22 $(TR $(TD $(I array)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes))
23 $(TR $(TD $(I associative array)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes))
24 $(TR $(TD $(I pointer)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)))
25 $(TR $(TD $(I SIMD vectors)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes))
26 $(TR $(TD $(I delegates)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes))
27 )
28
29 Enums can be used with all format characters of the base type.
30
31 $(SECTION3 Structs$(COMMA) Unions$(COMMA) Classes$(COMMA) and Interfaces)
32
33 Aggregate types can define various `toString` functions. If this
34 function takes a $(REF_ALTTEXT FormatSpec, FormatSpec, std, format,
35 spec) or a $(I format string) as argument, the function decides
36 which format characters are accepted. If no `toString` is defined and
37 the aggregate is an $(REF_ALTTEXT input range, isInputRange, std,
38 range, primitives), it is treated like a range, that is $(B 's'), $(B
39 'r') and a compound specifier are accepted. In all other cases
40 aggregate types only accept $(B 's').
41
42 `toString` should have one of the following signatures:
43
44 ---
45 void toString(Writer, Char)(ref Writer w, const ref FormatSpec!Char fmt)
46 void toString(Writer)(ref Writer w)
47 string toString();
48 ---
49
50 Where `Writer` is an $(REF_ALTTEXT output range, isOutputRange,
51 std,range,primitives) which accepts characters $(LPAREN)of type
52 `Char` in the first version$(RPAREN). The template type does not have
53 to be called `Writer`.
54
55 Sometimes it's not possible to use a template, for example when
56 `toString` overrides `Object.toString`. In this case, the following
57 $(LPAREN)slower and less flexible$(RPAREN) functions can be used:
58
59 ---
60 void toString(void delegate(const(char)[]) sink, const ref FormatSpec!char fmt);
61 void toString(void delegate(const(char)[]) sink, string fmt);
62 void toString(void delegate(const(char)[]) sink);
63 ---
64
65 When several of the above `toString` versions are available, the
66 versions with `Writer` take precedence over the versions with a
67 `sink`. `string toString()` has the lowest priority.
68
69 If none of the above mentioned `toString` versions are available, the
70 aggregates will be formatted by other means, in the following
71 order:
72
73 If an aggregate is an $(REF_ALTTEXT input range, isInputRange, std,
74 range, primitives), it is formatted like an input range.
75
76 If an aggregate is a builtin type (using `alias this`), it is formatted
77 like the builtin type.
78
79 If all else fails, structs are formatted like `Type(field1, field2, ...)`,
80 classes and interfaces are formatted with their fully qualified name
81 and unions with their base name.
82
83 Copyright: Copyright The D Language Foundation 2000-2013.
84
85 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
86
87 Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com,
88 Andrei Alexandrescu), and Kenji Hara
89
90 Source: $(PHOBOSSRC std/format/write.d)
91 */
92 module std.format.write;
93
94 /**
95 `bool`s are formatted as `"true"` or `"false"` with `%s` and like the
96 `byte`s 1 and 0 with all other format characters.
97 */
98 @safe pure unittest
99 {
100 import std.array : appender;
101 import std.format.spec : singleSpec;
102
103 auto w1 = appender!string();
104 auto spec1 = singleSpec("%s");
105 formatValue(w1, true, spec1);
106
107 assert(w1.data == "true");
108
109 auto w2 = appender!string();
110 auto spec2 = singleSpec("%#x");
111 formatValue(w2, true, spec2);
112
113 assert(w2.data == "0x1");
114 }
115
116 /// The `null` literal is formatted as `"null"`.
117 @safe pure unittest
118 {
119 import std.array : appender;
120 import std.format.spec : singleSpec;
121
122 auto w = appender!string();
123 auto spec = singleSpec("%s");
124 formatValue(w, null, spec);
125
126 assert(w.data == "null");
127 }
128
129 /**
130 Integrals are formatted in (signed) every day notation with `%s` and
131 `%d` and as an (unsigned) image of the underlying bit representation
132 with `%b` (binary), `%u` (decimal), `%o` (octal), and `%x` (hexadecimal).
133 */
134 @safe pure unittest
135 {
136 import std.array : appender;
137 import std.format.spec : singleSpec;
138
139 auto w1 = appender!string();
140 auto spec1 = singleSpec("%d");
141 formatValue(w1, -1337, spec1);
142
143 assert(w1.data == "-1337");
144
145 auto w2 = appender!string();
146 auto spec2 = singleSpec("%x");
147 formatValue(w2, -1337, spec2);
148
149 assert(w2.data == "fffffac7");
150 }
151
152 /**
153 Floating-point values are formatted in natural notation with `%f`, in
154 scientific notation with `%e`, in short notation with `%g`, and in
155 hexadecimal scientific notation with `%a`. If a rounding mode is
156 available, they are rounded according to this rounding mode, otherwise
157 they are rounded to the nearest value, ties to even.
158 */
159 @safe unittest
160 {
161 import std.array : appender;
162 import std.format.spec : singleSpec;
163
164 auto w1 = appender!string();
165 auto spec1 = singleSpec("%.3f");
166 formatValue(w1, 1337.7779, spec1);
167
168 assert(w1.data == "1337.778");
169
170 auto w2 = appender!string();
171 auto spec2 = singleSpec("%.3e");
172 formatValue(w2, 1337.7779, spec2);
173
174 assert(w2.data == "1.338e+03");
175
176 auto w3 = appender!string();
177 auto spec3 = singleSpec("%.3g");
178 formatValue(w3, 1337.7779, spec3);
179
180 assert(w3.data == "1.34e+03");
181
182 auto w4 = appender!string();
183 auto spec4 = singleSpec("%.3a");
184 formatValue(w4, 1337.7779, spec4);
185
186 assert(w4.data == "0x1.4e7p+10");
187 }
188
189 /**
190 Individual characters (`char`, `wchar`, or `dchar`) are formatted as
191 Unicode characters with `%s` and `%c` and as integers (`ubyte`,
192 `ushort`, `uint`) with all other format characters. With
193 $(MREF_ALTTEXT compound specifiers, std,format) characters are
194 treated differently.
195 */
196 @safe pure unittest
197 {
198 import std.array : appender;
199 import std.format.spec : singleSpec;
200
201 auto w1 = appender!string();
202 auto spec1 = singleSpec("%c");
203 formatValue(w1, 'ì', spec1);
204
205 assert(w1.data == "ì");
206
207 auto w2 = appender!string();
208 auto spec2 = singleSpec("%#x");
209 formatValue(w2, 'ì', spec2);
210
211 assert(w2.data == "0xec");
212 }
213
214 /**
215 Strings are formatted as a sequence of characters with `%s`.
216 Non-printable characters are not escaped. With a compound specifier
217 the string is treated like a range of characters. With $(MREF_ALTTEXT
218 compound specifiers, std,format) strings are treated differently.
219 */
220 @safe pure unittest
221 {
222 import std.array : appender;
223 import std.format.spec : singleSpec;
224
225 auto w1 = appender!string();
226 auto spec1 = singleSpec("%s");
227 formatValue(w1, "hello", spec1);
228
229 assert(w1.data == "hello");
230
231 auto w2 = appender!string();
232 auto spec2 = singleSpec("%(%#x%|/%)");
233 formatValue(w2, "hello", spec2);
234
235 assert(w2.data == "0x68/0x65/0x6c/0x6c/0x6f");
236 }
237
238 /// Static arrays are formatted as dynamic arrays.
239 @safe pure unittest
240 {
241 import std.array : appender;
242 import std.format.spec : singleSpec;
243
244 auto w = appender!string();
245 auto spec = singleSpec("%s");
246 int[2] two = [1, 2];
247 formatValue(w, two, spec);
248
249 assert(w.data == "[1, 2]");
250 }
251
252 /**
253 Dynamic arrays are formatted as input ranges.
254 */
255 @safe pure unittest
256 {
257 import std.array : appender;
258 import std.format.spec : singleSpec;
259
260 auto w1 = appender!string();
261 auto spec1 = singleSpec("%s");
262 auto two = [1, 2];
263 formatValue(w1, two, spec1);
264
265 assert(w1.data == "[1, 2]");
266
267 auto w2 = appender!string();
268 auto spec2 = singleSpec("%(%g%|, %)");
269 auto consts = [3.1415926, 299792458, 6.67430e-11];
270 formatValue(w2, consts, spec2);
271
272 assert(w2.data == "3.14159, 2.99792e+08, 6.6743e-11");
273
274 // void[] is treated like ubyte[]
275 auto w3 = appender!string();
276 auto spec3 = singleSpec("%s");
277 void[] val = cast(void[]) cast(ubyte[])[1, 2, 3];
278 formatValue(w3, val, spec3);
279
280 assert(w3.data == "[1, 2, 3]");
281 }
282
283 /**
284 Associative arrays are formatted by using `':'` and `", "` as
285 separators, enclosed by `'['` and `']'` when used with `%s`. It's
286 also possible to use a compound specifier for better control.
287
288 Please note, that the order of the elements is not defined, therefore
289 the result of this function might differ.
290 */
291 @safe pure unittest
292 {
293 import std.array : appender;
294 import std.format.spec : singleSpec;
295
296 auto aa = [10:17.5, 20:9.99];
297
298 auto w1 = appender!string();
299 auto spec1 = singleSpec("%s");
300 formatValue(w1, aa, spec1);
301
302 assert(w1.data == "[10:17.5, 20:9.99]" || w1.data == "[20:9.99, 10:17.5]");
303
304 auto w2 = appender!string();
305 auto spec2 = singleSpec("%(%x = %.0e%| # %)");
306 formatValue(w2, aa, spec2);
307
308 assert(w2.data == "a = 2e+01 # 14 = 1e+01" || w2.data == "14 = 1e+01 # a = 2e+01");
309 }
310
311 /**
312 `enum`s are formatted as their name when used with `%s` and like
313 their base value else.
314 */
315 @safe pure unittest
316 {
317 import std.array : appender;
318 import std.format.spec : singleSpec;
319
320 enum A { first, second, third }
321
322 auto w1 = appender!string();
323 auto spec1 = singleSpec("%s");
324 formatValue(w1, A.second, spec1);
325
326 assert(w1.data == "second");
327
328 auto w2 = appender!string();
329 auto spec2 = singleSpec("%d");
330 formatValue(w2, A.second, spec2);
331
332 assert(w2.data == "1");
333
334 // values of an enum that have no name are formatted with %s using a cast
335 A a = A.third;
336 a++;
337
338 auto w3 = appender!string();
339 auto spec3 = singleSpec("%s");
340 formatValue(w3, a, spec3);
341
342 assert(w3.data == "cast(A)3");
343 }
344
345 /**
346 `structs`, `unions`, `classes` and `interfaces` can be formatted in
347 several different ways. The following example highlights `struct`
348 formatting, however, it applies to other aggregates as well.
349 */
350 @safe unittest
351 {
352 import std.array : appender;
353 import std.format.spec : FormatSpec, singleSpec;
354
355 // Using a `toString` with a writer
356 static struct Point1
357 {
358 import std.range.primitives : isOutputRange, put;
359
360 int x, y;
361
362 void toString(W)(ref W writer, scope const ref FormatSpec!char f)
363 if (isOutputRange!(W, char))
364 {
365 put(writer, "(");
366 formatValue(writer, x, f);
367 put(writer, ",");
368 formatValue(writer, y, f);
369 put(writer, ")");
370 }
371 }
372
373 auto w1 = appender!string();
374 auto spec1 = singleSpec("%s");
375 auto p1 = Point1(16, 11);
376
377 formatValue(w1, p1, spec1);
378 assert(w1.data == "(16,11)");
379
380 // Using a `toString` with a sink
381 static struct Point2
382 {
383 int x, y;
384
385 void toString(scope void delegate(scope const(char)[]) @safe sink,
386 scope const FormatSpec!char fmt) const
387 {
388 sink("(");
389 sink.formatValue(x, fmt);
390 sink(",");
391 sink.formatValue(y, fmt);
392 sink(")");
393 }
394 }
395
396 auto w2 = appender!string();
397 auto spec2 = singleSpec("%03d");
398 auto p2 = Point2(16,11);
399
400 formatValue(w2, p2, spec2);
401 assert(w2.data == "(016,011)");
402
403 // Using `string toString()`
404 static struct Point3
405 {
406 int x, y;
407
toStringPoint3408 string toString()
409 {
410 import std.conv : to;
411
412 return "(" ~ to!string(x) ~ "," ~ to!string(y) ~ ")";
413 }
414 }
415
416 auto w3 = appender!string();
417 auto spec3 = singleSpec("%s"); // has to be %s
418 auto p3 = Point3(16,11);
419
420 formatValue(w3, p3, spec3);
421 assert(w3.data == "(16,11)");
422
423 // without `toString`
424 static struct Point4
425 {
426 int x, y;
427 }
428
429 auto w4 = appender!string();
430 auto spec4 = singleSpec("%s"); // has to be %s
431 auto p4 = Point4(16,11);
432
433 formatValue(w4, p4, spec3);
434 assert(w4.data == "Point4(16, 11)");
435 }
436
437 /// Pointers are formatted as hexadecimal integers.
438 @safe pure unittest
439 {
440 import std.array : appender;
441 import std.format.spec : singleSpec;
442
443 auto w1 = appender!string();
444 auto spec1 = singleSpec("%s");
445 auto p1 = () @trusted { return cast(void*) 0xFFEECCAA; } ();
446 formatValue(w1, p1, spec1);
447
448 assert(w1.data == "FFEECCAA");
449
450 // null pointers are printed as `"null"` when used with `%s` and as hexadecimal integer else
451 auto w2 = appender!string();
452 auto spec2 = singleSpec("%s");
453 auto p2 = () @trusted { return cast(void*) 0x00000000; } ();
454 formatValue(w2, p2, spec2);
455
456 assert(w2.data == "null");
457
458 auto w3 = appender!string();
459 auto spec3 = singleSpec("%x");
460 formatValue(w3, p2, spec3);
461
462 assert(w3.data == "0");
463 }
464
465 /// SIMD vectors are formatted as arrays.
466 @safe unittest
467 {
468 import core.simd; // cannot be selective, because float4 might not be defined
469 import std.array : appender;
470 import std.format.spec : singleSpec;
471
472 auto w = appender!string();
473 auto spec = singleSpec("%s");
474
475 static if (is(float4))
476 {
version(X86)477 version (X86) {}
478 else
479 {
480 float4 f4;
481 f4.array[0] = 1;
482 f4.array[1] = 2;
483 f4.array[2] = 3;
484 f4.array[3] = 4;
485
486 formatValue(w, f4, spec);
487 assert(w.data == "[1, 2, 3, 4]");
488 }
489 }
490 }
491
492 import std.format.internal.write;
493
494 import std.format.spec : FormatSpec;
495 import std.traits : isSomeString;
496
497 /**
498 Converts its arguments according to a format string and writes
499 the result to an output range.
500
501 The second version of `formattedWrite` takes the format string as a
502 template argument. In this case, it is checked for consistency at
503 compile-time.
504
505 Params:
506 w = an $(REF_ALTTEXT output range, isOutputRange, std, range, primitives),
507 where the formatted result is written to
508 fmt = a $(MREF_ALTTEXT format string, std,format)
509 args = a variadic list of arguments to be formatted
510 Writer = the type of the writer `w`
511 Char = character type of `fmt`
512 Args = a variadic list of types of the arguments
513
514 Returns:
515 The index of the last argument that was formatted. If no positional
516 arguments are used, this is the number of arguments that where formatted.
517
518 Throws:
519 A $(REF_ALTTEXT FormatException, FormatException, std, format)
520 if formatting did not succeed.
521
522 Note:
523 In theory this function should be `@nogc`. But with the current
524 implementation there are some cases where allocations occur.
525 See $(REF_ALTTEXT $(D sformat), sformat, std, format) for more details.
526 */
formattedWrite(Writer,Char,Args...)527 uint formattedWrite(Writer, Char, Args...)(auto ref Writer w, const scope Char[] fmt, Args args)
528 {
529 import std.conv : text;
530 import std.format : enforceFmt, FormatException;
531 import std.traits : isSomeChar;
532
533 auto spec = FormatSpec!Char(fmt);
534
535 // Are we already done with formats? Then just dump each parameter in turn
536 uint currentArg = 0;
537 while (spec.writeUpToNextSpec(w))
538 {
539 if (currentArg == Args.length && !spec.indexStart)
540 {
541 // leftover spec?
542 enforceFmt(fmt.length == 0,
543 text("Orphan format specifier: %", spec.spec));
544 break;
545 }
546
547 if (spec.width == spec.DYNAMIC)
548 {
549 auto width = getNthInt!"integer width"(currentArg, args);
550 if (width < 0)
551 {
552 spec.flDash = true;
553 width = -width;
554 }
555 spec.width = width;
556 ++currentArg;
557 }
558 else if (spec.width < 0)
559 {
560 // means: get width as a positional parameter
561 auto index = cast(uint) -spec.width;
562 assert(index > 0, "The index must be greater than zero");
563 auto width = getNthInt!"integer width"(index - 1, args);
564 if (currentArg < index) currentArg = index;
565 if (width < 0)
566 {
567 spec.flDash = true;
568 width = -width;
569 }
570 spec.width = width;
571 }
572
573 if (spec.precision == spec.DYNAMIC)
574 {
575 auto precision = getNthInt!"integer precision"(currentArg, args);
576 if (precision >= 0) spec.precision = precision;
577 // else negative precision is same as no precision
578 else spec.precision = spec.UNSPECIFIED;
579 ++currentArg;
580 }
581 else if (spec.precision < 0)
582 {
583 // means: get precision as a positional parameter
584 auto index = cast(uint) -spec.precision;
585 assert(index > 0, "The precision must be greater than zero");
586 auto precision = getNthInt!"integer precision"(index- 1, args);
587 if (currentArg < index) currentArg = index;
588 if (precision >= 0) spec.precision = precision;
589 // else negative precision is same as no precision
590 else spec.precision = spec.UNSPECIFIED;
591 }
592
593 if (spec.separators == spec.DYNAMIC)
594 {
595 auto separators = getNthInt!"separator digit width"(currentArg, args);
596 spec.separators = separators;
597 ++currentArg;
598 }
599
600 if (spec.dynamicSeparatorChar)
601 {
602 auto separatorChar =
603 getNth!("separator character", isSomeChar, dchar)(currentArg, args);
604 spec.separatorChar = separatorChar;
605 spec.dynamicSeparatorChar = false;
606 ++currentArg;
607 }
608
609 if (currentArg == Args.length && !spec.indexStart)
610 {
611 // leftover spec?
612 enforceFmt(fmt.length == 0,
613 text("Orphan format specifier: %", spec.spec));
614 break;
615 }
616
617 // Format an argument
618 // This switch uses a static foreach to generate a jump table.
619 // Currently `spec.indexStart` use the special value '0' to signal
620 // we should use the current argument. An enhancement would be to
621 // always store the index.
622 size_t index = currentArg;
623 if (spec.indexStart != 0)
624 index = spec.indexStart - 1;
625 else
626 ++currentArg;
627 SWITCH: switch (index)
628 {
629 foreach (i, Tunused; Args)
630 {
631 case i:
632 formatValue(w, args[i], spec);
633 if (currentArg < spec.indexEnd)
634 currentArg = spec.indexEnd;
635 // A little know feature of format is to format a range
636 // of arguments, e.g. `%1:3$` will format the first 3
637 // arguments. Since they have to be consecutive we can
638 // just use explicit fallthrough to cover that case.
639 if (i + 1 < spec.indexEnd)
640 {
641 // You cannot goto case if the next case is the default
642 static if (i + 1 < Args.length)
643 goto case;
644 else
645 goto default;
646 }
647 else
648 break SWITCH;
649 }
650 default:
651 throw new FormatException(
652 text("Positional specifier %", spec.indexStart, '$', spec.spec,
653 " index exceeds ", Args.length));
654 }
655 }
656 return currentArg;
657 }
658
659 ///
660 @safe pure unittest
661 {
662 import std.array : appender;
663
664 auto writer1 = appender!string();
665 formattedWrite(writer1, "%s is the ultimate %s.", 42, "answer");
666 assert(writer1[] == "42 is the ultimate answer.");
667
668 auto writer2 = appender!string();
669 formattedWrite(writer2, "Increase: %7.2f %%", 17.4285);
670 assert(writer2[] == "Increase: 17.43 %");
671 }
672
673 /// ditto
674 uint formattedWrite(alias fmt, Writer, Args...)(auto ref Writer w, Args args)
675 if (isSomeString!(typeof(fmt)))
676 {
677 import std.format : checkFormatException;
678
679 alias e = checkFormatException!(fmt, Args);
680 static assert(!e, e);
681 return .formattedWrite(w, fmt, args);
682 }
683
684 /// The format string can be checked at compile-time:
685 @safe pure unittest
686 {
687 import std.array : appender;
688
689 auto writer = appender!string();
690 writer.formattedWrite!"%d is the ultimate %s."(42, "answer");
691 assert(writer[] == "42 is the ultimate answer.");
692
693 // This line doesn't compile, because 3.14 cannot be formatted with %d:
694 // writer.formattedWrite!"%d is the ultimate %s."(3.14, "answer");
695 }
696
697 @safe pure unittest
698 {
699 import std.array : appender;
700
701 auto stream = appender!string();
702 formattedWrite(stream, "%s", 1.1);
703 assert(stream.data == "1.1", stream.data);
704 }
705
706 @safe pure unittest
707 {
708 import std.array;
709
710 auto w = appender!string();
711 formattedWrite(w, "%s %d", "@safe/pure", 42);
712 assert(w.data == "@safe/pure 42");
713 }
714
715 @safe pure unittest
716 {
717 char[20] buf;
718 auto w = buf[];
719 formattedWrite(w, "%s %d", "@safe/pure", 42);
720 assert(buf[0 .. $ - w.length] == "@safe/pure 42");
721 }
722
723 @safe pure unittest
724 {
725 import std.algorithm.iteration : map;
726 import std.array : appender;
727
728 auto stream = appender!string();
729 formattedWrite(stream, "%s", map!"a*a"([2, 3, 5]));
730 assert(stream.data == "[4, 9, 25]", stream.data);
731
732 // Test shared data.
733 stream = appender!string();
734 shared int s = 6;
735 formattedWrite(stream, "%s", s);
736 assert(stream.data == "6");
737 }
738
739 @safe pure unittest
740 {
741 // testing positional parameters
742 import std.array : appender;
743 import std.exception : collectExceptionMsg;
744 import std.format : FormatException;
745
746 auto w = appender!(char[])();
747 formattedWrite(w,
748 "Numbers %2$s and %1$s are reversed and %1$s%2$s repeated",
749 42, 0);
750 assert(w.data == "Numbers 0 and 42 are reversed and 420 repeated",
751 w.data);
752 assert(collectExceptionMsg!FormatException(formattedWrite(w, "%1$s, %3$s", 1, 2))
753 == "Positional specifier %3$s index exceeds 2");
754
755 w.clear();
756 formattedWrite(w, "asd%s", 23);
757 assert(w.data == "asd23", w.data);
758 w.clear();
759 formattedWrite(w, "%s%s", 23, 45);
760 assert(w.data == "2345", w.data);
761 }
762
763 // https://issues.dlang.org/show_bug.cgi?id=3479
764 @safe unittest
765 {
766 import std.array : appender;
767
768 auto stream = appender!(char[])();
769 formattedWrite(stream, "%2$.*1$d", 12, 10);
770 assert(stream.data == "000000000010", stream.data);
771 }
772
773 // https://issues.dlang.org/show_bug.cgi?id=6893
774 @safe unittest
775 {
776 import std.array : appender;
777
778 enum E : ulong { A, B, C }
779 auto stream = appender!(char[])();
780 formattedWrite(stream, "%s", E.C);
781 assert(stream.data == "C");
782 }
783
784 @safe pure unittest
785 {
786 import std.array : appender;
787
788 auto stream = appender!string();
789 formattedWrite(stream, "%u", 42);
790 assert(stream.data == "42", stream.data);
791 }
792
793 @safe pure unittest
794 {
795 // testing raw writes
796 import std.array : appender;
797
798 auto w = appender!(char[])();
799 uint a = 0x02030405;
800 formattedWrite(w, "%+r", a);
801 assert(w.data.length == 4 && w.data[0] == 2 && w.data[1] == 3
802 && w.data[2] == 4 && w.data[3] == 5);
803
804 w.clear();
805 formattedWrite(w, "%-r", a);
806 assert(w.data.length == 4 && w.data[0] == 5 && w.data[1] == 4
807 && w.data[2] == 3 && w.data[3] == 2);
808 }
809
810 @safe unittest
811 {
812 import std.array : appender;
813 import std.conv : text, octal;
814
815 auto stream = appender!(char[])();
816
817 formattedWrite(stream, "hello world! %s %s ", true, 57, 1_000_000_000, 'x', " foo");
818 assert(stream.data == "hello world! true 57 ", stream.data);
819 stream.clear();
820
821 formattedWrite(stream, "%g %A %s", 1.67, -1.28, float.nan);
822 assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan", stream.data);
823 stream.clear();
824
825 formattedWrite(stream, "%x %X", 0x1234AF, 0xAFAFAFAF);
826 assert(stream.data == "1234af AFAFAFAF");
827 stream.clear();
828
829 formattedWrite(stream, "%b %o", 0x1234AF, 0xAFAFAFAF);
830 assert(stream.data == "100100011010010101111 25753727657");
831 stream.clear();
832
833 formattedWrite(stream, "%d %s", 0x1234AF, 0xAFAFAFAF);
834 assert(stream.data == "1193135 2947526575");
835 stream.clear();
836
837 formattedWrite(stream, "%a %A", 1.32, 6.78f);
838 assert(stream.data == "0x1.51eb851eb851fp+0 0X1.B1EB86P+2");
839 stream.clear();
840
841 formattedWrite(stream, "%#06.*f", 2, 12.345);
842 assert(stream.data == "012.35");
843 stream.clear();
844
845 formattedWrite(stream, "%#0*.*f", 6, 2, 12.345);
846 assert(stream.data == "012.35");
847 stream.clear();
848
849 const real constreal = 1;
850 formattedWrite(stream, "%g",constreal);
851 assert(stream.data == "1");
852 stream.clear();
853
854 formattedWrite(stream, "%7.4g:", 12.678);
855 assert(stream.data == " 12.68:");
856 stream.clear();
857
858 formattedWrite(stream, "%7.4g:", 12.678L);
859 assert(stream.data == " 12.68:");
860 stream.clear();
861
862 formattedWrite(stream, "%04f|%05d|%#05x|%#5x", -4.0, -10, 1, 1);
863 assert(stream.data == "-4.000000|-0010|0x001| 0x1", stream.data);
864 stream.clear();
865
866 int i;
867 string s;
868
869 i = -10;
870 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i);
871 assert(stream.data == "-10|-10|-10|-10|-10.0000");
872 stream.clear();
873
874 i = -5;
875 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i);
876 assert(stream.data == "-5| -5|-05|-5|-5.0000");
877 stream.clear();
878
879 i = 0;
880 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i);
881 assert(stream.data == "0| 0|000|0|0.0000");
882 stream.clear();
883
884 i = 5;
885 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i);
886 assert(stream.data == "5| 5|005|5|5.0000");
887 stream.clear();
888
889 i = 10;
890 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i);
891 assert(stream.data == "10| 10|010|10|10.0000");
892 stream.clear();
893
894 formattedWrite(stream, "%.0d", 0);
895 assert(stream.data == "0");
896 stream.clear();
897
898 formattedWrite(stream, "%.g", .34);
899 assert(stream.data == "0.3");
900 stream.clear();
901
902 stream.clear();
903 formattedWrite(stream, "%.0g", .34);
904 assert(stream.data == "0.3");
905
906 stream.clear();
907 formattedWrite(stream, "%.2g", .34);
908 assert(stream.data == "0.34");
909
910 stream.clear();
911 formattedWrite(stream, "%0.0008f", 1e-08);
912 assert(stream.data == "0.00000001");
913
914 stream.clear();
915 formattedWrite(stream, "%0.0008f", 1e-05);
916 assert(stream.data == "0.00001000");
917
918 s = "helloworld";
919 string r;
920 stream.clear();
921 formattedWrite(stream, "%.2s", s[0 .. 5]);
922 assert(stream.data == "he");
923 stream.clear();
924 formattedWrite(stream, "%.20s", s[0 .. 5]);
925 assert(stream.data == "hello");
926 stream.clear();
927 formattedWrite(stream, "%8s", s[0 .. 5]);
928 assert(stream.data == " hello");
929
930 byte[] arrbyte = new byte[4];
931 arrbyte[0] = 100;
932 arrbyte[1] = -99;
933 arrbyte[3] = 0;
934 stream.clear();
935 formattedWrite(stream, "%s", arrbyte);
936 assert(stream.data == "[100, -99, 0, 0]", stream.data);
937
938 ubyte[] arrubyte = new ubyte[4];
939 arrubyte[0] = 100;
940 arrubyte[1] = 200;
941 arrubyte[3] = 0;
942 stream.clear();
943 formattedWrite(stream, "%s", arrubyte);
944 assert(stream.data == "[100, 200, 0, 0]", stream.data);
945
946 short[] arrshort = new short[4];
947 arrshort[0] = 100;
948 arrshort[1] = -999;
949 arrshort[3] = 0;
950 stream.clear();
951 formattedWrite(stream, "%s", arrshort);
952 assert(stream.data == "[100, -999, 0, 0]");
953 stream.clear();
954 formattedWrite(stream, "%s", arrshort);
955 assert(stream.data == "[100, -999, 0, 0]");
956
957 ushort[] arrushort = new ushort[4];
958 arrushort[0] = 100;
959 arrushort[1] = 20_000;
960 arrushort[3] = 0;
961 stream.clear();
962 formattedWrite(stream, "%s", arrushort);
963 assert(stream.data == "[100, 20000, 0, 0]");
964
965 int[] arrint = new int[4];
966 arrint[0] = 100;
967 arrint[1] = -999;
968 arrint[3] = 0;
969 stream.clear();
970 formattedWrite(stream, "%s", arrint);
971 assert(stream.data == "[100, -999, 0, 0]");
972 stream.clear();
973 formattedWrite(stream, "%s", arrint);
974 assert(stream.data == "[100, -999, 0, 0]");
975
976 long[] arrlong = new long[4];
977 arrlong[0] = 100;
978 arrlong[1] = -999;
979 arrlong[3] = 0;
980 stream.clear();
981 formattedWrite(stream, "%s", arrlong);
982 assert(stream.data == "[100, -999, 0, 0]");
983 stream.clear();
984 formattedWrite(stream, "%s",arrlong);
985 assert(stream.data == "[100, -999, 0, 0]");
986
987 ulong[] arrulong = new ulong[4];
988 arrulong[0] = 100;
989 arrulong[1] = 999;
990 arrulong[3] = 0;
991 stream.clear();
992 formattedWrite(stream, "%s", arrulong);
993 assert(stream.data == "[100, 999, 0, 0]");
994
995 string[] arr2 = new string[4];
996 arr2[0] = "hello";
997 arr2[1] = "world";
998 arr2[3] = "foo";
999 stream.clear();
1000 formattedWrite(stream, "%s", arr2);
1001 assert(stream.data == `["hello", "world", "", "foo"]`, stream.data);
1002
1003 stream.clear();
1004 formattedWrite(stream, "%.8d", 7);
1005 assert(stream.data == "00000007");
1006
1007 stream.clear();
1008 formattedWrite(stream, "%.8x", 10);
1009 assert(stream.data == "0000000a");
1010
1011 stream.clear();
1012 formattedWrite(stream, "%-3d", 7);
1013 assert(stream.data == "7 ");
1014
1015 stream.clear();
1016 formattedWrite(stream, "%*d", -3, 7);
1017 assert(stream.data == "7 ");
1018
1019 stream.clear();
1020 formattedWrite(stream, "%.*d", -3, 7);
1021 assert(stream.data == "7");
1022
1023 stream.clear();
1024 formattedWrite(stream, "%s", "abc"c);
1025 assert(stream.data == "abc");
1026 stream.clear();
1027 formattedWrite(stream, "%s", "def"w);
1028 assert(stream.data == "def", text(stream.data.length));
1029 stream.clear();
1030 formattedWrite(stream, "%s", "ghi"d);
1031 assert(stream.data == "ghi");
1032
deadBeef()1033 @trusted void* deadBeef() { return cast(void*) 0xDEADBEEF; }
1034 stream.clear();
1035 formattedWrite(stream, "%s", deadBeef());
1036 assert(stream.data == "DEADBEEF", stream.data);
1037
1038 stream.clear();
1039 formattedWrite(stream, "%#x", 0xabcd);
1040 assert(stream.data == "0xabcd");
1041 stream.clear();
1042 formattedWrite(stream, "%#X", 0xABCD);
1043 assert(stream.data == "0XABCD");
1044
1045 stream.clear();
1046 formattedWrite(stream, "%#o", octal!12345);
1047 assert(stream.data == "012345");
1048 stream.clear();
1049 formattedWrite(stream, "%o", 9);
1050 assert(stream.data == "11");
1051
1052 stream.clear();
1053 formattedWrite(stream, "%+d", 123);
1054 assert(stream.data == "+123");
1055 stream.clear();
1056 formattedWrite(stream, "%+d", -123);
1057 assert(stream.data == "-123");
1058 stream.clear();
1059 formattedWrite(stream, "% d", 123);
1060 assert(stream.data == " 123");
1061 stream.clear();
1062 formattedWrite(stream, "% d", -123);
1063 assert(stream.data == "-123");
1064
1065 stream.clear();
1066 formattedWrite(stream, "%%");
1067 assert(stream.data == "%");
1068
1069 stream.clear();
1070 formattedWrite(stream, "%d", true);
1071 assert(stream.data == "1");
1072 stream.clear();
1073 formattedWrite(stream, "%d", false);
1074 assert(stream.data == "0");
1075
1076 stream.clear();
1077 formattedWrite(stream, "%d", 'a');
1078 assert(stream.data == "97", stream.data);
1079 wchar wc = 'a';
1080 stream.clear();
1081 formattedWrite(stream, "%d", wc);
1082 assert(stream.data == "97");
1083 dchar dc = 'a';
1084 stream.clear();
1085 formattedWrite(stream, "%d", dc);
1086 assert(stream.data == "97");
1087
1088 byte b = byte.max;
1089 stream.clear();
1090 formattedWrite(stream, "%x", b);
1091 assert(stream.data == "7f");
1092 stream.clear();
1093 formattedWrite(stream, "%x", ++b);
1094 assert(stream.data == "80");
1095 stream.clear();
1096 formattedWrite(stream, "%x", ++b);
1097 assert(stream.data == "81");
1098
1099 short sh = short.max;
1100 stream.clear();
1101 formattedWrite(stream, "%x", sh);
1102 assert(stream.data == "7fff");
1103 stream.clear();
1104 formattedWrite(stream, "%x", ++sh);
1105 assert(stream.data == "8000");
1106 stream.clear();
1107 formattedWrite(stream, "%x", ++sh);
1108 assert(stream.data == "8001");
1109
1110 i = int.max;
1111 stream.clear();
1112 formattedWrite(stream, "%x", i);
1113 assert(stream.data == "7fffffff");
1114 stream.clear();
1115 formattedWrite(stream, "%x", ++i);
1116 assert(stream.data == "80000000");
1117 stream.clear();
1118 formattedWrite(stream, "%x", ++i);
1119 assert(stream.data == "80000001");
1120
1121 stream.clear();
1122 formattedWrite(stream, "%x", 10);
1123 assert(stream.data == "a");
1124 stream.clear();
1125 formattedWrite(stream, "%X", 10);
1126 assert(stream.data == "A");
1127 stream.clear();
1128 formattedWrite(stream, "%x", 15);
1129 assert(stream.data == "f");
1130 stream.clear();
1131 formattedWrite(stream, "%X", 15);
1132 assert(stream.data == "F");
1133
ObjectTest()1134 @trusted void ObjectTest()
1135 {
1136 Object c = null;
1137 stream.clear();
1138 formattedWrite(stream, "%s", c);
1139 assert(stream.data == "null");
1140 }
1141 ObjectTest();
1142
1143 enum TestEnum
1144 {
1145 Value1, Value2
1146 }
1147 stream.clear();
1148 formattedWrite(stream, "%s", TestEnum.Value2);
1149 assert(stream.data == "Value2", stream.data);
1150 stream.clear();
1151 formattedWrite(stream, "%s", cast(TestEnum) 5);
1152 assert(stream.data == "cast(TestEnum)5", stream.data);
1153
1154 //immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]);
1155 //stream.clear();
1156 //formattedWrite(stream, "%s", aa.values);
1157 //assert(stream.data == "[[h,e,l,l,o],[b,e,t,t,y]]");
1158 //stream.clear();
1159 //formattedWrite(stream, "%s", aa);
1160 //assert(stream.data == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]");
1161
1162 static const dchar[] ds = ['a','b'];
1163 for (int j = 0; j < ds.length; ++j)
1164 {
1165 stream.clear(); formattedWrite(stream, " %d", ds[j]);
1166 if (j == 0)
1167 assert(stream.data == " 97");
1168 else
1169 assert(stream.data == " 98");
1170 }
1171
1172 stream.clear();
1173 formattedWrite(stream, "%.-3d", 7);
1174 assert(stream.data == "7", ">" ~ stream.data ~ "<");
1175 }
1176
1177 @safe unittest
1178 {
1179 import std.array : appender;
1180 import std.meta : AliasSeq;
1181
1182 immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]);
1183 assert(aa[3] == "hello");
1184 assert(aa[4] == "betty");
1185
1186 auto stream = appender!(char[])();
1187 alias AllNumerics =
1188 AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong,
1189 float, double, real);
foreach(T;AllNumerics)1190 foreach (T; AllNumerics)
1191 {
1192 T value = 1;
1193 stream.clear();
1194 formattedWrite(stream, "%s", value);
1195 assert(stream.data == "1");
1196 }
1197
1198 stream.clear();
1199 formattedWrite(stream, "%s", aa);
1200 }
1201
1202 /**
1203 Formats a value of any type according to a format specifier and
1204 writes the result to an output range.
1205
1206 More details about how types are formatted, and how the format
1207 specifier influences the outcome, can be found in the definition of a
1208 $(MREF_ALTTEXT format string, std,format).
1209
1210 Params:
1211 w = an $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) where
1212 the formatted value is written to
1213 val = the value to write
1214 f = a $(REF_ALTTEXT FormatSpec, FormatSpec, std, format, spec) defining the
1215 format specifier
1216 Writer = the type of the output range `w`
1217 T = the type of value `val`
1218 Char = the character type used for `f`
1219
1220 Throws:
1221 A $(LREF FormatException) if formatting did not succeed.
1222
1223 Note:
1224 In theory this function should be `@nogc`. But with the current
1225 implementation there are some cases where allocations occur.
1226 See $(REF_ALTTEXT $(D sformat), sformat, std, format) for more details.
1227
1228 See_Also:
1229 $(LREF formattedWrite) which formats several values at once.
1230 */
formatValue(Writer,T,Char)1231 void formatValue(Writer, T, Char)(auto ref Writer w, auto ref T val, scope const ref FormatSpec!Char f)
1232 {
1233 import std.format : enforceFmt;
1234
1235 enforceFmt(f.width != f.DYNAMIC && f.precision != f.DYNAMIC
1236 && f.separators != f.DYNAMIC && !f.dynamicSeparatorChar,
1237 "Dynamic argument not allowed for `formatValue`");
1238
1239 formatValueImpl(w, val, f);
1240 }
1241
1242 ///
1243 @safe pure unittest
1244 {
1245 import std.array : appender;
1246 import std.format.spec : singleSpec;
1247
1248 auto writer = appender!string();
1249 auto spec = singleSpec("%08b");
1250 writer.formatValue(42, spec);
1251 assert(writer.data == "00101010");
1252
1253 spec = singleSpec("%2s");
1254 writer.formatValue('=', spec);
1255 assert(writer.data == "00101010 =");
1256
1257 spec = singleSpec("%+14.6e");
1258 writer.formatValue(42.0, spec);
1259 assert(writer.data == "00101010 = +4.200000e+01");
1260 }
1261
1262 // https://issues.dlang.org/show_bug.cgi?id=15386
1263 @safe pure unittest
1264 {
1265 import std.array : appender;
1266 import std.format.spec : FormatSpec;
1267 import std.format : FormatException;
1268 import std.exception : assertThrown;
1269
1270 auto w = appender!(char[])();
1271 auto dor = appender!(char[])();
1272 auto fs = FormatSpec!char("%.*s");
1273 fs.writeUpToNextSpec(dor);
1274 assertThrown!FormatException(formatValue(w, 0, fs));
1275
1276 fs = FormatSpec!char("%*s");
1277 fs.writeUpToNextSpec(dor);
1278 assertThrown!FormatException(formatValue(w, 0, fs));
1279
1280 fs = FormatSpec!char("%,*s");
1281 fs.writeUpToNextSpec(dor);
1282 assertThrown!FormatException(formatValue(w, 0, fs));
1283
1284 fs = FormatSpec!char("%,?s");
1285 fs.writeUpToNextSpec(dor);
1286 assertThrown!FormatException(formatValue(w, 0, fs));
1287
1288 assertThrown!FormatException(formattedWrite(w, "%(%0*d%)", new int[1]));
1289 }
1290
1291 // https://issues.dlang.org/show_bug.cgi?id=22609
1292 @safe pure unittest
1293 {
1294 static enum State: ubyte { INACTIVE }
1295 static struct S {
1296 State state = State.INACTIVE;
1297 int generation = 1;
1298 alias state this;
1299 // DMDBUG: https://issues.dlang.org/show_bug.cgi?id=16657
opEquals(S other)1300 auto opEquals(S other) const { return state == other.state && generation == other.generation; }
opEquals(State other)1301 auto opEquals(State other) const { return state == other; }
1302 }
1303
1304 import std.array : appender;
1305 import std.format.spec : singleSpec;
1306
1307 auto writer = appender!string();
1308 const spec = singleSpec("%s");
1309 S a;
1310 writer.formatValue(a, spec);
1311 assert(writer.data == "0");
1312 }
1313