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