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