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     import std.utf : encode, toUCSindex, isValidDchar, UTFException, toUTF8;
394     import core.stdc.string : strlen;
395     import core.stdc.stdlib : alloca, malloc, realloc, free;
396     import core.stdc.stdio : snprintf;
397 
398     size_t bufLength = 1024;
399     void* argBuffer = malloc(bufLength);
400     scope(exit) free(argBuffer);
401 
402     size_t bufUsed = 0;
403     foreach (ti; arguments)
404     {
405         // Ensure the required alignment
406         bufUsed += ti.talign - 1;
407         bufUsed -= (cast(size_t)argBuffer + bufUsed) & (ti.talign - 1);
408         auto pos = bufUsed;
409         // Align to next word boundary
410         bufUsed += ti.tsize + size_t.sizeof - 1;
411         bufUsed -= (cast(size_t)argBuffer + bufUsed) & (size_t.sizeof - 1);
412         // Resize buffer if necessary
413         while (bufUsed > bufLength)
414         {
415             bufLength *= 2;
416             argBuffer = realloc(argBuffer, bufLength);
417         }
418         // Copy argument into buffer
419         va_arg(ap, ti, argBuffer + pos);
420     }
421 
422     auto argptr = argBuffer;
423     void* skipArg(TypeInfo ti)
424     {
425         // Ensure the required alignment
426         argptr += ti.talign - 1;
427         argptr -= cast(size_t)argptr & (ti.talign - 1);
428         auto p = argptr;
429         // Align to next word boundary
430         argptr += ti.tsize + size_t.sizeof - 1;
431         argptr -= cast(size_t)argptr & (size_t.sizeof - 1);
432         return p;
433     }
434     auto getArg(T)()
435     {
436         return *cast(T*)skipArg(typeid(T));
437     }
438 
439     TypeInfo ti;
440     Mangle m;
441     uint flags;
442     int field_width;
443     int precision;
444 
445     enum : uint
446     {
447         FLdash = 1,
448         FLplus = 2,
449         FLspace = 4,
450         FLhash = 8,
451         FLlngdbl = 0x20,
452         FL0pad = 0x40,
453         FLprecision = 0x80,
454     }
455 
456     static TypeInfo skipCI(TypeInfo valti)
457     {
458         for (;;)
459         {
460             if (typeid(valti).name.length == 18 &&
461                     typeid(valti).name[9..18] == "Invariant")
462                 valti = (cast(TypeInfo_Invariant)valti).base;
463             else if (typeid(valti).name.length == 14 &&
464                     typeid(valti).name[9..14] == "Const")
465                 valti = (cast(TypeInfo_Const)valti).base;
466             else
467                 break;
468         }
469 
470         return valti;
471     }
472 
473     void formatArg(char fc)
474     {
475         bool vbit;
476         ulong vnumber;
477         char vchar;
478         dchar vdchar;
479         Object vobject;
480         real vreal;
481         Mangle m2;
482         int signed = 0;
483         uint base = 10;
484         int uc;
485         char[ulong.sizeof * 8] tmpbuf; // long enough to print long in binary
486         const(char)* prefix = "";
487         string s;
488 
489         void putstr(const char[] s)
490         {
491             //printf("putstr: s = %.*s, flags = x%x\n", s.length, s.ptr, flags);
492             ptrdiff_t padding = field_width -
493                 (strlen(prefix) + toUCSindex(s, s.length));
494             ptrdiff_t prepad = 0;
495             ptrdiff_t postpad = 0;
496             if (padding > 0)
497             {
498                 if (flags & FLdash)
499                     postpad = padding;
500                 else
501                     prepad = padding;
502             }
503 
504             if (flags & FL0pad)
505             {
506                 while (*prefix)
507                     putc(*prefix++);
508                 while (prepad--)
509                     putc('0');
510             }
511             else
512             {
513                 while (prepad--)
514                     putc(' ');
515                 while (*prefix)
516                     putc(*prefix++);
517             }
518 
519             foreach (dchar c; s)
520                 putc(c);
521 
522             while (postpad--)
523                 putc(' ');
524         }
525 
526         void putreal(real v)
527         {
528             //printf("putreal %Lg\n", vreal);
529 
530             switch (fc)
531             {
532                 case 's':
533                     fc = 'g';
534                     break;
535 
536                 case 'f', 'F', 'e', 'E', 'g', 'G', 'a', 'A':
537                     break;
538 
539                 default:
540                     //printf("fc = '%c'\n", fc);
541                 Lerror:
542                     throw new FormatException("incompatible format character for floating point type");
543             }
544             version (DigitalMarsC)
545             {
546                 uint sl;
547                 char[] fbuf = tmpbuf;
548                 if (!(flags & FLprecision))
549                     precision = 6;
550                 while (1)
551                 {
552                     sl = fbuf.length;
553                     prefix = (*__pfloatfmt)(fc, flags | FLlngdbl,
554                             precision, &v, cast(char*)fbuf, &sl, field_width);
555                     if (sl != -1)
556                         break;
557                     sl = fbuf.length * 2;
558                     fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl];
559                 }
560                 putstr(fbuf[0 .. sl]);
561             }
562             else
563             {
564                 ptrdiff_t sl;
565                 char[] fbuf = tmpbuf;
566                 char[12] format;
567                 format[0] = '%';
568                 int i = 1;
569                 if (flags & FLdash)
570                     format[i++] = '-';
571                 if (flags & FLplus)
572                     format[i++] = '+';
573                 if (flags & FLspace)
574                     format[i++] = ' ';
575                 if (flags & FLhash)
576                     format[i++] = '#';
577                 if (flags & FL0pad)
578                     format[i++] = '0';
579                 format[i + 0] = '*';
580                 format[i + 1] = '.';
581                 format[i + 2] = '*';
582                 format[i + 3] = 'L';
583                 format[i + 4] = fc;
584                 format[i + 5] = 0;
585                 if (!(flags & FLprecision))
586                     precision = -1;
587                 while (1)
588                 {
589                     sl = fbuf.length;
590                     int n;
591                     version (CRuntime_Microsoft)
592                     {
593                         import std.math : isNaN, isInfinity;
594                         if (isNaN(v)) // snprintf writes 1.#QNAN
595                             n = snprintf(fbuf.ptr, sl, "nan");
596                         else if (isInfinity(v)) // snprintf writes 1.#INF
597                             n = snprintf(fbuf.ptr, sl, v < 0 ? "-inf" : "inf");
598                         else
599                             n = snprintf(fbuf.ptr, sl, format.ptr, field_width,
600                                          precision, cast(double)v);
601                     }
602                     else
603                         n = snprintf(fbuf.ptr, sl, format.ptr, field_width,
604                                 precision, v);
605                     //printf("format = '%s', n = %d\n", cast(char*)format, n);
606                     if (n >= 0 && n < sl)
607                     {        sl = n;
608                         break;
609                     }
610                     if (n < 0)
611                         sl = sl * 2;
612                     else
613                         sl = n + 1;
614                     fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl];
615                 }
616                 putstr(fbuf[0 .. sl]);
617             }
618             return;
619         }
620 
621         static Mangle getMan(TypeInfo ti)
622         {
623           auto m = cast(Mangle)typeid(ti).name[9];
624           if (typeid(ti).name.length == 20 &&
625               typeid(ti).name[9..20] == "StaticArray")
626                 m = cast(Mangle)'G';
627           return m;
628         }
629 
630         /* p = pointer to the first element in the array
631          * len = number of elements in the array
632          * valti = type of the elements
633          */
634         void putArray(void* p, size_t len, TypeInfo valti)
635         {
636           //printf("\nputArray(len = %u), tsize = %u\n", len, valti.tsize);
637           putc('[');
638           valti = skipCI(valti);
639           size_t tsize = valti.tsize;
640           auto argptrSave = argptr;
641           auto tiSave = ti;
642           auto mSave = m;
643           ti = valti;
644           //printf("\n%.*s\n", typeid(valti).name.length, typeid(valti).name.ptr);
645           m = getMan(valti);
646           while (len--)
647           {
648             //doFormat(putc, (&valti)[0 .. 1], p);
649             argptr = p;
650             formatArg('s');
651             p += tsize;
652             if (len > 0) putc(',');
653           }
654           m = mSave;
655           ti = tiSave;
656           argptr = argptrSave;
657           putc(']');
658         }
659 
660         void putAArray(ubyte[long] vaa, TypeInfo valti, TypeInfo keyti)
661         {
662             putc('[');
663             bool comma=false;
664             auto argptrSave = argptr;
665             auto tiSave = ti;
666             auto mSave = m;
667             valti = skipCI(valti);
668             keyti = skipCI(keyti);
669             foreach (ref fakevalue; vaa)
670             {
671                 if (comma) putc(',');
672                 comma = true;
673                 void *pkey = &fakevalue;
674                 version (D_LP64)
675                     pkey -= (long.sizeof + 15) & ~(15);
676                 else
677                     pkey -= (long.sizeof + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
678 
679                 // the key comes before the value
680                 auto keysize = keyti.tsize;
681                 version (D_LP64)
682                     auto keysizet = (keysize + 15) & ~(15);
683                 else
684                     auto keysizet = (keysize + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
685 
686                 void* pvalue = pkey + keysizet;
687 
688                 //doFormat(putc, (&keyti)[0..1], pkey);
689                 m = getMan(keyti);
690                 argptr = pkey;
691 
692                 ti = keyti;
693                 formatArg('s');
694 
695                 putc(':');
696                 //doFormat(putc, (&valti)[0..1], pvalue);
697                 m = getMan(valti);
698                 argptr = pvalue;
699 
700                 ti = valti;
701                 formatArg('s');
702             }
703             m = mSave;
704             ti = tiSave;
705             argptr = argptrSave;
706             putc(']');
707         }
708 
709         //printf("formatArg(fc = '%c', m = '%c')\n", fc, m);
710         int mi;
711         switch (m)
712         {
713             case Mangle.Tbool:
714                 vbit = getArg!(bool)();
715                 if (fc != 's')
716                 {   vnumber = vbit;
717                     goto Lnumber;
718                 }
719                 putstr(vbit ? "true" : "false");
720                 return;
721 
722             case Mangle.Tchar:
723                 vchar = getArg!(char)();
724                 if (fc != 's')
725                 {   vnumber = vchar;
726                     goto Lnumber;
727                 }
728             L2:
729                 putstr((&vchar)[0 .. 1]);
730                 return;
731 
732             case Mangle.Twchar:
733                 vdchar = getArg!(wchar)();
734                 goto L1;
735 
736             case Mangle.Tdchar:
737                 vdchar = getArg!(dchar)();
738             L1:
739                 if (fc != 's')
740                 {   vnumber = vdchar;
741                     goto Lnumber;
742                 }
743                 if (vdchar <= 0x7F)
744                 {   vchar = cast(char)vdchar;
745                     goto L2;
746                 }
747                 else
748                 {   if (!isValidDchar(vdchar))
749                         throw new UTFException("invalid dchar in format");
750                     char[4] vbuf;
751                     putstr(vbuf[0 .. encode(vbuf, vdchar)]);
752                 }
753                 return;
754 
755             case Mangle.Tbyte:
756                 signed = 1;
757                 vnumber = getArg!(byte)();
758                 goto Lnumber;
759 
760             case Mangle.Tubyte:
761                 vnumber = getArg!(ubyte)();
762                 goto Lnumber;
763 
764             case Mangle.Tshort:
765                 signed = 1;
766                 vnumber = getArg!(short)();
767                 goto Lnumber;
768 
769             case Mangle.Tushort:
770                 vnumber = getArg!(ushort)();
771                 goto Lnumber;
772 
773             case Mangle.Tint:
774                 signed = 1;
775                 vnumber = getArg!(int)();
776                 goto Lnumber;
777 
778             case Mangle.Tuint:
779             Luint:
780                 vnumber = getArg!(uint)();
781                 goto Lnumber;
782 
783             case Mangle.Tlong:
784                 signed = 1;
785                 vnumber = cast(ulong)getArg!(long)();
786                 goto Lnumber;
787 
788             case Mangle.Tulong:
789             Lulong:
790                 vnumber = getArg!(ulong)();
791                 goto Lnumber;
792 
793             case Mangle.Tclass:
794                 vobject = getArg!(Object)();
795                 if (vobject is null)
796                     s = "null";
797                 else
798                     s = vobject.toString();
799                 goto Lputstr;
800 
801             case Mangle.Tpointer:
802                 vnumber = cast(ulong)getArg!(void*)();
803                 if (fc != 'x')  uc = 1;
804                 flags |= FL0pad;
805                 if (!(flags & FLprecision))
806                 {   flags |= FLprecision;
807                     precision = (void*).sizeof;
808                 }
809                 base = 16;
810                 goto Lnumber;
811 
812             case Mangle.Tfloat:
813                 if (fc == 'x' || fc == 'X')
814                     goto Luint;
815                 vreal = getArg!(float)();
816                 goto Lreal;
817 
818             case Mangle.Tdouble:
819                 if (fc == 'x' || fc == 'X')
820                     goto Lulong;
821                 vreal = getArg!(double)();
822                 goto Lreal;
823 
824             case Mangle.Treal:
825                 vreal = getArg!(real)();
826                 goto Lreal;
827 
828             case Mangle.Tsarray:
829                 putArray(argptr, (cast(TypeInfo_StaticArray)ti).len, (cast(TypeInfo_StaticArray)ti).next);
830                 return;
831 
832             case Mangle.Tarray:
833                 mi = 10;
834                 if (typeid(ti).name.length == 14 &&
835                     typeid(ti).name[9..14] == "Array")
836                 { // array of non-primitive types
837                   TypeInfo tn = (cast(TypeInfo_Array)ti).next;
838                   tn = skipCI(tn);
839                   switch (cast(Mangle)typeid(tn).name[9])
840                   {
841                     case Mangle.Tchar:  goto LarrayChar;
842                     case Mangle.Twchar: goto LarrayWchar;
843                     case Mangle.Tdchar: goto LarrayDchar;
844                     default:
845                         break;
846                   }
847                   void[] va = getArg!(void[])();
848                   putArray(va.ptr, va.length, tn);
849                   return;
850                 }
851                 if (typeid(ti).name.length == 25 &&
852                     typeid(ti).name[9..25] == "AssociativeArray")
853                 { // associative array
854                   ubyte[long] vaa = getArg!(ubyte[long])();
855                   putAArray(vaa,
856                         (cast(TypeInfo_AssociativeArray)ti).next,
857                         (cast(TypeInfo_AssociativeArray)ti).key);
858                   return;
859                 }
860 
861                 while (1)
862                 {
863                     m2 = cast(Mangle)typeid(ti).name[mi];
864                     switch (m2)
865                     {
866                         case Mangle.Tchar:
867                         LarrayChar:
868                             s = getArg!(string)();
869                             goto Lputstr;
870 
871                         case Mangle.Twchar:
872                         LarrayWchar:
873                             wchar[] sw = getArg!(wchar[])();
874                             s = toUTF8(sw);
875                             goto Lputstr;
876 
877                         case Mangle.Tdchar:
878                         LarrayDchar:
879                             s = toUTF8(getArg!(dstring)());
880                         Lputstr:
881                             if (fc != 's')
882                                 throw new FormatException("string");
883                             if (flags & FLprecision && precision < s.length)
884                                 s = s[0 .. precision];
885                             putstr(s);
886                             break;
887 
888                         case Mangle.Tconst:
889                         case Mangle.Timmutable:
890                             mi++;
891                             continue;
892 
893                         default:
894                             TypeInfo ti2 = primitiveTypeInfo(m2);
895                             if (!ti2)
896                               goto Lerror;
897                             void[] va = getArg!(void[])();
898                             putArray(va.ptr, va.length, ti2);
899                     }
900                     return;
901                 }
902                 assert(0);
903 
904             case Mangle.Tenum:
905                 ti = (cast(TypeInfo_Enum)ti).base;
906                 m = cast(Mangle)typeid(ti).name[9];
907                 formatArg(fc);
908                 return;
909 
910             case Mangle.Tstruct:
911             {        TypeInfo_Struct tis = cast(TypeInfo_Struct)ti;
912                 if (tis.xtoString is null)
913                     throw new FormatException("Can't convert " ~ tis.toString()
914                             ~ " to string: \"string toString()\" not defined");
915                 s = tis.xtoString(skipArg(tis));
916                 goto Lputstr;
917             }
918 
919             default:
920                 goto Lerror;
921         }
922 
923     Lnumber:
924         switch (fc)
925         {
926             case 's':
927             case 'd':
928                 if (signed)
929                 {   if (cast(long)vnumber < 0)
930                     {        prefix = "-";
931                         vnumber = -vnumber;
932                     }
933                     else if (flags & FLplus)
934                         prefix = "+";
935                     else if (flags & FLspace)
936                         prefix = " ";
937                 }
938                 break;
939 
940             case 'b':
941                 signed = 0;
942                 base = 2;
943                 break;
944 
945             case 'o':
946                 signed = 0;
947                 base = 8;
948                 break;
949 
950             case 'X':
951                 uc = 1;
952                 if (flags & FLhash && vnumber)
953                     prefix = "0X";
954                 signed = 0;
955                 base = 16;
956                 break;
957 
958             case 'x':
959                 if (flags & FLhash && vnumber)
960                     prefix = "0x";
961                 signed = 0;
962                 base = 16;
963                 break;
964 
965             default:
966                 goto Lerror;
967         }
968 
969         if (!signed)
970         {
971             switch (m)
972             {
973                 case Mangle.Tbyte:
974                     vnumber &= 0xFF;
975                     break;
976 
977                 case Mangle.Tshort:
978                     vnumber &= 0xFFFF;
979                     break;
980 
981                 case Mangle.Tint:
982                     vnumber &= 0xFFFFFFFF;
983                     break;
984 
985                 default:
986                     break;
987             }
988         }
989 
990         if (flags & FLprecision && fc != 'p')
991             flags &= ~FL0pad;
992 
993         if (vnumber < base)
994         {
995             if (vnumber == 0 && precision == 0 && flags & FLprecision &&
996                 !(fc == 'o' && flags & FLhash))
997             {
998                 putstr(null);
999                 return;
1000             }
1001             if (precision == 0 || !(flags & FLprecision))
1002             {        vchar = cast(char)('0' + vnumber);
1003                 if (vnumber < 10)
1004                     vchar = cast(char)('0' + vnumber);
1005                 else
1006                     vchar = cast(char)((uc ? 'A' - 10 : 'a' - 10) + vnumber);
1007                 goto L2;
1008             }
1009         }
1010 
1011         {
1012             ptrdiff_t n = tmpbuf.length;
1013             char c;
1014             int hexoffset = uc ? ('A' - ('9' + 1)) : ('a' - ('9' + 1));
1015 
1016             while (vnumber)
1017             {
1018                 c = cast(char)((vnumber % base) + '0');
1019                 if (c > '9')
1020                     c += hexoffset;
1021                 vnumber /= base;
1022                 tmpbuf[--n] = c;
1023             }
1024             if (tmpbuf.length - n < precision && precision < tmpbuf.length)
1025             {
1026                 ptrdiff_t m = tmpbuf.length - precision;
1027                 tmpbuf[m .. n] = '0';
1028                 n = m;
1029             }
1030             else if (flags & FLhash && fc == 'o')
1031                 prefix = "0";
1032             putstr(tmpbuf[n .. tmpbuf.length]);
1033             return;
1034         }
1035 
1036     Lreal:
1037         putreal(vreal);
1038         return;
1039 
1040     Lerror:
1041         throw new FormatException("formatArg");
1042     }
1043 
1044     for (int j = 0; j < arguments.length; )
1045     {
1046         ti = arguments[j++];
1047         //printf("arg[%d]: '%.*s' %d\n", j, typeid(ti).name.length, typeid(ti).name.ptr, typeid(ti).name.length);
1048         //ti.print();
1049 
1050         flags = 0;
1051         precision = 0;
1052         field_width = 0;
1053 
1054         ti = skipCI(ti);
1055         int mi = 9;
1056         do
1057         {
1058             if (typeid(ti).name.length <= mi)
1059                 goto Lerror;
1060             m = cast(Mangle)typeid(ti).name[mi++];
1061         } while (m == Mangle.Tconst || m == Mangle.Timmutable);
1062 
1063         if (m == Mangle.Tarray)
1064         {
1065             if (typeid(ti).name.length == 14 &&
1066                     typeid(ti).name[9..14] == "Array")
1067             {
1068                 TypeInfo tn = (cast(TypeInfo_Array)ti).next;
1069                 tn = skipCI(tn);
1070                 switch (cast(Mangle)typeid(tn).name[9])
1071                 {
1072                 case Mangle.Tchar:
1073                 case Mangle.Twchar:
1074                 case Mangle.Tdchar:
1075                     ti = tn;
1076                     mi = 9;
1077                     break;
1078                 default:
1079                     break;
1080                 }
1081             }
1082           L1:
1083             Mangle m2 = cast(Mangle)typeid(ti).name[mi];
1084             string  fmt;                        // format string
1085             wstring wfmt;
1086             dstring dfmt;
1087 
1088             /* For performance reasons, this code takes advantage of the
1089              * fact that most format strings will be ASCII, and that the
1090              * format specifiers are always ASCII. This means we only need
1091              * to deal with UTF in a couple of isolated spots.
1092              */
1093 
1094             switch (m2)
1095             {
1096             case Mangle.Tchar:
1097                 fmt = getArg!(string)();
1098                 break;
1099 
1100             case Mangle.Twchar:
1101                 wfmt = getArg!(wstring)();
1102                 fmt = toUTF8(wfmt);
1103                 break;
1104 
1105             case Mangle.Tdchar:
1106                 dfmt = getArg!(dstring)();
1107                 fmt = toUTF8(dfmt);
1108                 break;
1109 
1110             case Mangle.Tconst:
1111             case Mangle.Timmutable:
1112                 mi++;
1113                 goto L1;
1114 
1115             default:
1116                 formatArg('s');
1117                 continue;
1118             }
1119 
1120             for (size_t i = 0; i < fmt.length; )
1121             {        dchar c = fmt[i++];
1122 
1123                 dchar getFmtChar()
1124                 {   // Valid format specifier characters will never be UTF
1125                     if (i == fmt.length)
1126                         throw new FormatException("invalid specifier");
1127                     return fmt[i++];
1128                 }
1129 
1130                 int getFmtInt()
1131                 {   int n;
1132 
1133                     while (1)
1134                     {
1135                         n = n * 10 + (c - '0');
1136                         if (n < 0)        // overflow
1137                             throw new FormatException("int overflow");
1138                         c = getFmtChar();
1139                         if (c < '0' || c > '9')
1140                             break;
1141                     }
1142                     return n;
1143                 }
1144 
1145                 int getFmtStar()
1146                 {   Mangle m;
1147                     TypeInfo ti;
1148 
1149                     if (j == arguments.length)
1150                         throw new FormatException("too few arguments");
1151                     ti = arguments[j++];
1152                     m = cast(Mangle)typeid(ti).name[9];
1153                     if (m != Mangle.Tint)
1154                         throw new FormatException("int argument expected");
1155                     return getArg!(int)();
1156                 }
1157 
1158                 if (c != '%')
1159                 {
1160                     if (c > 0x7F)        // if UTF sequence
1161                     {
1162                         i--;                // back up and decode UTF sequence
1163                         import std.utf : decode;
1164                         c = decode(fmt, i);
1165                     }
1166                   Lputc:
1167                     putc(c);
1168                     continue;
1169                 }
1170 
1171                 // Get flags {-+ #}
1172                 flags = 0;
1173                 while (1)
1174                 {
1175                     c = getFmtChar();
1176                     switch (c)
1177                     {
1178                     case '-':        flags |= FLdash;        continue;
1179                     case '+':        flags |= FLplus;        continue;
1180                     case ' ':        flags |= FLspace;        continue;
1181                     case '#':        flags |= FLhash;        continue;
1182                     case '0':        flags |= FL0pad;        continue;
1183 
1184                     case '%':        if (flags == 0)
1185                                           goto Lputc;
1186                                      break;
1187 
1188                     default:         break;
1189                     }
1190                     break;
1191                 }
1192 
1193                 // Get field width
1194                 field_width = 0;
1195                 if (c == '*')
1196                 {
1197                     field_width = getFmtStar();
1198                     if (field_width < 0)
1199                     {   flags |= FLdash;
1200                         field_width = -field_width;
1201                     }
1202 
1203                     c = getFmtChar();
1204                 }
1205                 else if (c >= '0' && c <= '9')
1206                     field_width = getFmtInt();
1207 
1208                 if (flags & FLplus)
1209                     flags &= ~FLspace;
1210                 if (flags & FLdash)
1211                     flags &= ~FL0pad;
1212 
1213                 // Get precision
1214                 precision = 0;
1215                 if (c == '.')
1216                 {   flags |= FLprecision;
1217                     //flags &= ~FL0pad;
1218 
1219                     c = getFmtChar();
1220                     if (c == '*')
1221                     {
1222                         precision = getFmtStar();
1223                         if (precision < 0)
1224                         {   precision = 0;
1225                             flags &= ~FLprecision;
1226                         }
1227 
1228                         c = getFmtChar();
1229                     }
1230                     else if (c >= '0' && c <= '9')
1231                         precision = getFmtInt();
1232                 }
1233 
1234                 if (j == arguments.length)
1235                     goto Lerror;
1236                 ti = arguments[j++];
1237                 ti = skipCI(ti);
1238                 mi = 9;
1239                 do
1240                 {
1241                     m = cast(Mangle)typeid(ti).name[mi++];
1242                 } while (m == Mangle.Tconst || m == Mangle.Timmutable);
1243 
1244                 if (c > 0x7F)                // if UTF sequence
1245                     goto Lerror;        // format specifiers can't be UTF
1246                 formatArg(cast(char)c);
1247             }
1248         }
1249         else
1250         {
1251             formatArg('s');
1252         }
1253     }
1254     return;
1255 
1256   Lerror:
1257     throw new FormatException();
1258 }
1259 
1260 
1261 private bool needToSwapEndianess(Char)(ref FormatSpec!Char f)
1262 {
1263     import std.system : endian, Endian;
1264 
1265     return endian == Endian.littleEndian && f.flPlus
1266         || endian == Endian.bigEndian && f.flDash;
1267 }
1268 
1269 unittest
1270 {
1271     string res;
1272     void putc(dchar c)
1273     {
1274         res ~= c;
1275     }
1276 
1277     void myPrint(...)
1278     {
1279         undead.doformat.doFormat(&putc, _arguments, _argptr);
1280     }
1281 
1282     myPrint("The answer is %s:", 27, 6);
1283     assert(res == "The answer is 27:6");
1284 }