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 $(H3 $(LNAME2 aggregates, Structs, Unions, Classes, 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 
408         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     {
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  */
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             if (spec.indexEnd == spec.indexEnd.max)
652                 break;
653             else if (spec.indexEnd == spec.indexStart)
654                 throw new FormatException(
655                     text("Positional specifier %", spec.indexStart, '$', spec.spec,
656                     " index exceeds ", Args.length));
657             else
658                 throw new FormatException(
659                     text("Positional specifier %", spec.indexStart, ":", spec.indexEnd, '$', spec.spec,
660                     " index exceeds ", Args.length));
661         }
662     }
663     return currentArg;
664 }
665 
666 ///
667 @safe pure unittest
668 {
669     import std.array : appender;
670 
671     auto writer1 = appender!string();
672     formattedWrite(writer1, "%s is the ultimate %s.", 42, "answer");
673     assert(writer1[] == "42 is the ultimate answer.");
674 
675     auto writer2 = appender!string();
676     formattedWrite(writer2, "Increase: %7.2f %%", 17.4285);
677     assert(writer2[] == "Increase:   17.43 %");
678 }
679 
680 /// ditto
681 uint formattedWrite(alias fmt, Writer, Args...)(auto ref Writer w, Args args)
682 if (isSomeString!(typeof(fmt)))
683 {
684     import std.format : checkFormatException;
685 
686     alias e = checkFormatException!(fmt, Args);
687     static assert(!e, e);
688     return .formattedWrite(w, fmt, args);
689 }
690 
691 /// The format string can be checked at compile-time:
692 @safe pure unittest
693 {
694     import std.array : appender;
695 
696     auto writer = appender!string();
697     writer.formattedWrite!"%d is the ultimate %s."(42, "answer");
698     assert(writer[] == "42 is the ultimate answer.");
699 
700     // This line doesn't compile, because 3.14 cannot be formatted with %d:
701     // writer.formattedWrite!"%d is the ultimate %s."(3.14, "answer");
702 }
703 
704 @safe pure unittest
705 {
706     import std.array : appender;
707 
708     auto stream = appender!string();
709     formattedWrite(stream, "%s", 1.1);
710     assert(stream.data == "1.1", stream.data);
711 }
712 
713 @safe pure unittest
714 {
715     import std.array;
716 
717     auto w = appender!string();
718     formattedWrite(w, "%s %d", "@safe/pure", 42);
719     assert(w.data == "@safe/pure 42");
720 }
721 
722 @safe pure unittest
723 {
724     char[20] buf;
725     auto w = buf[];
726     formattedWrite(w, "%s %d", "@safe/pure", 42);
727     assert(buf[0 .. $ - w.length] == "@safe/pure 42");
728 }
729 
730 @safe pure unittest
731 {
732     import std.algorithm.iteration : map;
733     import std.array : appender;
734 
735     auto stream = appender!string();
736     formattedWrite(stream, "%s", map!"a*a"([2, 3, 5]));
737     assert(stream.data == "[4, 9, 25]", stream.data);
738 
739     // Test shared data.
740     stream = appender!string();
741     shared int s = 6;
742     formattedWrite(stream, "%s", s);
743     assert(stream.data == "6");
744 }
745 
746 @safe pure unittest
747 {
748     // testing positional parameters
749     import std.array : appender;
750     import std.exception : collectExceptionMsg;
751     import std.format : FormatException;
752 
753     auto w = appender!(char[])();
754     formattedWrite(w,
755             "Numbers %2$s and %1$s are reversed and %1$s%2$s repeated",
756             42, 0);
757     assert(w.data == "Numbers 0 and 42 are reversed and 420 repeated",
758             w.data);
759     assert(collectExceptionMsg!FormatException(formattedWrite(w, "%1$s, %3$s", 1, 2))
760         == "Positional specifier %3$s index exceeds 2");
761 
762     w.clear();
763     formattedWrite(w, "asd%s", 23);
764     assert(w.data == "asd23", w.data);
765     w.clear();
766     formattedWrite(w, "%s%s", 23, 45);
767     assert(w.data == "2345", w.data);
768 }
769 
770 // https://issues.dlang.org/show_bug.cgi?id=3479
771 @safe unittest
772 {
773     import std.array : appender;
774 
775     auto stream = appender!(char[])();
776     formattedWrite(stream, "%2$.*1$d", 12, 10);
777     assert(stream.data == "000000000010", stream.data);
778 }
779 
780 // https://issues.dlang.org/show_bug.cgi?id=6893
781 @safe unittest
782 {
783     import std.array : appender;
784 
785     enum E : ulong { A, B, C }
786     auto stream = appender!(char[])();
787     formattedWrite(stream, "%s", E.C);
788     assert(stream.data == "C");
789 }
790 
791 @safe pure unittest
792 {
793     import std.array : appender;
794 
795     auto stream = appender!string();
796     formattedWrite(stream, "%u", 42);
797     assert(stream.data == "42", stream.data);
798 }
799 
800 @safe pure unittest
801 {
802     // testing raw writes
803     import std.array : appender;
804 
805     auto w = appender!(char[])();
806     uint a = 0x02030405;
807     formattedWrite(w, "%+r", a);
808     assert(w.data.length == 4 && w.data[0] == 2 && w.data[1] == 3
809         && w.data[2] == 4 && w.data[3] == 5);
810 
811     w.clear();
812     formattedWrite(w, "%-r", a);
813     assert(w.data.length == 4 && w.data[0] == 5 && w.data[1] == 4
814         && w.data[2] == 3 && w.data[3] == 2);
815 }
816 
817 @safe unittest
818 {
819     import std.array : appender;
820     import std.conv : text, octal;
821 
822     auto stream = appender!(char[])();
823 
824     formattedWrite(stream, "hello world! %s %s ", true, 57, 1_000_000_000, 'x', " foo");
825     assert(stream.data == "hello world! true 57 ", stream.data);
826     stream.clear();
827 
828     formattedWrite(stream, "%g %A %s", 1.67, -1.28, float.nan);
829     assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan", stream.data);
830     stream.clear();
831 
832     formattedWrite(stream, "%x %X", 0x1234AF, 0xAFAFAFAF);
833     assert(stream.data == "1234af AFAFAFAF");
834     stream.clear();
835 
836     formattedWrite(stream, "%b %o", 0x1234AF, 0xAFAFAFAF);
837     assert(stream.data == "100100011010010101111 25753727657");
838     stream.clear();
839 
840     formattedWrite(stream, "%d %s", 0x1234AF, 0xAFAFAFAF);
841     assert(stream.data == "1193135 2947526575");
842     stream.clear();
843 
844     formattedWrite(stream, "%a %A", 1.32, 6.78f);
845     assert(stream.data == "0x1.51eb851eb851fp+0 0X1.B1EB86P+2");
846     stream.clear();
847 
848     formattedWrite(stream, "%#06.*f", 2, 12.345);
849     assert(stream.data == "012.35");
850     stream.clear();
851 
852     formattedWrite(stream, "%#0*.*f", 6, 2, 12.345);
853     assert(stream.data == "012.35");
854     stream.clear();
855 
856     const real constreal = 1;
857     formattedWrite(stream, "%g",constreal);
858     assert(stream.data == "1");
859     stream.clear();
860 
861     formattedWrite(stream, "%7.4g:", 12.678);
862     assert(stream.data == "  12.68:");
863     stream.clear();
864 
865     formattedWrite(stream, "%7.4g:", 12.678L);
866     assert(stream.data == "  12.68:");
867     stream.clear();
868 
869     formattedWrite(stream, "%04f|%05d|%#05x|%#5x", -4.0, -10, 1, 1);
870     assert(stream.data == "-4.000000|-0010|0x001|  0x1", stream.data);
871     stream.clear();
872 
873     int i;
874     string s;
875 
876     i = -10;
877     formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i);
878     assert(stream.data == "-10|-10|-10|-10|-10.0000");
879     stream.clear();
880 
881     i = -5;
882     formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i);
883     assert(stream.data == "-5| -5|-05|-5|-5.0000");
884     stream.clear();
885 
886     i = 0;
887     formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i);
888     assert(stream.data == "0|  0|000|0|0.0000");
889     stream.clear();
890 
891     i = 5;
892     formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i);
893     assert(stream.data == "5|  5|005|5|5.0000");
894     stream.clear();
895 
896     i = 10;
897     formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i);
898     assert(stream.data == "10| 10|010|10|10.0000");
899     stream.clear();
900 
901     formattedWrite(stream, "%.0d", 0);
902     assert(stream.data == "0");
903     stream.clear();
904 
905     formattedWrite(stream, "%.g", .34);
906     assert(stream.data == "0.3");
907     stream.clear();
908 
909     stream.clear();
910     formattedWrite(stream, "%.0g", .34);
911     assert(stream.data == "0.3");
912 
913     stream.clear();
914     formattedWrite(stream, "%.2g", .34);
915     assert(stream.data == "0.34");
916 
917     stream.clear();
918     formattedWrite(stream, "%0.0008f", 1e-08);
919     assert(stream.data == "0.00000001");
920 
921     stream.clear();
922     formattedWrite(stream, "%0.0008f", 1e-05);
923     assert(stream.data == "0.00001000");
924 
925     s = "helloworld";
926     string r;
927     stream.clear();
928     formattedWrite(stream, "%.2s", s[0 .. 5]);
929     assert(stream.data == "he");
930     stream.clear();
931     formattedWrite(stream, "%.20s", s[0 .. 5]);
932     assert(stream.data == "hello");
933     stream.clear();
934     formattedWrite(stream, "%8s", s[0 .. 5]);
935     assert(stream.data == "   hello");
936 
937     byte[] arrbyte = new byte[4];
938     arrbyte[0] = 100;
939     arrbyte[1] = -99;
940     arrbyte[3] = 0;
941     stream.clear();
942     formattedWrite(stream, "%s", arrbyte);
943     assert(stream.data == "[100, -99, 0, 0]", stream.data);
944 
945     ubyte[] arrubyte = new ubyte[4];
946     arrubyte[0] = 100;
947     arrubyte[1] = 200;
948     arrubyte[3] = 0;
949     stream.clear();
950     formattedWrite(stream, "%s", arrubyte);
951     assert(stream.data == "[100, 200, 0, 0]", stream.data);
952 
953     short[] arrshort = new short[4];
954     arrshort[0] = 100;
955     arrshort[1] = -999;
956     arrshort[3] = 0;
957     stream.clear();
958     formattedWrite(stream, "%s", arrshort);
959     assert(stream.data == "[100, -999, 0, 0]");
960     stream.clear();
961     formattedWrite(stream, "%s", arrshort);
962     assert(stream.data == "[100, -999, 0, 0]");
963 
964     ushort[] arrushort = new ushort[4];
965     arrushort[0] = 100;
966     arrushort[1] = 20_000;
967     arrushort[3] = 0;
968     stream.clear();
969     formattedWrite(stream, "%s", arrushort);
970     assert(stream.data == "[100, 20000, 0, 0]");
971 
972     int[] arrint = new int[4];
973     arrint[0] = 100;
974     arrint[1] = -999;
975     arrint[3] = 0;
976     stream.clear();
977     formattedWrite(stream, "%s", arrint);
978     assert(stream.data == "[100, -999, 0, 0]");
979     stream.clear();
980     formattedWrite(stream, "%s", arrint);
981     assert(stream.data == "[100, -999, 0, 0]");
982 
983     long[] arrlong = new long[4];
984     arrlong[0] = 100;
985     arrlong[1] = -999;
986     arrlong[3] = 0;
987     stream.clear();
988     formattedWrite(stream, "%s", arrlong);
989     assert(stream.data == "[100, -999, 0, 0]");
990     stream.clear();
991     formattedWrite(stream, "%s",arrlong);
992     assert(stream.data == "[100, -999, 0, 0]");
993 
994     ulong[] arrulong = new ulong[4];
995     arrulong[0] = 100;
996     arrulong[1] = 999;
997     arrulong[3] = 0;
998     stream.clear();
999     formattedWrite(stream, "%s", arrulong);
1000     assert(stream.data == "[100, 999, 0, 0]");
1001 
1002     string[] arr2 = new string[4];
1003     arr2[0] = "hello";
1004     arr2[1] = "world";
1005     arr2[3] = "foo";
1006     stream.clear();
1007     formattedWrite(stream, "%s", arr2);
1008     assert(stream.data == `["hello", "world", "", "foo"]`, stream.data);
1009 
1010     stream.clear();
1011     formattedWrite(stream, "%.8d", 7);
1012     assert(stream.data == "00000007");
1013 
1014     stream.clear();
1015     formattedWrite(stream, "%.8x", 10);
1016     assert(stream.data == "0000000a");
1017 
1018     stream.clear();
1019     formattedWrite(stream, "%-3d", 7);
1020     assert(stream.data == "7  ");
1021 
1022     stream.clear();
1023     formattedWrite(stream, "%*d", -3, 7);
1024     assert(stream.data == "7  ");
1025 
1026     stream.clear();
1027     formattedWrite(stream, "%.*d", -3, 7);
1028     assert(stream.data == "7");
1029 
1030     stream.clear();
1031     formattedWrite(stream, "%s", "abc"c);
1032     assert(stream.data == "abc");
1033     stream.clear();
1034     formattedWrite(stream, "%s", "def"w);
1035     assert(stream.data == "def", text(stream.data.length));
1036     stream.clear();
1037     formattedWrite(stream, "%s", "ghi"d);
1038     assert(stream.data == "ghi");
1039 
1040     @trusted void* deadBeef() { return cast(void*) 0xDEADBEEF; }
1041     stream.clear();
1042     formattedWrite(stream, "%s", deadBeef());
1043     assert(stream.data == "DEADBEEF", stream.data);
1044 
1045     stream.clear();
1046     formattedWrite(stream, "%#x", 0xabcd);
1047     assert(stream.data == "0xabcd");
1048     stream.clear();
1049     formattedWrite(stream, "%#X", 0xABCD);
1050     assert(stream.data == "0XABCD");
1051 
1052     stream.clear();
1053     formattedWrite(stream, "%#o", octal!12345);
1054     assert(stream.data == "012345");
1055     stream.clear();
1056     formattedWrite(stream, "%o", 9);
1057     assert(stream.data == "11");
1058 
1059     stream.clear();
1060     formattedWrite(stream, "%+d", 123);
1061     assert(stream.data == "+123");
1062     stream.clear();
1063     formattedWrite(stream, "%+d", -123);
1064     assert(stream.data == "-123");
1065     stream.clear();
1066     formattedWrite(stream, "% d", 123);
1067     assert(stream.data == " 123");
1068     stream.clear();
1069     formattedWrite(stream, "% d", -123);
1070     assert(stream.data == "-123");
1071 
1072     stream.clear();
1073     formattedWrite(stream, "%%");
1074     assert(stream.data == "%");
1075 
1076     stream.clear();
1077     formattedWrite(stream, "%d", true);
1078     assert(stream.data == "1");
1079     stream.clear();
1080     formattedWrite(stream, "%d", false);
1081     assert(stream.data == "0");
1082 
1083     stream.clear();
1084     formattedWrite(stream, "%d", 'a');
1085     assert(stream.data == "97", stream.data);
1086     wchar wc = 'a';
1087     stream.clear();
1088     formattedWrite(stream, "%d", wc);
1089     assert(stream.data == "97");
1090     dchar dc = 'a';
1091     stream.clear();
1092     formattedWrite(stream, "%d", dc);
1093     assert(stream.data == "97");
1094 
1095     byte b = byte.max;
1096     stream.clear();
1097     formattedWrite(stream, "%x", b);
1098     assert(stream.data == "7f");
1099     stream.clear();
1100     formattedWrite(stream, "%x", ++b);
1101     assert(stream.data == "80");
1102     stream.clear();
1103     formattedWrite(stream, "%x", ++b);
1104     assert(stream.data == "81");
1105 
1106     short sh = short.max;
1107     stream.clear();
1108     formattedWrite(stream, "%x", sh);
1109     assert(stream.data == "7fff");
1110     stream.clear();
1111     formattedWrite(stream, "%x", ++sh);
1112     assert(stream.data == "8000");
1113     stream.clear();
1114     formattedWrite(stream, "%x", ++sh);
1115     assert(stream.data == "8001");
1116 
1117     i = int.max;
1118     stream.clear();
1119     formattedWrite(stream, "%x", i);
1120     assert(stream.data == "7fffffff");
1121     stream.clear();
1122     formattedWrite(stream, "%x", ++i);
1123     assert(stream.data == "80000000");
1124     stream.clear();
1125     formattedWrite(stream, "%x", ++i);
1126     assert(stream.data == "80000001");
1127 
1128     stream.clear();
1129     formattedWrite(stream, "%x", 10);
1130     assert(stream.data == "a");
1131     stream.clear();
1132     formattedWrite(stream, "%X", 10);
1133     assert(stream.data == "A");
1134     stream.clear();
1135     formattedWrite(stream, "%x", 15);
1136     assert(stream.data == "f");
1137     stream.clear();
1138     formattedWrite(stream, "%X", 15);
1139     assert(stream.data == "F");
1140 
1141     @trusted void ObjectTest()
1142     {
1143         Object c = null;
1144         stream.clear();
1145         formattedWrite(stream, "%s", c);
1146         assert(stream.data == "null");
1147     }
1148     ObjectTest();
1149 
1150     enum TestEnum
1151     {
1152         Value1, Value2
1153     }
1154     stream.clear();
1155     formattedWrite(stream, "%s", TestEnum.Value2);
1156     assert(stream.data == "Value2", stream.data);
1157     stream.clear();
1158     formattedWrite(stream, "%s", cast(TestEnum) 5);
1159     assert(stream.data == "cast(TestEnum)5", stream.data);
1160 
1161     //immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]);
1162     //stream.clear();
1163     //formattedWrite(stream, "%s", aa.values);
1164     //assert(stream.data == "[[h,e,l,l,o],[b,e,t,t,y]]");
1165     //stream.clear();
1166     //formattedWrite(stream, "%s", aa);
1167     //assert(stream.data == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]");
1168 
1169     static const dchar[] ds = ['a','b'];
1170     for (int j = 0; j < ds.length; ++j)
1171     {
1172         stream.clear(); formattedWrite(stream, " %d", ds[j]);
1173         if (j == 0)
1174             assert(stream.data == " 97");
1175         else
1176             assert(stream.data == " 98");
1177     }
1178 
1179     stream.clear();
1180     formattedWrite(stream, "%.-3d", 7);
1181     assert(stream.data == "7", ">" ~ stream.data ~ "<");
1182 }
1183 
1184 @safe unittest
1185 {
1186     import std.array : appender;
1187     import std.meta : AliasSeq;
1188 
1189     immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]);
1190     assert(aa[3] == "hello");
1191     assert(aa[4] == "betty");
1192 
1193     auto stream = appender!(char[])();
1194     alias AllNumerics =
1195         AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong,
1196                   float, double, real);
1197     foreach (T; AllNumerics)
1198     {
1199         T value = 1;
1200         stream.clear();
1201         formattedWrite(stream, "%s", value);
1202         assert(stream.data == "1");
1203     }
1204 
1205     stream.clear();
1206     formattedWrite(stream, "%s", aa);
1207 }
1208 
1209 // https://github.com/dlang/phobos/issues/10699
1210 @safe pure unittest
1211 {
1212     import std.array : appender;
1213     auto w = appender!(char[])();
1214 
1215     formattedWrite(w, "%1:$d", 1, 2, 3);
1216     assert(w.data == "123");
1217 }
1218 
1219 /**
1220 Formats a value of any type according to a format specifier and
1221 writes the result to an output range.
1222 
1223 More details about how types are formatted, and how the format
1224 specifier influences the outcome, can be found in the definition of a
1225 $(MREF_ALTTEXT format string, std,format).
1226 
1227 Params:
1228     w = an $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) where
1229         the formatted value is written to
1230     val = the value to write
1231     f = a $(REF_ALTTEXT FormatSpec, FormatSpec, std, format, spec) defining the
1232         format specifier
1233     Writer = the type of the output range `w`
1234     T = the type of value `val`
1235     Char = the character type used for `f`
1236 
1237 Throws:
1238     A $(LREF FormatException) if formatting did not succeed.
1239 
1240 Note:
1241     In theory this function should be `@nogc`. But with the current
1242     implementation there are some cases where allocations occur.
1243     See $(REF_ALTTEXT $(D sformat), sformat, std, format) for more details.
1244 
1245 See_Also:
1246     $(LREF formattedWrite) which formats several values at once.
1247  */
1248 void formatValue(Writer, T, Char)(auto ref Writer w, auto ref T val, scope const ref FormatSpec!Char f)
1249 {
1250     import std.format : enforceFmt;
1251 
1252     enforceFmt(f.width != f.DYNAMIC && f.precision != f.DYNAMIC
1253                && f.separators != f.DYNAMIC && !f.dynamicSeparatorChar,
1254                "Dynamic argument not allowed for `formatValue`");
1255 
1256     formatValueImpl(w, val, f);
1257 }
1258 
1259 ///
1260 @safe pure unittest
1261 {
1262     import std.array : appender;
1263     import std.format.spec : singleSpec;
1264 
1265     auto writer = appender!string();
1266     auto spec = singleSpec("%08b");
1267     writer.formatValue(42, spec);
1268     assert(writer.data == "00101010");
1269 
1270     spec = singleSpec("%2s");
1271     writer.formatValue('=', spec);
1272     assert(writer.data == "00101010 =");
1273 
1274     spec = singleSpec("%+14.6e");
1275     writer.formatValue(42.0, spec);
1276     assert(writer.data == "00101010 = +4.200000e+01");
1277 }
1278 
1279 // https://issues.dlang.org/show_bug.cgi?id=15386
1280 @safe pure unittest
1281 {
1282     import std.array : appender;
1283     import std.format.spec : FormatSpec;
1284     import std.format : FormatException;
1285     import std.exception : assertThrown;
1286 
1287     auto w = appender!(char[])();
1288     auto dor = appender!(char[])();
1289     auto fs = FormatSpec!char("%.*s");
1290     fs.writeUpToNextSpec(dor);
1291     assertThrown!FormatException(formatValue(w, 0, fs));
1292 
1293     fs = FormatSpec!char("%*s");
1294     fs.writeUpToNextSpec(dor);
1295     assertThrown!FormatException(formatValue(w, 0, fs));
1296 
1297     fs = FormatSpec!char("%,*s");
1298     fs.writeUpToNextSpec(dor);
1299     assertThrown!FormatException(formatValue(w, 0, fs));
1300 
1301     fs = FormatSpec!char("%,?s");
1302     fs.writeUpToNextSpec(dor);
1303     assertThrown!FormatException(formatValue(w, 0, fs));
1304 
1305     assertThrown!FormatException(formattedWrite(w, "%(%0*d%)", new int[1]));
1306 }
1307 
1308 // https://issues.dlang.org/show_bug.cgi?id=22609
1309 @safe pure unittest
1310 {
1311     static enum State: ubyte { INACTIVE }
1312     static struct S {
1313         State state = State.INACTIVE;
1314         int generation = 1;
1315         alias state this;
1316         // DMDBUG: https://issues.dlang.org/show_bug.cgi?id=16657
1317         auto opEquals(S other) const { return state == other.state && generation == other.generation; }
1318         auto opEquals(State other) const { return state == other; }
1319     }
1320 
1321     import std.array : appender;
1322     import std.format.spec : singleSpec;
1323 
1324     auto writer = appender!string();
1325     const spec = singleSpec("%s");
1326     S a;
1327     writer.formatValue(a, spec);
1328     assert(writer.data == "0");
1329 }
1330 
1331 // https://issues.dlang.org/show_bug.cgi?id=23400
1332 @safe pure unittest
1333 {
1334     import std.range : nullSink;
1335     import std.format.spec : singleSpec;
1336 
1337     static struct S
1338     {
1339         // non-const opEquals method
1340         bool opEquals(S rhs) { return false; }
1341     }
1342 
1343     enum E { a = S() }
1344 
1345     E e;
1346     auto writer = nullSink;
1347     const spec = singleSpec("%s");
1348     writer.formatValue(e, spec);
1349 }