1 // Written in the D programming language.
2 
3 /**
4    Copyright: Copyright Digital Mars 2000-2013.
5 
6    License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
7 
8    Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com,
9    Andrei Alexandrescu), and Kenji Hara
10 
11    Source: $(PHOBOSSRC std/_format.d)
12  */
13 module undead.doformat;
14 
15 //debug=format;                // uncomment to turn on debugging printf's
16 
17 import core.vararg;
18 import std.exception;
19 import std.meta;
20 import std.range.primitives;
21 import std.traits;
22 import std.format;
23 
24 version(CRuntime_DigitalMars)
25 {
26     version = DigitalMarsC;
27 }
28 
29 version (DigitalMarsC)
30 {
31     // This is DMC's internal floating point formatting function
32     extern (C)
33     {
34         extern shared char* function(int c, int flags, int precision,
35                 in real* pdval,
36                 char* buf, size_t* psl, int width) __pfloatfmt;
37     }
38 }
39 
40 /**********************************************************************
41  * Signals a mismatch between a format and its corresponding argument.
42  */
43 class FormatException : Exception
44 {
45     @safe pure nothrow
46     this()
47     {
48         super("format error");
49     }
50 
51     @safe pure nothrow
52     this(string msg, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null)
53     {
54         super(msg, fn, ln, next);
55     }
56 }
57 
58 
59 // Legacy implementation
60 
61 enum Mangle : char
62 {
63     Tvoid     = 'v',
64     Tbool     = 'b',
65     Tbyte     = 'g',
66     Tubyte    = 'h',
67     Tshort    = 's',
68     Tushort   = 't',
69     Tint      = 'i',
70     Tuint     = 'k',
71     Tlong     = 'l',
72     Tulong    = 'm',
73     Tfloat    = 'f',
74     Tdouble   = 'd',
75     Treal     = 'e',
76 
77     Tchar     = 'a',
78     Twchar    = 'u',
79     Tdchar    = 'w',
80 
81     Tarray    = 'A',
82     Tsarray   = 'G',
83     Taarray   = 'H',
84     Tpointer  = 'P',
85     Tfunction = 'F',
86     Tident    = 'I',
87     Tclass    = 'C',
88     Tstruct   = 'S',
89     Tenum     = 'E',
90     Ttypedef  = 'T',
91     Tdelegate = 'D',
92 
93     Tconst    = 'x',
94     Timmutable = 'y',
95 }
96 
97 // return the TypeInfo for a primitive type and null otherwise.  This
98 // is required since for arrays of ints we only have the mangled char
99 // to work from. If arrays always subclassed TypeInfo_Array this
100 // routine could go away.
101 private TypeInfo primitiveTypeInfo(Mangle m)
102 {
103     // BUG: should fix this in static this() to avoid double checked locking bug
104     __gshared TypeInfo[Mangle] dic;
105     if (!dic.length)
106     {
107         dic = [
108             Mangle.Tvoid : typeid(void),
109             Mangle.Tbool : typeid(bool),
110             Mangle.Tbyte : typeid(byte),
111             Mangle.Tubyte : typeid(ubyte),
112             Mangle.Tshort : typeid(short),
113             Mangle.Tushort : typeid(ushort),
114             Mangle.Tint : typeid(int),
115             Mangle.Tuint : typeid(uint),
116             Mangle.Tlong : typeid(long),
117             Mangle.Tulong : typeid(ulong),
118             Mangle.Tfloat : typeid(float),
119             Mangle.Tdouble : typeid(double),
120             Mangle.Treal : typeid(real),
121             Mangle.Tchar : typeid(char),
122             Mangle.Twchar : typeid(wchar),
123             Mangle.Tdchar : typeid(dchar)
124             ];
125     }
126     auto p = m in dic;
127     return p ? *p : null;
128 }
129 
130 // This stuff has been removed from the docs and is planned for deprecation.
131 /*
132  * Interprets variadic argument list pointed to by argptr whose types
133  * are given by arguments[], formats them according to embedded format
134  * strings in the variadic argument list, and sends the resulting
135  * characters to putc.
136  *
137  * The variadic arguments are consumed in order.  Each is formatted
138  * into a sequence of chars, using the default format specification
139  * for its type, and the characters are sequentially passed to putc.
140  * If a $(D char[]), $(D wchar[]), or $(D dchar[]) argument is
141  * encountered, it is interpreted as a format string. As many
142  * arguments as specified in the format string are consumed and
143  * formatted according to the format specifications in that string and
144  * passed to putc. If there are too few remaining arguments, a
145  * $(D FormatException) is thrown. If there are more remaining arguments than
146  * needed by the format specification, the default processing of
147  * arguments resumes until they are all consumed.
148  *
149  * Params:
150  *        putc =        Output is sent do this delegate, character by character.
151  *        arguments = Array of $(D TypeInfo)s, one for each argument to be formatted.
152  *        argptr = Points to variadic argument list.
153  *
154  * Throws:
155  *        Mismatched arguments and formats result in a $(D FormatException) being thrown.
156  *
157  * Format_String:
158  *        <a name="format-string">$(I Format strings)</a>
159  *        consist of characters interspersed with
160  *        $(I format specifications). Characters are simply copied
161  *        to the output (such as putc) after any necessary conversion
162  *        to the corresponding UTF-8 sequence.
163  *
164  *        A $(I format specification) starts with a '%' character,
165  *        and has the following grammar:
166 
167 $(CONSOLE
168 $(I FormatSpecification):
169     $(B '%%')
170     $(B '%') $(I Flags) $(I Width) $(I Precision) $(I FormatChar)
171 
172 $(I Flags):
173     $(I empty)
174     $(B '-') $(I Flags)
175     $(B '+') $(I Flags)
176     $(B '#') $(I Flags)
177     $(B '0') $(I Flags)
178     $(B ' ') $(I Flags)
179 
180 $(I Width):
181     $(I empty)
182     $(I Integer)
183     $(B '*')
184 
185 $(I Precision):
186     $(I empty)
187     $(B '.')
188     $(B '.') $(I Integer)
189     $(B '.*')
190 
191 $(I Integer):
192     $(I Digit)
193     $(I Digit) $(I Integer)
194 
195 $(I Digit):
196     $(B '0')
197     $(B '1')
198     $(B '2')
199     $(B '3')
200     $(B '4')
201     $(B '5')
202     $(B '6')
203     $(B '7')
204     $(B '8')
205     $(B '9')
206 
207 $(I FormatChar):
208     $(B 's')
209     $(B 'b')
210     $(B 'd')
211     $(B 'o')
212     $(B 'x')
213     $(B 'X')
214     $(B 'e')
215     $(B 'E')
216     $(B 'f')
217     $(B 'F')
218     $(B 'g')
219     $(B 'G')
220     $(B 'a')
221     $(B 'A')
222 )
223     $(DL
224     $(DT $(I Flags))
225     $(DL
226         $(DT $(B '-'))
227         $(DD
228         Left justify the result in the field.
229         It overrides any $(B 0) flag.)
230 
231         $(DT $(B '+'))
232         $(DD Prefix positive numbers in a signed conversion with a $(B +).
233         It overrides any $(I space) flag.)
234 
235         $(DT $(B '#'))
236         $(DD Use alternative formatting:
237         $(DL
238             $(DT For $(B 'o'):)
239             $(DD Add to precision as necessary so that the first digit
240             of the octal formatting is a '0', even if both the argument
241             and the $(I Precision) are zero.)
242             $(DT For $(B 'x') ($(B 'X')):)
243             $(DD If non-zero, prefix result with $(B 0x) ($(B 0X)).)
244             $(DT For floating point formatting:)
245             $(DD Always insert the decimal point.)
246             $(DT For $(B 'g') ($(B 'G')):)
247             $(DD Do not elide trailing zeros.)
248         ))
249 
250         $(DT $(B '0'))
251         $(DD For integer and floating point formatting when not nan or
252         infinity, use leading zeros
253         to pad rather than spaces.
254         Ignore if there's a $(I Precision).)
255 
256         $(DT $(B ' '))
257         $(DD Prefix positive numbers in a signed conversion with a space.)
258     )
259 
260     $(DT $(I Width))
261     $(DD
262     Specifies the minimum field width.
263     If the width is a $(B *), the next argument, which must be
264     of type $(B int), is taken as the width.
265     If the width is negative, it is as if the $(B -) was given
266     as a $(I Flags) character.)
267 
268     $(DT $(I Precision))
269     $(DD Gives the precision for numeric conversions.
270     If the precision is a $(B *), the next argument, which must be
271     of type $(B int), is taken as the precision. If it is negative,
272     it is as if there was no $(I Precision).)
273 
274     $(DT $(I FormatChar))
275     $(DD
276     $(DL
277         $(DT $(B 's'))
278         $(DD The corresponding argument is formatted in a manner consistent
279         with its type:
280         $(DL
281             $(DT $(B bool))
282             $(DD The result is <tt>'true'</tt> or <tt>'false'</tt>.)
283             $(DT integral types)
284             $(DD The $(B %d) format is used.)
285             $(DT floating point types)
286             $(DD The $(B %g) format is used.)
287             $(DT string types)
288             $(DD The result is the string converted to UTF-8.)
289             A $(I Precision) specifies the maximum number of characters
290             to use in the result.
291             $(DT classes derived from $(B Object))
292             $(DD The result is the string returned from the class instance's
293             $(B .toString()) method.
294             A $(I Precision) specifies the maximum number of characters
295             to use in the result.)
296             $(DT non-string static and dynamic arrays)
297             $(DD The result is [s<sub>0</sub>, s<sub>1</sub>, ...]
298             where s<sub>k</sub> is the kth element
299             formatted with the default format.)
300         ))
301 
302         $(DT $(B 'b','d','o','x','X'))
303         $(DD The corresponding argument must be an integral type
304         and is formatted as an integer. If the argument is a signed type
305         and the $(I FormatChar) is $(B d) it is converted to
306         a signed string of characters, otherwise it is treated as
307         unsigned. An argument of type $(B bool) is formatted as '1'
308         or '0'. The base used is binary for $(B b), octal for $(B o),
309         decimal
310         for $(B d), and hexadecimal for $(B x) or $(B X).
311         $(B x) formats using lower case letters, $(B X) uppercase.
312         If there are fewer resulting digits than the $(I Precision),
313         leading zeros are used as necessary.
314         If the $(I Precision) is 0 and the number is 0, no digits
315         result.)
316 
317         $(DT $(B 'e','E'))
318         $(DD A floating point number is formatted as one digit before
319         the decimal point, $(I Precision) digits after, the $(I FormatChar),
320         &plusmn;, followed by at least a two digit exponent: $(I d.dddddd)e$(I &plusmn;dd).
321         If there is no $(I Precision), six
322         digits are generated after the decimal point.
323         If the $(I Precision) is 0, no decimal point is generated.)
324 
325         $(DT $(B 'f','F'))
326         $(DD A floating point number is formatted in decimal notation.
327         The $(I Precision) specifies the number of digits generated
328         after the decimal point. It defaults to six. At least one digit
329         is generated before the decimal point. If the $(I Precision)
330         is zero, no decimal point is generated.)
331 
332         $(DT $(B 'g','G'))
333         $(DD A floating point number is formatted in either $(B e) or
334         $(B f) format for $(B g); $(B E) or $(B F) format for
335         $(B G).
336         The $(B f) format is used if the exponent for an $(B e) format
337         is greater than -5 and less than the $(I Precision).
338         The $(I Precision) specifies the number of significant
339         digits, and defaults to six.
340         Trailing zeros are elided after the decimal point, if the fractional
341         part is zero then no decimal point is generated.)
342 
343         $(DT $(B 'a','A'))
344         $(DD A floating point number is formatted in hexadecimal
345         exponential notation 0x$(I h.hhhhhh)p$(I &plusmn;d).
346         There is one hexadecimal digit before the decimal point, and as
347         many after as specified by the $(I Precision).
348         If the $(I Precision) is zero, no decimal point is generated.
349         If there is no $(I Precision), as many hexadecimal digits as
350         necessary to exactly represent the mantissa are generated.
351         The exponent is written in as few digits as possible,
352         but at least one, is in decimal, and represents a power of 2 as in
353         $(I h.hhhhhh)*2<sup>$(I &plusmn;d)</sup>.
354         The exponent for zero is zero.
355         The hexadecimal digits, x and p are in upper case if the
356         $(I FormatChar) is upper case.)
357     )
358 
359     Floating point NaN's are formatted as $(B nan) if the
360     $(I FormatChar) is lower case, or $(B NAN) if upper.
361     Floating point infinities are formatted as $(B inf) or
362     $(B infinity) if the
363     $(I FormatChar) is lower case, or $(B INF) or $(B INFINITY) if upper.
364     ))
365 
366 Example:
367 
368 -------------------------
369 import core.stdc.stdio;
370 import std.format;
371 
372 void myPrint(...)
373 {
374     void putc(dchar c)
375     {
376         fputc(c, stdout);
377     }
378 
379     std.format.doFormat(&putc, _arguments, _argptr);
380 }
381 
382 void main()
383 {
384     int x = 27;
385 
386     // prints 'The answer is 27:6'
387     myPrint("The answer is %s:", x, 6);
388 }
389 ------------------------
390  */
391 void doFormat()(scope void delegate(dchar) putc, TypeInfo[] arguments, va_list ap)
392 {
393     version (GNU)
394     {
395         static assert(false, "GNU D compiler does not support doFormat");
396     }
397 
398     import std.utf : encode, toUCSindex, isValidDchar, UTFException, toUTF8;
399     import core.stdc.string : strlen;
400     import core.stdc.stdlib : alloca, malloc, realloc, free;
401     import core.stdc.stdio : snprintf;
402 
403     size_t bufLength = 1024;
404     void* argBuffer = malloc(bufLength);
405     scope(exit) free(argBuffer);
406 
407     size_t bufUsed = 0;
408     foreach (ti; arguments)
409     {
410         // Ensure the required alignment
411         bufUsed += ti.talign - 1;
412         bufUsed -= (cast(size_t)argBuffer + bufUsed) & (ti.talign - 1);
413         auto pos = bufUsed;
414         // Align to next word boundary
415         bufUsed += ti.tsize + size_t.sizeof - 1;
416         bufUsed -= (cast(size_t)argBuffer + bufUsed) & (size_t.sizeof - 1);
417         // Resize buffer if necessary
418         while (bufUsed > bufLength)
419         {
420             bufLength *= 2;
421             argBuffer = realloc(argBuffer, bufLength);
422         }
423         // Copy argument into buffer
424         va_arg(ap, ti, argBuffer + pos);
425     }
426 
427     auto argptr = argBuffer;
428     void* skipArg(TypeInfo ti)
429     {
430         // Ensure the required alignment
431         argptr += ti.talign - 1;
432         argptr -= cast(size_t)argptr & (ti.talign - 1);
433         auto p = argptr;
434         // Align to next word boundary
435         argptr += ti.tsize + size_t.sizeof - 1;
436         argptr -= cast(size_t)argptr & (size_t.sizeof - 1);
437         return p;
438     }
439     auto getArg(T)()
440     {
441         return *cast(T*)skipArg(typeid(T));
442     }
443 
444     TypeInfo ti;
445     Mangle m;
446     uint flags;
447     int field_width;
448     int precision;
449 
450     enum : uint
451     {
452         FLdash = 1,
453         FLplus = 2,
454         FLspace = 4,
455         FLhash = 8,
456         FLlngdbl = 0x20,
457         FL0pad = 0x40,
458         FLprecision = 0x80,
459     }
460 
461     static TypeInfo skipCI(TypeInfo valti)
462     {
463         for (;;)
464         {
465             if (typeid(valti).name.length == 18 &&
466                     typeid(valti).name[9..18] == "Invariant")
467                 valti = (cast(TypeInfo_Invariant)valti).base;
468             else if (typeid(valti).name.length == 14 &&
469                     typeid(valti).name[9..14] == "Const")
470                 valti = (cast(TypeInfo_Const)valti).base;
471             else
472                 break;
473         }
474 
475         return valti;
476     }
477 
478     void formatArg(char fc)
479     {
480         bool vbit;
481         ulong vnumber;
482         char vchar;
483         dchar vdchar;
484         Object vobject;
485         real vreal;
486         Mangle m2;
487         int signed = 0;
488         uint base = 10;
489         int uc;
490         char[ulong.sizeof * 8] tmpbuf; // long enough to print long in binary
491         const(char)* prefix = "";
492         string s;
493 
494         void putstr(const char[] s)
495         {
496             //printf("putstr: s = %.*s, flags = x%x\n", s.length, s.ptr, flags);
497             ptrdiff_t padding = field_width -
498                 (strlen(prefix) + toUCSindex(s, s.length));
499             ptrdiff_t prepad = 0;
500             ptrdiff_t postpad = 0;
501             if (padding > 0)
502             {
503                 if (flags & FLdash)
504                     postpad = padding;
505                 else
506                     prepad = padding;
507             }
508 
509             if (flags & FL0pad)
510             {
511                 while (*prefix)
512                     putc(*prefix++);
513                 while (prepad--)
514                     putc('0');
515             }
516             else
517             {
518                 while (prepad--)
519                     putc(' ');
520                 while (*prefix)
521                     putc(*prefix++);
522             }
523 
524             foreach (dchar c; s)
525                 putc(c);
526 
527             while (postpad--)
528                 putc(' ');
529         }
530 
531         void putreal(real v)
532         {
533             //printf("putreal %Lg\n", vreal);
534 
535             switch (fc)
536             {
537                 case 's':
538                     fc = 'g';
539                     break;
540 
541                 case 'f', 'F', 'e', 'E', 'g', 'G', 'a', 'A':
542                     break;
543 
544                 default:
545                     //printf("fc = '%c'\n", fc);
546                 Lerror:
547                     throw new FormatException("incompatible format character for floating point type");
548             }
549             version (DigitalMarsC)
550             {
551                 uint sl;
552                 char[] fbuf = tmpbuf;
553                 if (!(flags & FLprecision))
554                     precision = 6;
555                 while (1)
556                 {
557                     sl = fbuf.length;
558                     prefix = (*__pfloatfmt)(fc, flags | FLlngdbl,
559                             precision, &v, cast(char*)fbuf, &sl, field_width);
560                     if (sl != -1)
561                         break;
562                     sl = fbuf.length * 2;
563                     fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl];
564                 }
565                 putstr(fbuf[0 .. sl]);
566             }
567             else
568             {
569                 ptrdiff_t sl;
570                 char[] fbuf = tmpbuf;
571                 char[12] format;
572                 format[0] = '%';
573                 int i = 1;
574                 if (flags & FLdash)
575                     format[i++] = '-';
576                 if (flags & FLplus)
577                     format[i++] = '+';
578                 if (flags & FLspace)
579                     format[i++] = ' ';
580                 if (flags & FLhash)
581                     format[i++] = '#';
582                 if (flags & FL0pad)
583                     format[i++] = '0';
584                 format[i + 0] = '*';
585                 format[i + 1] = '.';
586                 format[i + 2] = '*';
587                 format[i + 3] = 'L';
588                 format[i + 4] = fc;
589                 format[i + 5] = 0;
590                 if (!(flags & FLprecision))
591                     precision = -1;
592                 while (1)
593                 {
594                     sl = fbuf.length;
595                     int n;
596                     version (CRuntime_Microsoft)
597                     {
598                         import std.math : isNaN, isInfinity;
599                         if (isNaN(v)) // snprintf writes 1.#QNAN
600                             n = snprintf(fbuf.ptr, sl, "nan");
601                         else if (isInfinity(v)) // snprintf writes 1.#INF
602                             n = snprintf(fbuf.ptr, sl, v < 0 ? "-inf" : "inf");
603                         else
604                             n = snprintf(fbuf.ptr, sl, format.ptr, field_width,
605                                          precision, cast(double)v);
606                     }
607                     else
608                         n = snprintf(fbuf.ptr, sl, format.ptr, field_width,
609                                 precision, v);
610                     //printf("format = '%s', n = %d\n", cast(char*)format, n);
611                     if (n >= 0 && n < sl)
612                     {        sl = n;
613                         break;
614                     }
615                     if (n < 0)
616                         sl = sl * 2;
617                     else
618                         sl = n + 1;
619                     fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl];
620                 }
621                 putstr(fbuf[0 .. sl]);
622             }
623             return;
624         }
625 
626         static Mangle getMan(TypeInfo ti)
627         {
628           auto m = cast(Mangle)typeid(ti).name[9];
629           if (typeid(ti).name.length == 20 &&
630               typeid(ti).name[9..20] == "StaticArray")
631                 m = cast(Mangle)'G';
632           return m;
633         }
634 
635         /* p = pointer to the first element in the array
636          * len = number of elements in the array
637          * valti = type of the elements
638          */
639         void putArray(void* p, size_t len, TypeInfo valti)
640         {
641           //printf("\nputArray(len = %u), tsize = %u\n", len, valti.tsize);
642           putc('[');
643           valti = skipCI(valti);
644           size_t tsize = valti.tsize;
645           auto argptrSave = argptr;
646           auto tiSave = ti;
647           auto mSave = m;
648           ti = valti;
649           //printf("\n%.*s\n", typeid(valti).name.length, typeid(valti).name.ptr);
650           m = getMan(valti);
651           while (len--)
652           {
653             //doFormat(putc, (&valti)[0 .. 1], p);
654             argptr = p;
655             formatArg('s');
656             p += tsize;
657             if (len > 0) putc(',');
658           }
659           m = mSave;
660           ti = tiSave;
661           argptr = argptrSave;
662           putc(']');
663         }
664 
665         void putAArray(ubyte[long] vaa, TypeInfo valti, TypeInfo keyti)
666         {
667             putc('[');
668             bool comma=false;
669             auto argptrSave = argptr;
670             auto tiSave = ti;
671             auto mSave = m;
672             valti = skipCI(valti);
673             keyti = skipCI(keyti);
674             foreach (ref fakevalue; vaa)
675             {
676                 if (comma) putc(',');
677                 comma = true;
678                 void *pkey = &fakevalue;
679                 version (D_LP64)
680                     pkey -= (long.sizeof + 15) & ~(15);
681                 else
682                     pkey -= (long.sizeof + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
683 
684                 // the key comes before the value
685                 auto keysize = keyti.tsize;
686                 version (D_LP64)
687                     auto keysizet = (keysize + 15) & ~(15);
688                 else
689                     auto keysizet = (keysize + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
690 
691                 void* pvalue = pkey + keysizet;
692 
693                 //doFormat(putc, (&keyti)[0..1], pkey);
694                 m = getMan(keyti);
695                 argptr = pkey;
696 
697                 ti = keyti;
698                 formatArg('s');
699 
700                 putc(':');
701                 //doFormat(putc, (&valti)[0..1], pvalue);
702                 m = getMan(valti);
703                 argptr = pvalue;
704 
705                 ti = valti;
706                 formatArg('s');
707             }
708             m = mSave;
709             ti = tiSave;
710             argptr = argptrSave;
711             putc(']');
712         }
713 
714         //printf("formatArg(fc = '%c', m = '%c')\n", fc, m);
715         int mi;
716         switch (m)
717         {
718             case Mangle.Tbool:
719                 vbit = getArg!(bool)();
720                 if (fc != 's')
721                 {   vnumber = vbit;
722                     goto Lnumber;
723                 }
724                 putstr(vbit ? "true" : "false");
725                 return;
726 
727             case Mangle.Tchar:
728                 vchar = getArg!(char)();
729                 if (fc != 's')
730                 {   vnumber = vchar;
731                     goto Lnumber;
732                 }
733             L2:
734                 putstr((&vchar)[0 .. 1]);
735                 return;
736 
737             case Mangle.Twchar:
738                 vdchar = getArg!(wchar)();
739                 goto L1;
740 
741             case Mangle.Tdchar:
742                 vdchar = getArg!(dchar)();
743             L1:
744                 if (fc != 's')
745                 {   vnumber = vdchar;
746                     goto Lnumber;
747                 }
748                 if (vdchar <= 0x7F)
749                 {   vchar = cast(char)vdchar;
750                     goto L2;
751                 }
752                 else
753                 {   if (!isValidDchar(vdchar))
754                         throw new UTFException("invalid dchar in format");
755                     char[4] vbuf;
756                     putstr(vbuf[0 .. encode(vbuf, vdchar)]);
757                 }
758                 return;
759 
760             case Mangle.Tbyte:
761                 signed = 1;
762                 vnumber = getArg!(byte)();
763                 goto Lnumber;
764 
765             case Mangle.Tubyte:
766                 vnumber = getArg!(ubyte)();
767                 goto Lnumber;
768 
769             case Mangle.Tshort:
770                 signed = 1;
771                 vnumber = getArg!(short)();
772                 goto Lnumber;
773 
774             case Mangle.Tushort:
775                 vnumber = getArg!(ushort)();
776                 goto Lnumber;
777 
778             case Mangle.Tint:
779                 signed = 1;
780                 vnumber = getArg!(int)();
781                 goto Lnumber;
782 
783             case Mangle.Tuint:
784             Luint:
785                 vnumber = getArg!(uint)();
786                 goto Lnumber;
787 
788             case Mangle.Tlong:
789                 signed = 1;
790                 vnumber = cast(ulong)getArg!(long)();
791                 goto Lnumber;
792 
793             case Mangle.Tulong:
794             Lulong:
795                 vnumber = getArg!(ulong)();
796                 goto Lnumber;
797 
798             case Mangle.Tclass:
799                 vobject = getArg!(Object)();
800                 if (vobject is null)
801                     s = "null";
802                 else
803                     s = vobject.toString();
804                 goto Lputstr;
805 
806             case Mangle.Tpointer:
807                 vnumber = cast(ulong)getArg!(void*)();
808                 if (fc != 'x')  uc = 1;
809                 flags |= FL0pad;
810                 if (!(flags & FLprecision))
811                 {   flags |= FLprecision;
812                     precision = (void*).sizeof;
813                 }
814                 base = 16;
815                 goto Lnumber;
816 
817             case Mangle.Tfloat:
818                 if (fc == 'x' || fc == 'X')
819                     goto Luint;
820                 vreal = getArg!(float)();
821                 goto Lreal;
822 
823             case Mangle.Tdouble:
824                 if (fc == 'x' || fc == 'X')
825                     goto Lulong;
826                 vreal = getArg!(double)();
827                 goto Lreal;
828 
829             case Mangle.Treal:
830                 vreal = getArg!(real)();
831                 goto Lreal;
832 
833             case Mangle.Tsarray:
834                 putArray(argptr, (cast(TypeInfo_StaticArray)ti).len, (cast(TypeInfo_StaticArray)ti).next);
835                 return;
836 
837             case Mangle.Tarray:
838                 mi = 10;
839                 if (typeid(ti).name.length == 14 &&
840                     typeid(ti).name[9..14] == "Array")
841                 { // array of non-primitive types
842                   TypeInfo tn = (cast(TypeInfo_Array)ti).next;
843                   tn = skipCI(tn);
844                   switch (cast(Mangle)typeid(tn).name[9])
845                   {
846                     case Mangle.Tchar:  goto LarrayChar;
847                     case Mangle.Twchar: goto LarrayWchar;
848                     case Mangle.Tdchar: goto LarrayDchar;
849                     default:
850                         break;
851                   }
852                   void[] va = getArg!(void[])();
853                   putArray(va.ptr, va.length, tn);
854                   return;
855                 }
856                 if (typeid(ti).name.length == 25 &&
857                     typeid(ti).name[9..25] == "AssociativeArray")
858                 { // associative array
859                   ubyte[long] vaa = getArg!(ubyte[long])();
860                   putAArray(vaa,
861                         (cast(TypeInfo_AssociativeArray)ti).next,
862                         (cast(TypeInfo_AssociativeArray)ti).key);
863                   return;
864                 }
865 
866                 while (1)
867                 {
868                     m2 = cast(Mangle)typeid(ti).name[mi];
869                     switch (m2)
870                     {
871                         case Mangle.Tchar:
872                         LarrayChar:
873                             s = getArg!(string)();
874                             goto Lputstr;
875 
876                         case Mangle.Twchar:
877                         LarrayWchar:
878                             wchar[] sw = getArg!(wchar[])();
879                             s = toUTF8(sw);
880                             goto Lputstr;
881 
882                         case Mangle.Tdchar:
883                         LarrayDchar:
884                             s = toUTF8(getArg!(dstring)());
885                         Lputstr:
886                             if (fc != 's')
887                                 throw new FormatException("string");
888                             if (flags & FLprecision && precision < s.length)
889                                 s = s[0 .. precision];
890                             putstr(s);
891                             break;
892 
893                         case Mangle.Tconst:
894                         case Mangle.Timmutable:
895                             mi++;
896                             continue;
897 
898                         default:
899                             TypeInfo ti2 = primitiveTypeInfo(m2);
900                             if (!ti2)
901                               goto Lerror;
902                             void[] va = getArg!(void[])();
903                             putArray(va.ptr, va.length, ti2);
904                     }
905                     return;
906                 }
907                 assert(0);
908 
909             case Mangle.Tenum:
910                 ti = (cast(TypeInfo_Enum)ti).base;
911                 m = cast(Mangle)typeid(ti).name[9];
912                 formatArg(fc);
913                 return;
914 
915             case Mangle.Tstruct:
916             {        TypeInfo_Struct tis = cast(TypeInfo_Struct)ti;
917                 if (tis.xtoString is null)
918                     throw new FormatException("Can't convert " ~ tis.toString()
919                             ~ " to string: \"string toString()\" not defined");
920                 s = tis.xtoString(skipArg(tis));
921                 goto Lputstr;
922             }
923 
924             default:
925                 goto Lerror;
926         }
927 
928     Lnumber:
929         switch (fc)
930         {
931             case 's':
932             case 'd':
933                 if (signed)
934                 {   if (cast(long)vnumber < 0)
935                     {        prefix = "-";
936                         vnumber = -vnumber;
937                     }
938                     else if (flags & FLplus)
939                         prefix = "+";
940                     else if (flags & FLspace)
941                         prefix = " ";
942                 }
943                 break;
944 
945             case 'b':
946                 signed = 0;
947                 base = 2;
948                 break;
949 
950             case 'o':
951                 signed = 0;
952                 base = 8;
953                 break;
954 
955             case 'X':
956                 uc = 1;
957                 if (flags & FLhash && vnumber)
958                     prefix = "0X";
959                 signed = 0;
960                 base = 16;
961                 break;
962 
963             case 'x':
964                 if (flags & FLhash && vnumber)
965                     prefix = "0x";
966                 signed = 0;
967                 base = 16;
968                 break;
969 
970             default:
971                 goto Lerror;
972         }
973 
974         if (!signed)
975         {
976             switch (m)
977             {
978                 case Mangle.Tbyte:
979                     vnumber &= 0xFF;
980                     break;
981 
982                 case Mangle.Tshort:
983                     vnumber &= 0xFFFF;
984                     break;
985 
986                 case Mangle.Tint:
987                     vnumber &= 0xFFFFFFFF;
988                     break;
989 
990                 default:
991                     break;
992             }
993         }
994 
995         if (flags & FLprecision && fc != 'p')
996             flags &= ~FL0pad;
997 
998         if (vnumber < base)
999         {
1000             if (vnumber == 0 && precision == 0 && flags & FLprecision &&
1001                 !(fc == 'o' && flags & FLhash))
1002             {
1003                 putstr(null);
1004                 return;
1005             }
1006             if (precision == 0 || !(flags & FLprecision))
1007             {        vchar = cast(char)('0' + vnumber);
1008                 if (vnumber < 10)
1009                     vchar = cast(char)('0' + vnumber);
1010                 else
1011                     vchar = cast(char)((uc ? 'A' - 10 : 'a' - 10) + vnumber);
1012                 goto L2;
1013             }
1014         }
1015 
1016         {
1017             ptrdiff_t n = tmpbuf.length;
1018             char c;
1019             int hexoffset = uc ? ('A' - ('9' + 1)) : ('a' - ('9' + 1));
1020 
1021             while (vnumber)
1022             {
1023                 c = cast(char)((vnumber % base) + '0');
1024                 if (c > '9')
1025                     c += hexoffset;
1026                 vnumber /= base;
1027                 tmpbuf[--n] = c;
1028             }
1029             if (tmpbuf.length - n < precision && precision < tmpbuf.length)
1030             {
1031                 ptrdiff_t m = tmpbuf.length - precision;
1032                 tmpbuf[m .. n] = '0';
1033                 n = m;
1034             }
1035             else if (flags & FLhash && fc == 'o')
1036                 prefix = "0";
1037             putstr(tmpbuf[n .. tmpbuf.length]);
1038             return;
1039         }
1040 
1041     Lreal:
1042         putreal(vreal);
1043         return;
1044 
1045     Lerror:
1046         throw new FormatException("formatArg");
1047     }
1048 
1049     for (int j = 0; j < arguments.length; )
1050     {
1051         ti = arguments[j++];
1052         //printf("arg[%d]: '%.*s' %d\n", j, typeid(ti).name.length, typeid(ti).name.ptr, typeid(ti).name.length);
1053         //ti.print();
1054 
1055         flags = 0;
1056         precision = 0;
1057         field_width = 0;
1058 
1059         ti = skipCI(ti);
1060         int mi = 9;
1061         do
1062         {
1063             if (typeid(ti).name.length <= mi)
1064                 goto Lerror;
1065             m = cast(Mangle)typeid(ti).name[mi++];
1066         } while (m == Mangle.Tconst || m == Mangle.Timmutable);
1067 
1068         if (m == Mangle.Tarray)
1069         {
1070             if (typeid(ti).name.length == 14 &&
1071                     typeid(ti).name[9..14] == "Array")
1072             {
1073                 TypeInfo tn = (cast(TypeInfo_Array)ti).next;
1074                 tn = skipCI(tn);
1075                 switch (cast(Mangle)typeid(tn).name[9])
1076                 {
1077                 case Mangle.Tchar:
1078                 case Mangle.Twchar:
1079                 case Mangle.Tdchar:
1080                     ti = tn;
1081                     mi = 9;
1082                     break;
1083                 default:
1084                     break;
1085                 }
1086             }
1087           L1:
1088             Mangle m2 = cast(Mangle)typeid(ti).name[mi];
1089             string  fmt;                        // format string
1090             wstring wfmt;
1091             dstring dfmt;
1092 
1093             /* For performance reasons, this code takes advantage of the
1094              * fact that most format strings will be ASCII, and that the
1095              * format specifiers are always ASCII. This means we only need
1096              * to deal with UTF in a couple of isolated spots.
1097              */
1098 
1099             switch (m2)
1100             {
1101             case Mangle.Tchar:
1102                 fmt = getArg!(string)();
1103                 break;
1104 
1105             case Mangle.Twchar:
1106                 wfmt = getArg!(wstring)();
1107                 fmt = toUTF8(wfmt);
1108                 break;
1109 
1110             case Mangle.Tdchar:
1111                 dfmt = getArg!(dstring)();
1112                 fmt = toUTF8(dfmt);
1113                 break;
1114 
1115             case Mangle.Tconst:
1116             case Mangle.Timmutable:
1117                 mi++;
1118                 goto L1;
1119 
1120             default:
1121                 formatArg('s');
1122                 continue;
1123             }
1124 
1125             for (size_t i = 0; i < fmt.length; )
1126             {        dchar c = fmt[i++];
1127 
1128                 dchar getFmtChar()
1129                 {   // Valid format specifier characters will never be UTF
1130                     if (i == fmt.length)
1131                         throw new FormatException("invalid specifier");
1132                     return fmt[i++];
1133                 }
1134 
1135                 int getFmtInt()
1136                 {   int n;
1137 
1138                     while (1)
1139                     {
1140                         n = n * 10 + (c - '0');
1141                         if (n < 0)        // overflow
1142                             throw new FormatException("int overflow");
1143                         c = getFmtChar();
1144                         if (c < '0' || c > '9')
1145                             break;
1146                     }
1147                     return n;
1148                 }
1149 
1150                 int getFmtStar()
1151                 {   Mangle m;
1152                     TypeInfo ti;
1153 
1154                     if (j == arguments.length)
1155                         throw new FormatException("too few arguments");
1156                     ti = arguments[j++];
1157                     m = cast(Mangle)typeid(ti).name[9];
1158                     if (m != Mangle.Tint)
1159                         throw new FormatException("int argument expected");
1160                     return getArg!(int)();
1161                 }
1162 
1163                 if (c != '%')
1164                 {
1165                     if (c > 0x7F)        // if UTF sequence
1166                     {
1167                         i--;                // back up and decode UTF sequence
1168                         import std.utf : decode;
1169                         c = decode(fmt, i);
1170                     }
1171                   Lputc:
1172                     putc(c);
1173                     continue;
1174                 }
1175 
1176                 // Get flags {-+ #}
1177                 flags = 0;
1178                 while (1)
1179                 {
1180                     c = getFmtChar();
1181                     switch (c)
1182                     {
1183                     case '-':        flags |= FLdash;        continue;
1184                     case '+':        flags |= FLplus;        continue;
1185                     case ' ':        flags |= FLspace;        continue;
1186                     case '#':        flags |= FLhash;        continue;
1187                     case '0':        flags |= FL0pad;        continue;
1188 
1189                     case '%':        if (flags == 0)
1190                                           goto Lputc;
1191                                      break;
1192 
1193                     default:         break;
1194                     }
1195                     break;
1196                 }
1197 
1198                 // Get field width
1199                 field_width = 0;
1200                 if (c == '*')
1201                 {
1202                     field_width = getFmtStar();
1203                     if (field_width < 0)
1204                     {   flags |= FLdash;
1205                         field_width = -field_width;
1206                     }
1207 
1208                     c = getFmtChar();
1209                 }
1210                 else if (c >= '0' && c <= '9')
1211                     field_width = getFmtInt();
1212 
1213                 if (flags & FLplus)
1214                     flags &= ~FLspace;
1215                 if (flags & FLdash)
1216                     flags &= ~FL0pad;
1217 
1218                 // Get precision
1219                 precision = 0;
1220                 if (c == '.')
1221                 {   flags |= FLprecision;
1222                     //flags &= ~FL0pad;
1223 
1224                     c = getFmtChar();
1225                     if (c == '*')
1226                     {
1227                         precision = getFmtStar();
1228                         if (precision < 0)
1229                         {   precision = 0;
1230                             flags &= ~FLprecision;
1231                         }
1232 
1233                         c = getFmtChar();
1234                     }
1235                     else if (c >= '0' && c <= '9')
1236                         precision = getFmtInt();
1237                 }
1238 
1239                 if (j == arguments.length)
1240                     goto Lerror;
1241                 ti = arguments[j++];
1242                 ti = skipCI(ti);
1243                 mi = 9;
1244                 do
1245                 {
1246                     m = cast(Mangle)typeid(ti).name[mi++];
1247                 } while (m == Mangle.Tconst || m == Mangle.Timmutable);
1248 
1249                 if (c > 0x7F)                // if UTF sequence
1250                     goto Lerror;        // format specifiers can't be UTF
1251                 formatArg(cast(char)c);
1252             }
1253         }
1254         else
1255         {
1256             formatArg('s');
1257         }
1258     }
1259     return;
1260 
1261   Lerror:
1262     throw new FormatException();
1263 }
1264 
1265 
1266 private bool needToSwapEndianess(Char)(ref FormatSpec!Char f)
1267 {
1268     import std.system : endian, Endian;
1269 
1270     return endian == Endian.littleEndian && f.flPlus
1271         || endian == Endian.bigEndian && f.flDash;
1272 }
1273 
1274 version(GNU) {} else
1275 unittest
1276 {
1277     string res;
1278     void putc(dchar c)
1279     {
1280         res ~= c;
1281     }
1282 
1283     void myPrint(...)
1284     {
1285         undead.doformat.doFormat(&putc, _arguments, _argptr);
1286     }
1287 
1288     myPrint("The answer is %s:", 27, 6);
1289     assert(res == "The answer is 27:6");
1290 }