1// Written in the D programming language.
2
3/**
4This is a submodule of $(MREF std, format).
5
6It provides two functions for writing formatted output: $(LREF
7formatValue) and $(LREF formattedWrite). The former writes a single
8value. The latter writes several values at once, interspersed with
9unformatted text.
10
11The following combinations of format characters and types are
12available:
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
29Enums can be used with all format characters of the base type.
30
31$(SECTION3 Structs$(COMMA) Unions$(COMMA) Classes$(COMMA) and Interfaces)
32
33Aggregate types can define various `toString` functions. If this
34function takes a $(REF_ALTTEXT FormatSpec, FormatSpec, std, format,
35spec) or a $(I format string) as argument, the function decides
36which format characters are accepted. If no `toString` is defined and
37the aggregate is an $(REF_ALTTEXT input range, isInputRange, std,
38range, primitives), it is treated like a range, that is $(B 's'), $(B
39'r') and a compound specifier are accepted. In all other cases
40aggregate types only accept $(B 's').
41
42`toString` should have one of the following signatures:
43
44---
45void toString(Writer, Char)(ref Writer w, const ref FormatSpec!Char fmt)
46void toString(Writer)(ref Writer w)
47string toString();
48---
49
50Where `Writer` is an $(REF_ALTTEXT output range, isOutputRange,
51std,range,primitives) which accepts characters $(LPAREN)of type
52`Char` in the first version$(RPAREN). The template type does not have
53to be called `Writer`.
54
55Sometimes 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---
60void toString(void delegate(const(char)[]) sink, const ref FormatSpec!char fmt);
61void toString(void delegate(const(char)[]) sink, string fmt);
62void toString(void delegate(const(char)[]) sink);
63---
64
65When several of the above `toString` versions are available, the
66versions with `Writer` take precedence over the versions with a
67`sink`. `string toString()` has the lowest priority.
68
69If none of the above mentioned `toString` versions are available, the
70aggregates will be formatted by other means, in the following
71order:
72
73If an aggregate is an $(REF_ALTTEXT input range, isInputRange, std,
74range, primitives), it is formatted like an input range.
75
76If an aggregate is a builtin type (using `alias this`), it is formatted
77like the builtin type.
78
79If all else fails, structs are formatted like `Type(field1, field2, ...)`,
80classes and interfaces are formatted with their fully qualified name
81and unions with their base name.
82
83Copyright: Copyright The D Language Foundation 2000-2013.
84
85License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
86
87Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com,
88Andrei Alexandrescu), and Kenji Hara
89
90Source: $(PHOBOSSRC std/format/write.d)
91 */
92module 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/**
130Integrals are formatted in (signed) every day notation with `%s` and
131`%d` and as an (unsigned) image of the underlying bit representation
132with `%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/**
153Floating-point values are formatted in natural notation with `%f`, in
154scientific notation with `%e`, in short notation with `%g`, and in
155hexadecimal scientific notation with `%a`. If a rounding mode is
156available, they are rounded according to this rounding mode, otherwise
157they 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/**
190Individual characters (`char`, `wchar`, or `dchar`) are formatted as
191Unicode 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
194treated 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/**
215Strings are formatted as a sequence of characters with `%s`.
216Non-printable characters are not escaped. With a compound specifier
217the string is treated like a range of characters. With $(MREF_ALTTEXT
218compound 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/**
253Dynamic 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/**
284Associative arrays are formatted by using `':'` and `", "` as
285separators, enclosed by `'['` and `']'` when used with `%s`. It's
286also possible to use a compound specifier for better control.
287
288Please note, that the order of the elements is not defined, therefore
289the 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
313their 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
347several different ways. The following example highlights `struct`
348formatting, 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
492import std.format.internal.write;
493
494import std.format.spec : FormatSpec;
495import std.traits : isSomeString;
496
497/**
498Converts its arguments according to a format string and writes
499the result to an output range.
500
501The second version of `formattedWrite` takes the format string as a
502template argument. In this case, it is checked for consistency at
503compile-time.
504
505Params:
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
514Returns:
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
518Throws:
519    A $(REF_ALTTEXT FormatException, FormatException, std, format)
520    if formatting did not succeed.
521
522Note:
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 */
527uint 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
674uint formattedWrite(alias fmt, Writer, Args...)(auto ref Writer w, Args args)
675if (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
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
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);
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/**
1203Formats a value of any type according to a format specifier and
1204writes the result to an output range.
1205
1206More details about how types are formatted, and how the format
1207specifier influences the outcome, can be found in the definition of a
1208$(MREF_ALTTEXT format string, std,format).
1209
1210Params:
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
1220Throws:
1221    A $(LREF FormatException) if formatting did not succeed.
1222
1223Note:
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
1228See_Also:
1229    $(LREF formattedWrite) which formats several values at once.
1230 */
1231void 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
1300        auto opEquals(S other) const { return state == other.state && generation == other.generation; }
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