Note: This website is archived. For up-to-date information about D projects and development, please visit wiki.dlang.org.

Changeset 1748

Show
Ignore:
Timestamp:
07/12/10 00:49:18 (14 years ago)
Author:
andrei
Message:

Radical overhaul.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/phobos/std/format.d

    r1733 r1748  
    11// Written in the D programming language. 
    22 
    33/** 
    4  * This module implements the workhorse functionality for string and 
    5  * I/O formatting.  It's comparable to C99's vsprintf(). 
    6  * 
    7  * Macros: 
    8  *        WIKI = Phobos/StdFormat 
    9  * 
    10  * Copyright: Copyright Digital Mars 2000-. 
    11  * License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 
    12  * 
    13  * Authors: $(WEB digitalmars.com, Walter Bright), $(WEB 
    14  * erdani.com, Andrei Alexandrescu) 
     4   This module implements the formatting functionality for strings and 
     5   I/O. It's comparable to C99's $(D vsprintf()) and uses a similar 
     6   format encoding scheme. 
     7 
     8   Macros: WIKI = Phobos/StdFormat 
     9 
     10   Copyright: Copyright Digital Mars 2000-. 
     11 
     12   License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 
     13 
     14   Authors: $(WEB digitalmars.com, Walter Bright), $(WEB erdani.com, 
     15   Andrei Alexandrescu) 
    1516 */ 
    1617module std.format; 
    1718 
    1819//debug=format;                // uncomment to turn on debugging printf's 
    1920 
    2021import core.stdc.stdio, core.stdc.stdlib, core.stdc.string; 
    2122import std.algorithm, std.array, std.bitmanip, std.conv, 
    2223    std.ctype, std.exception, std.functional, std.range, std.stdarg, 
    2324    std.string, std.system, std.traits, std.typetuple, std.utf; 
    2425version(unittest) { 
     
    5657    { 
    5758        super("std.format"); 
    5859    } 
    5960 
    6061    this(string msg) 
    6162    { 
    6263        super("std.format " ~ msg); 
    6364    } 
    6465} 
    6566 
    66  
    67 enum Mangle : char 
    68 
    69     Tvoid     = 'v', 
    70     Tbool     = 'b', 
    71     Tbyte     = 'g', 
    72     Tubyte    = 'h', 
    73     Tshort    = 's', 
    74     Tushort   = 't', 
    75     Tint      = 'i', 
    76     Tuint     = 'k', 
    77     Tlong     = 'l', 
    78     Tulong    = 'm', 
    79     Tfloat    = 'f', 
    80     Tdouble   = 'd', 
    81     Treal     = 'e', 
    82  
    83     Tifloat   = 'o', 
    84     Tidouble  = 'p', 
    85     Tireal    = 'j', 
    86     Tcfloat   = 'q', 
    87     Tcdouble  = 'r', 
    88     Tcreal    = 'c', 
    89  
    90     Tchar     = 'a', 
    91     Twchar    = 'u', 
    92     Tdchar    = 'w', 
    93  
    94     Tarray    = 'A', 
    95     Tsarray   = 'G', 
    96     Taarray   = 'H', 
    97     Tpointer  = 'P', 
    98     Tfunction = 'F', 
    99     Tident    = 'I', 
    100     Tclass    = 'C', 
    101     Tstruct   = 'S', 
    102     Tenum     = 'E', 
    103     Ttypedef  = 'T', 
    104     Tdelegate = 'D', 
    105  
    106     Tconst    = 'x', 
    107     Timmutable = 'y', 
    108 
    109  
    110 // return the TypeInfo for a primitive type and null otherwise.  This 
    111 // is required since for arrays of ints we only have the mangled char 
    112 // to work from. If arrays always subclassed TypeInfo_Array this 
    113 // routine could go away. 
    114 private TypeInfo primitiveTypeInfo(Mangle m) 
    115 
    116     // BUG: should fix this in static this() to avoid double checked locking bug 
    117     __gshared TypeInfo[Mangle] dic; 
    118     if (!dic.length) { 
    119         dic = [ 
    120             Mangle.Tvoid : typeid(void), 
    121             Mangle.Tbool : typeid(bool), 
    122             Mangle.Tbyte : typeid(byte), 
    123             Mangle.Tubyte : typeid(ubyte), 
    124             Mangle.Tshort : typeid(short), 
    125             Mangle.Tushort : typeid(ushort), 
    126             Mangle.Tint : typeid(int), 
    127             Mangle.Tuint : typeid(uint), 
    128             Mangle.Tlong : typeid(long), 
    129             Mangle.Tulong : typeid(ulong), 
    130             Mangle.Tfloat : typeid(float), 
    131             Mangle.Tdouble : typeid(double), 
    132             Mangle.Treal : typeid(real), 
    133             Mangle.Tifloat : typeid(ifloat), 
    134             Mangle.Tidouble : typeid(idouble), 
    135             Mangle.Tireal : typeid(ireal), 
    136             Mangle.Tcfloat : typeid(cfloat), 
    137             Mangle.Tcdouble : typeid(cdouble), 
    138             Mangle.Tcreal : typeid(creal), 
    139             Mangle.Tchar : typeid(char), 
    140             Mangle.Twchar : typeid(wchar), 
    141             Mangle.Tdchar : typeid(dchar) 
    142             ]; 
    143     } 
    144     auto p = m in dic; 
    145     return p ? *p : null; 
    146 
    147  
    148 /************************************ 
    149  * Interprets variadic argument list pointed to by argptr whose types 
    150  * are given by arguments[], formats them according to embedded format 
    151  * strings in the variadic argument list, and sends the resulting 
    152  * characters to putc. 
    153  * 
    154  * The variadic arguments are consumed in order.  Each is formatted 
    155  * into a sequence of chars, using the default format specification 
    156  * for its type, and the characters are sequentially passed to putc. 
    157  * If a $(D char[]), $(D wchar[]), or $(D dchar[]) argument is 
    158  * encountered, it is interpreted as a format string. As many 
    159  * arguments as specified in the format string are consumed and 
    160  * formatted according to the format specifications in that string and 
    161  * passed to putc. If there are too few remaining arguments, a 
    162  * FormatError is thrown. If there are more remaining arguments than 
    163  * needed by the format specification, the default processing of 
    164  * arguments resumes until they are all consumed. 
    165  * 
    166  * Params: 
    167  *        putc =        Output is sent do this delegate, character by character. 
    168  *        arguments = Array of $(D TypeInfo)s, one for each argument to be formatted. 
    169  *        argptr = Points to variadic argument list. 
    170  * 
    171  * Throws: 
    172  *        Mismatched arguments and formats result in a $(D FormatError) being thrown. 
    173  * 
    174  * Format_String: 
    175  *        <a name="format-string">$(I Format strings)</a> 
    176  *        consist of characters interspersed with 
    177  *        $(I format specifications). Characters are simply copied 
    178  *        to the output (such as putc) after any necessary conversion 
    179  *        to the corresponding UTF-8 sequence. 
    180  * 
    181  *        A $(I format specification) starts with a '%' character, 
    182  *        and has the following grammar: 
    183  
    184 <pre> 
    185 $(I FormatSpecification): 
     67/********************************************************************** 
     68   Interprets variadic argument list $(D args), formats them according 
     69   to $(D fmt), and sends the resulting characters to $(D w). The 
     70   encoding of the output is the same as $(D Char). type $(D Writer) 
     71   must satisfy $(XREF range,isOutputRange!(Writer, Char)). 
     72 
     73   The variadic arguments are normally consumed in order. POSIX-style 
     74   $(WEB opengroup.org/onlinepubs/009695399/functions/printf.html, 
     75   positional parameter syntax) is also supported. Each argument is 
     76   formatted into a sequence of chars according to the format 
     77   specification, and the characters are passed to $(D w). As many 
     78   arguments as specified in the format string are consumed and 
     79   formatted. If there are fewer arguments than format specifiers, a 
     80   $(D FormatError) is thrown. If there are more remaining arguments 
     81   than needed by the format specification, they are ignored but only 
     82   if at least one argument was formatted. 
     83 
     84   Params: 
     85 
     86   w = Output is sent do this writer. Typical output writers include 
     87   $(XREF range,Appender!string) and $(XREF stdio,BlockingTextWriter). 
     88 
     89   fmt = Format string. 
     90 
     91   args = Variadic argument list. 
     92 
     93   Throws: Mismatched arguments and formats result in a $(D 
     94   FormatError) being thrown. 
     95 
     96   Format_String: <a name="format-string">$(I Format strings)</a> 
     97   consist of characters interspersed with $(I format 
     98   specifications). Characters are simply copied to the output (such 
     99   as putc) after any necessary conversion to the corresponding UTF-8 
     100   sequence. 
     101 
     102   The format string has the following grammar: 
     103 
     104$(PRE 
     105$(I FormatString): 
     106    $(I FormatStringItem)* 
     107$(I FormatStringItem): 
    186108    $(B '%%') 
    187     $(B '%') $(I Flags) $(I Width) $(I Precision) $(I FormatChar) 
    188  
     109    $(B '%') $(I Position) $(I Flags) $(I Width) $(I Precision) $(I FormatChar) 
     110    $(B '%$(LPAREN)') $(I FormatString) $(B '%$(RPAREN)') 
     111    $(I OtherCharacterExceptPercent) 
     112$(I Position): 
     113    $(I empty) 
     114    $(I Integer) $(B '$') 
    189115$(I Flags): 
    190116    $(I empty) 
    191117    $(B '-') $(I Flags) 
    192118    $(B '+') $(I Flags) 
    193119    $(B '#') $(I Flags) 
    194120    $(B '0') $(I Flags) 
    195121    $(B ' ') $(I Flags) 
    196  
    197122$(I Width): 
    198123    $(I empty) 
    199124    $(I Integer) 
    200125    $(B '*') 
    201  
    202126$(I Precision): 
    203127    $(I empty) 
    204128    $(B '.') 
    205129    $(B '.') $(I Integer) 
    206130    $(B '.*') 
    207  
    208131$(I Integer): 
    209132    $(I Digit) 
    210133    $(I Digit) $(I Integer) 
    211  
    212134$(I Digit): 
    213     $(B '0') 
    214     $(B '1') 
    215     $(B '2') 
    216     $(B '3') 
    217     $(B '4') 
    218     $(B '5') 
    219     $(B '6') 
    220     $(B '7') 
    221     $(B '8') 
    222     $(B '9') 
    223  
     135    $(B '0')|$(B '1')|$(B '2')|$(B '3')|$(B '4')|$(B '5')|$(B '6')|$(B '7')|$(B '8')|$(B '9') 
    224136$(I FormatChar): 
    225     $(B 's') 
    226     $(B 'b') 
    227     $(B 'd') 
    228     $(B 'o') 
    229     $(B 'x') 
    230     $(B 'X') 
    231     $(B 'e') 
    232     $(B 'E') 
    233     $(B 'f') 
    234     $(B 'F') 
    235     $(B 'g') 
    236     $(B 'G') 
    237     $(B 'a') 
    238     $(B 'A') 
    239 </pre> 
    240     <dl> 
    241     <dt>$(I Flags) 
    242     <dl> 
    243         <dt>$(B '-') 
    244         <dd> 
    245         Left justify the result in the field. 
    246         It overrides any $(B 0) flag. 
    247  
    248         <dt>$(B '+') 
    249         <dd>Prefix positive numbers in a signed conversion with a $(B +). 
    250         It overrides any $(I space) flag. 
    251  
    252         <dt>$(B '#') 
    253         <dd>Use alternative formatting: 
    254         <dl> 
    255             <dt>For $(B 'o'): 
    256             <dd> Add to precision as necessary so that the first digit 
    257             of the octal formatting is a '0', even if both the argument 
    258             and the $(I Precision) are zero. 
    259             <dt> For $(B 'x') ($(B 'X')): 
    260             <dd> If non-zero, prefix result with $(B 0x) ($(B 0X)). 
    261             <dt> For floating point formatting: 
    262             <dd> Always insert the decimal point. 
    263             <dt> For $(B 'g') ($(B 'G')): 
    264             <dd> Do not elide trailing zeros. 
    265         </dl> 
    266  
    267         <dt>$(B '0') 
    268         <dd> For integer and floating point formatting when not nan or 
    269         infinity, use leading zeros 
    270         to pad rather than spaces. 
    271         Ignore if there's a $(I Precision). 
    272  
    273         <dt>$(B ' ') 
    274         <dd>Prefix positive numbers in a signed conversion with a space. 
    275     </dl> 
     137    $(B 's')|$(B 'b')|$(B 'd')|$(B 'o')|$(B 'x')|$(B 'X')|$(B 'e')|$(B 'E')|$(B 'f')|$(B 'F')|$(B 'g')|$(B 'G')|$(B 'a')|$(B 'A') 
     138
     139 
     140    $(BOOKTABLE Flags affect formatting depending on the specifier as 
     141    follows., $(TR $(TH Flag) $(TH Types&nbsp;affected) $(TH Semantics)) 
     142 
     143    $(TR $(TD $(B '-')) $(TD numeric) $(TD Left justify the result in 
     144        the field.  It overrides any $(B 0) flag.)) 
     145 
     146    $(TR $(TD $(B '+')) $(TD numeric) $(TD Prefix positive numbers in 
     147    a signed conversion with a $(B +).  It overrides any $(I space) 
     148    flag.)) 
     149 
     150    $(TR $(TD $(B '#')) $(TD integral ($(B 'o'))) $(TD Add to 
     151    precision as necessary so that the first digit of the octal 
     152    formatting is a '0', even if both the argument and the $(I 
     153    Precision) are zero.)) 
     154 
     155    $(TR $(TD $(B '#')) $(TD integral ($(B 'x'), $(B 'X'))) $(TD If 
     156       non-zero, prefix result with $(B 0x) ($(B 0X)).)) 
     157 
     158    $(TR $(TD $(B '#')) $(TD floating) $(TD Always insert the decimal 
     159       point and print trailing zeros.)) 
     160 
     161    $(TR $(TD $(B '#')) $(TD numeric ($(B '0'))) $(TD Use leading 
     162    zeros to pad rather than spaces (except for the floating point 
     163    values $(D nan) and $(D infinity)).  Ignore if there's a $(I 
     164    Precision).)) 
     165 
     166    $(TR $(TD $(B ' ')) $(TD integral ($(B 'd'))) $(TD Prefix positive 
     167    numbers in a signed conversion with a space.))) 
    276168 
    277169    <dt>$(I Width) 
    278170    <dd> 
    279171    Specifies the minimum field width. 
    280172    If the width is a $(B *), the next argument, which must be 
    281173    of type $(B int), is taken as the width. 
    282174    If the width is negative, it is as if the $(B -) was given 
    283175    as a $(I Flags) character. 
    284176 
    285177    <dt>$(I Precision) 
     
    379271    $(B infinity) if the 
    380272    $(I FormatChar) is lower case, or $(B INF) or $(B INFINITY) if upper. 
    381273    </dl> 
    382274 
    383275Example: 
    384276 
    385277------------------------- 
    386278import std.c.stdio; 
    387279import std.format; 
    388280 
    389 void myPrint(...) 
    390 
    391     void putc(char c) 
    392     { 
    393         fputc(c, stdout); 
    394     } 
    395  
    396     std.format.doFormat(&putc, _arguments, _argptr); 
    397 
    398  
    399 ... 
    400  
    401 int x = 27; 
    402 // prints 'The answer is 27:6' 
    403 myPrint("The answer is %s:", x, 6); 
     281void main() 
     282
     283    auto writer = appender!string(); 
     284    formattedWrite(writer, "%s is the ultimate %s.", 42, "answer"); 
     285    assert(writer.data == "42 is the ultimate answer."); 
     286    // Clear the writer 
     287    writer = appender!string(); 
     288    formattedWrite(writer, "Date: %2$s %1$s", "October", 5); 
     289    assert(writer.data == "Date: 5 October"); 
     290
    404291------------------------ 
     292 
     293The positional and non-positional styles can be mixed in the same 
     294format string. (POSIX leaves this behavior undefined.) The internal 
     295counter for non-positional parameters tracks the next parameter after 
     296the largest positional parameter already used. 
    405297 */ 
    406 void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) 
    407 
    408     TypeInfo ti; 
    409     Mangle m; 
    410     uint flags; 
    411     int field_width; 
    412     int precision; 
    413  
    414     enum : uint 
    415     { 
    416         FLdash = 1, 
    417         FLplus = 2, 
    418         FLspace = 4, 
    419         FLhash = 8, 
    420         FLlngdbl = 0x20, 
    421         FL0pad = 0x40, 
    422         FLprecision = 0x80, 
    423     } 
    424  
    425     static TypeInfo skipCI(TypeInfo valti) 
    426     { 
    427         for (;;) 
    428         { 
    429             if (valti.classinfo.name.length == 18 && 
    430                     valti.classinfo.name[9..18] == "Invariant") 
    431                 valti =        (cast(TypeInfo_Invariant)valti).next; 
    432             else if (valti.classinfo.name.length == 14 && 
    433                     valti.classinfo.name[9..14] == "Const") 
    434                 valti =        (cast(TypeInfo_Const)valti).next; 
    435             else 
    436                 break; 
    437         } 
    438         return valti; 
    439     } 
    440  
    441     void formatArg(char fc) 
    442     { 
    443         bool vbit; 
    444         ulong vnumber; 
    445         char vchar; 
    446         dchar vdchar; 
    447         Object vobject; 
    448         real vreal; 
    449         creal vcreal; 
    450         Mangle m2; 
    451         int signed = 0; 
    452         uint base = 10; 
    453         int uc; 
    454         char[ulong.sizeof * 8] tmpbuf; // long enough to print long in binary 
    455         const(char)* prefix = ""; 
    456         string s; 
    457  
    458         void putstr(const char[] s) 
    459         { 
    460             //printf("flags = x%x\n", flags); 
    461             int prepad = 0; 
    462             int postpad = 0; 
    463             int padding = field_width - 
    464                 (strlen(prefix) + toUCSindex(s, s.length)); 
    465             if (padding > 0) 
     298void formattedWrite(Writer, Char, A...)(Writer w, in Char[] fmt, A args) 
     299
     300    enum len = args.length; 
     301    void function(Writer, const(void)*, ref FormatSpec!Char) funs[len] = void; 
     302    const(void)* argsAddresses[len] = void; 
     303    foreach (i, arg; args) 
     304    { 
     305        funs[i] = &formatGeneric!(Writer, typeof(arg), Char); 
     306        argsAddresses[i] = &arg; 
     307    } 
     308    // Are we already done with formats? Then just dump each parameter in turn 
     309    uint currentArg = 0; 
     310    auto spec = FormatSpec!Char(fmt); 
     311    for (;spec.writeUpToNextSpec(w);) 
     312    { 
     313        if (currentArg == funs.length && !spec.index) 
     314        { 
     315            // leftover spec? 
     316            enforce(fmt.length == 0, new FormatError( 
     317                    cast(string) ("Orphan format specifier: %" ~ fmt))); 
     318            break; 
     319        } 
     320        if (spec.width == spec.DYNAMIC) 
     321        { 
     322            auto width = to!(typeof(spec.width))(getNthInt(currentArg, args)); 
     323            if (width < 0) 
    466324            { 
    467                 if (flags & FLdash) 
    468                     postpad = padding; 
    469                 else 
    470                     prepad = padding; 
     325                spec.flDash = true; 
     326                width = -width; 
    471327            } 
    472  
    473             if (flags & FL0pad) 
     328            spec.width = width; 
     329            ++currentArg; 
     330        } 
     331        else if (spec.width < 0) 
     332        { 
     333            // means: get width as a positional parameter 
     334            auto index = cast(uint) -spec.width; 
     335            auto width = to!(typeof(spec.width))(getNthInt(index, args)); 
     336            if (currentArg < index) currentArg = index; 
     337            if (width < 0) 
    474338            { 
    475                 while (*prefix) 
    476                     putc(*prefix++); 
    477                 while (prepad--) 
    478                     putc('0'); 
     339                spec.flDash = true; 
     340                width = -width; 
    479341            } 
    480             else 
     342            spec.width = width; 
     343        } 
     344        if (spec.precision == spec.DYNAMIC) 
     345        { 
     346            auto precision = to!(typeof(spec.precision))( 
     347                getNthInt(currentArg, args)); 
     348            if (precision >= 0) spec.precision = precision; 
     349            // else negative precision is same as no precision 
     350            else spec.precision = spec.UNSPECIFIED; 
     351            ++currentArg; 
     352        } 
     353        else if (spec.precision < 0) 
     354        { 
     355            // means: get precision as a positional parameter 
     356            auto index = cast(uint) -spec.precision; 
     357            auto precision = to!(typeof(spec.precision))( 
     358                getNthInt(index, args)); 
     359            if (currentArg < index) currentArg = index; 
     360            if (precision >= 0) spec.precision = precision; 
     361            // else negative precision is same as no precision 
     362            else spec.precision = spec.UNSPECIFIED; 
     363        } 
     364        // Format! 
     365        if (spec.index > 0) 
     366        { 
     367            // using positional parameters! 
     368            funs[spec.index - 1](w, argsAddresses[spec.index - 1], spec); 
     369            if (currentArg < spec.index) currentArg = spec.index; 
     370        } 
     371        else 
     372        { 
     373            funs[currentArg](w, argsAddresses[currentArg], spec); 
     374            ++currentArg; 
     375        } 
     376    } 
     377
     378 
     379/** 
     380   Reads characters from input range $(D r), converts them according 
     381   to $(D fmt), and writes them to $(D args). 
     382 
     383   Example: 
     384---- 
     385string s = "hello!124:34.5"; 
     386string a; 
     387int b; 
     388double c; 
     389formattedRead(s, "%s!%s:%s", &a, &b, &c); 
     390assert(a == "hello" && b == 124 && c == 34.5); 
     391---- 
     392 */ 
     393void formattedRead(R, Char, S...)(ref R r, const(Char)[] fmt, S args) 
     394
     395    auto spec = FormatSpec!Char(fmt); 
     396    static if (!S.length) 
     397    { 
     398        spec.readUpToNextSpec(r); 
     399        enforce(spec.trailing.empty); 
     400    } 
     401    else 
     402    { 
     403        // The function below accounts for '*' == fields meant to be 
     404        // read and skipped 
     405        void skipUnstoredFields() 
     406        { 
     407            for (;;) 
    481408            { 
    482                 while (prepad--) 
    483                     putc(' ')
    484                 while (*prefix) 
    485                     putc(*prefix++); 
     409                spec.readUpToNextSpec(r); 
     410                if (spec.width != spec.DYNAMIC) break
     411                // must skip this field 
     412                skipData(r, spec); 
    486413            } 
    487  
    488             foreach (dchar c; s) 
    489                 putc(c); 
    490  
    491             while (postpad--) 
    492                 putc(' '); 
    493         } 
    494  
    495         void putreal(real v) 
    496         { 
    497             //printf("putreal %Lg\n", vreal); 
    498  
    499             switch (fc) 
     414        } 
     415 
     416        skipUnstoredFields(); 
     417        alias typeof(*args[0]) A; 
     418        //@@@BUG 2725 
     419        //static if (is(A X == Tuple!(T), T)) 
     420        static if (is(A.Types[0]))             // Is it a tuple? 
     421        { 
     422            foreach (i, T; A.Types) 
    500423            { 
    501                 case 's': 
    502                     fc = 'g'; 
    503                     break; 
    504  
    505                 case 'f', 'F', 'e', 'E', 'g', 'G', 'a', 'A': 
    506                     break; 
    507  
    508                 default: 
    509                     //printf("fc = '%c'\n", fc); 
    510                 Lerror: 
    511                     throw new FormatError("floating"); 
     424                //writeln("Parsing ", r, " with format ", fmt); 
     425                args[0].field[i] = unformatValue!(T)(r, spec); 
     426                skipUnstoredFields(); 
    512427            } 
    513             version (DigitalMarsC) 
    514             { 
    515                 uint sl; 
    516                 char[] fbuf = tmpbuf; 
    517                 if (!(flags & FLprecision)) 
    518                     precision = 6; 
    519                 while (1) 
    520                 { 
    521                     sl = fbuf.length; 
    522                     prefix = (*__pfloatfmt)(fc, flags | FLlngdbl, 
    523                             precision, &v, cast(char*)fbuf, &sl, field_width); 
    524                     if (sl != -1) 
    525                         break; 
    526                     sl = fbuf.length * 2; 
    527                     fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl]; 
    528                 } 
    529                 putstr(fbuf[0 .. sl]); 
    530             } 
    531             else 
    532             { 
    533                 int sl; 
    534                 char[] fbuf = tmpbuf; 
    535                 char[12] format; 
    536                 format[0] = '%'; 
    537                 int i = 1; 
    538                 if (flags & FLdash) 
    539                     format[i++] = '-'; 
    540                 if (flags & FLplus) 
    541                     format[i++] = '+'; 
    542                 if (flags & FLspace) 
    543                     format[i++] = ' '; 
    544                 if (flags & FLhash) 
    545                     format[i++] = '#'; 
    546                 if (flags & FL0pad) 
    547                     format[i++] = '0'; 
    548                 format[i + 0] = '*'; 
    549                 format[i + 1] = '.'; 
    550                 format[i + 2] = '*'; 
    551                 format[i + 3] = 'L'; 
    552                 format[i + 4] = fc; 
    553                 format[i + 5] = 0; 
    554                 if (!(flags & FLprecision)) 
    555                     precision = -1; 
    556                 while (1) 
    557                 { 
    558                     sl = fbuf.length; 
    559                     auto n = snprintf(fbuf.ptr, sl, format.ptr, field_width, 
    560                             precision, v); 
    561                     //printf("format = '%s', n = %d\n", cast(char*)format, n); 
    562                     if (n >= 0 && n < sl) 
    563                     {        sl = n; 
    564                         break; 
    565                     } 
    566                     if (n < 0) 
    567                         sl = sl * 2; 
    568                     else 
    569                         sl = n + 1; 
    570                     fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl]; 
    571                 } 
    572                 putstr(fbuf[0 .. sl]); 
    573             } 
    574             return; 
    575         } 
    576  
    577         static Mangle getMan(TypeInfo ti) 
    578         { 
    579           auto m = cast(Mangle)ti.classinfo.name[9]; 
    580           if (ti.classinfo.name.length == 20 && 
    581               ti.classinfo.name[9..20] == "StaticArray") 
    582                 m = cast(Mangle)'G'; 
    583           return m; 
    584         } 
    585  
    586         void putArray(void* p, size_t len, TypeInfo valti) 
    587         { 
    588           //printf("\nputArray(len = %u), tsize = %u\n", len, valti.tsize()); 
    589           putc('['); 
    590           valti = skipCI(valti); 
    591           size_t tsize = valti.tsize(); 
    592           auto argptrSave = argptr; 
    593           auto tiSave = ti; 
    594           auto mSave = m; 
    595           ti = valti; 
    596           //printf("\n%.*s\n", valti.classinfo.name); 
    597           m = getMan(valti); 
    598           while (len--) 
    599           { 
    600             //doFormat(putc, (&valti)[0 .. 1], p); 
    601             argptr = p; 
    602             formatArg('s'); 
    603  
    604             p += tsize; 
    605             if (len > 0) putc(','); 
    606           } 
    607           m = mSave; 
    608           ti = tiSave; 
    609           argptr = argptrSave; 
    610           putc(']'); 
    611         } 
    612  
    613         void putAArray(ubyte[long] vaa, TypeInfo valti, TypeInfo keyti) 
    614         { 
    615             putc('['); 
    616             bool comma=false; 
    617             auto argptrSave = argptr; 
    618             auto tiSave = ti; 
    619             auto mSave = m; 
    620             valti = skipCI(valti); 
    621             keyti = skipCI(keyti); 
    622             foreach(ref fakevalue; vaa) 
    623             { 
    624                 if (comma) putc(','); 
    625                 comma = true; 
    626                 // the key comes before the value 
    627                 ubyte* key = &fakevalue - long.sizeof; 
    628  
    629                 //doFormat(putc, (&keyti)[0..1], key); 
    630                 argptr = key; 
    631                 ti = keyti; 
    632                 m = getMan(keyti); 
    633                 formatArg('s'); 
    634  
    635                 putc(':'); 
    636                 auto keysize = keyti.tsize; 
    637                 keysize = (keysize + 3) & ~3; 
    638                 ubyte* value = key + keysize; 
    639                 //doFormat(putc, (&valti)[0..1], value); 
    640                 argptr = value; 
    641                 ti = valti; 
    642                 m = getMan(valti); 
    643                 formatArg('s'); 
    644             } 
    645             m = mSave; 
    646             ti = tiSave; 
    647             argptr = argptrSave; 
    648             putc(']'); 
    649         } 
    650  
    651         //printf("formatArg(fc = '%c', m = '%c')\n", fc, m); 
    652         switch (m) 
    653         { 
    654             case Mangle.Tbool: 
    655                 vbit = va_arg!(bool)(argptr); 
    656                 if (fc != 's') 
    657                 {   vnumber = vbit; 
    658                     goto Lnumber; 
    659                 } 
    660                 putstr(vbit ? "true" : "false"); 
    661                 return; 
    662  
    663  
    664             case Mangle.Tchar: 
    665                 vchar = va_arg!(char)(argptr); 
    666                 if (fc != 's') 
    667                 {   vnumber = vchar; 
    668                     goto Lnumber; 
    669                 } 
    670             L2: 
    671                 putstr((&vchar)[0 .. 1]); 
    672                 return; 
    673  
    674             case Mangle.Twchar: 
    675                 vdchar = va_arg!(wchar)(argptr); 
    676                 goto L1; 
    677  
    678             case Mangle.Tdchar: 
    679                 vdchar = va_arg!(dchar)(argptr); 
    680             L1: 
    681                 if (fc != 's') 
    682                 {   vnumber = vdchar; 
    683                     goto Lnumber; 
    684                 } 
    685                 if (vdchar <= 0x7F) 
    686                 {   vchar = cast(char)vdchar; 
    687                     goto L2; 
    688                 } 
    689                 else 
    690                 {   if (!isValidDchar(vdchar)) 
    691                         throw new UtfException("invalid dchar in format", 0); 
    692                     char[4] vbuf; 
    693                     putstr(toUTF8(vbuf, vdchar)); 
    694                 } 
    695                 return; 
    696  
    697  
    698             case Mangle.Tbyte: 
    699                 signed = 1; 
    700                 vnumber = va_arg!(byte)(argptr); 
    701                 goto Lnumber; 
    702  
    703             case Mangle.Tubyte: 
    704                 vnumber = va_arg!(ubyte)(argptr); 
    705                 goto Lnumber; 
    706  
    707             case Mangle.Tshort: 
    708                 signed = 1; 
    709                 vnumber = va_arg!(short)(argptr); 
    710                 goto Lnumber; 
    711  
    712             case Mangle.Tushort: 
    713                 vnumber = va_arg!(ushort)(argptr); 
    714                 goto Lnumber; 
    715  
    716             case Mangle.Tint: 
    717                 signed = 1; 
    718                 vnumber = va_arg!(int)(argptr); 
    719                 goto Lnumber; 
    720  
    721             case Mangle.Tuint: 
    722             Luint: 
    723                 vnumber = va_arg!(uint)(argptr); 
    724                 goto Lnumber; 
    725  
    726             case Mangle.Tlong: 
    727                 signed = 1; 
    728                 vnumber = cast(ulong)va_arg!(long)(argptr); 
    729                 goto Lnumber; 
    730  
    731             case Mangle.Tulong: 
    732             Lulong: 
    733                 vnumber = va_arg!(ulong)(argptr); 
    734                 goto Lnumber; 
    735  
    736             case Mangle.Tclass: 
    737                 vobject = va_arg!(Object)(argptr); 
    738                 if (vobject is null) 
    739                     s = "null"; 
    740                 else 
    741                     s = vobject.toString(); 
    742                 goto Lputstr; 
    743  
    744             case Mangle.Tpointer: 
    745                 vnumber = cast(ulong)va_arg!(void*)(argptr); 
    746                 if (fc != 'x')  uc = 1; 
    747                 flags |= FL0pad; 
    748                 if (!(flags & FLprecision)) 
    749                 {   flags |= FLprecision; 
    750                     precision = (void*).sizeof; 
    751                 } 
    752                 base = 16; 
    753                 goto Lnumber; 
    754  
    755  
    756             case Mangle.Tfloat: 
    757             case Mangle.Tifloat: 
    758                 if (fc == 'x' || fc == 'X') 
    759                     goto Luint; 
    760                 vreal = va_arg!(float)(argptr); 
    761                 goto Lreal; 
    762  
    763             case Mangle.Tdouble: 
    764             case Mangle.Tidouble: 
    765                 if (fc == 'x' || fc == 'X') 
    766                     goto Lulong; 
    767                 vreal = va_arg!(double)(argptr); 
    768                 goto Lreal; 
    769  
    770             case Mangle.Treal: 
    771             case Mangle.Tireal: 
    772                 vreal = va_arg!(real)(argptr); 
    773                 goto Lreal; 
    774  
    775  
    776             case Mangle.Tcfloat: 
    777                 vcreal = va_arg!(cfloat)(argptr); 
    778                 goto Lcomplex; 
    779  
    780             case Mangle.Tcdouble: 
    781                 vcreal = va_arg!(cdouble)(argptr); 
    782                 goto Lcomplex; 
    783  
    784             case Mangle.Tcreal: 
    785                 vcreal = va_arg!(creal)(argptr); 
    786                 goto Lcomplex; 
    787  
    788             case Mangle.Tsarray: 
    789                 putArray(argptr, (cast(TypeInfo_StaticArray)ti).len, 
    790                         (cast(TypeInfo_StaticArray)ti).next); 
    791                 return; 
    792  
    793             case Mangle.Tarray: 
    794                 int mi = 10; 
    795                 if (ti.classinfo.name.length == 14 && 
    796                     ti.classinfo.name[9..14] == "Array") 
    797                 { // array of non-primitive types 
    798                   TypeInfo tn = (cast(TypeInfo_Array)ti).next; 
    799                   tn = skipCI(tn); 
    800                   switch (cast(Mangle)tn.classinfo.name[9]) 
    801                   { 
    802                     case Mangle.Tchar:  goto LarrayChar; 
    803                     case Mangle.Twchar: goto LarrayWchar; 
    804                     case Mangle.Tdchar: goto LarrayDchar; 
    805                     default: 
    806                         break; 
    807                   } 
    808                   void[] va = va_arg!(void[])(argptr); 
    809                   putArray(va.ptr, va.length, tn); 
    810                   return; 
    811                 } 
    812                 if (ti.classinfo.name.length == 25 && 
    813                     ti.classinfo.name[9..25] == "AssociativeArray") 
    814                 { // associative array 
    815                   ubyte[long] vaa = va_arg!(ubyte[long])(argptr); 
    816                   putAArray(vaa, 
    817                         (cast(TypeInfo_AssociativeArray)ti).next, 
    818                         (cast(TypeInfo_AssociativeArray)ti).key); 
    819                   return; 
    820                 } 
    821  
    822                 while (1) 
    823                 { 
    824                     m2 = cast(Mangle)ti.classinfo.name[mi]; 
    825                     switch (m2) 
    826                     { 
    827                         case Mangle.Tchar: 
    828                         LarrayChar: 
    829                             s = va_arg!(string)(argptr); 
    830                             goto Lputstr; 
    831  
    832                         case Mangle.Twchar: 
    833                         LarrayWchar: 
    834                             wchar[] sw = va_arg!(wchar[])(argptr); 
    835                             s = toUTF8(sw); 
    836                             goto Lputstr; 
    837  
    838                         case Mangle.Tdchar: 
    839                         LarrayDchar: 
    840                             auto sd = va_arg!(dstring)(argptr); 
    841                             s = toUTF8(sd); 
    842                         Lputstr: 
    843                             if (fc != 's') 
    844                                 throw new FormatError("string"); 
    845                             if (flags & FLprecision && precision < s.length) 
    846                                 s = s[0 .. precision]; 
    847                             putstr(s); 
    848                             break; 
    849  
    850                         case Mangle.Tconst: 
    851                         case Mangle.Timmutable: 
    852                             mi++; 
    853                             continue; 
    854  
    855                         default: 
    856                             TypeInfo ti2 = primitiveTypeInfo(m2); 
    857                             if (!ti2) 
    858                               goto Lerror; 
    859                             void[] va = va_arg!(void[])(argptr); 
    860                             putArray(va.ptr, va.length, ti2); 
    861                     } 
    862                     return; 
    863                 } 
    864  
    865             case Mangle.Ttypedef: 
    866                 ti = (cast(TypeInfo_Typedef)ti).base; 
    867                 m = cast(Mangle)ti.classinfo.name[9]; 
    868                 formatArg(fc); 
    869                 return; 
    870  
    871             case Mangle.Tenum: 
    872                 ti = (cast(TypeInfo_Enum)ti).base; 
    873                 m = cast(Mangle)ti.classinfo.name[9]; 
    874                 formatArg(fc); 
    875                 return; 
    876  
    877             case Mangle.Tstruct: 
    878             {        TypeInfo_Struct tis = cast(TypeInfo_Struct)ti; 
    879                 if (tis.xtoString is null) 
    880                     throw new FormatError("Can't convert " ~ tis.toString() 
    881                             ~ " to string: \"string toString()\" not defined"); 
    882                 s = tis.xtoString(argptr); 
    883                 argptr += (tis.tsize() + 3) & ~3; 
    884                 goto Lputstr; 
    885             } 
    886  
    887             default: 
    888                 goto Lerror; 
    889         } 
    890  
    891     Lnumber: 
    892         switch (fc) 
    893         { 
    894             case 's': 
    895             case 'd': 
    896                 if (signed) 
    897                 {   if (cast(long)vnumber < 0) 
    898                     {        prefix = "-"; 
    899                         vnumber = -vnumber; 
    900                     } 
    901                     else if (flags & FLplus) 
    902                         prefix = "+"; 
    903                     else if (flags & FLspace) 
    904                         prefix = " "; 
    905                 } 
    906                 break; 
    907  
    908             case 'b': 
    909                 signed = 0; 
    910                 base = 2; 
    911                 break; 
    912  
    913             case 'o': 
    914                 signed = 0; 
    915                 base = 8; 
    916                 break; 
    917  
    918             case 'X': 
    919                 uc = 1; 
    920                 if (flags & FLhash && vnumber) 
    921                     prefix = "0X"; 
    922                 signed = 0; 
    923                 base = 16; 
    924                 break; 
    925  
    926             case 'x': 
    927                 if (flags & FLhash && vnumber) 
    928                     prefix = "0x"; 
    929                 signed = 0; 
    930                 base = 16; 
    931                 break; 
    932  
    933             default: 
    934                 goto Lerror; 
    935         } 
    936  
    937         if (!signed) 
    938         { 
    939             switch (m) 
    940             { 
    941                 case Mangle.Tbyte: 
    942                     vnumber &= 0xFF; 
    943                     break; 
    944  
    945                 case Mangle.Tshort: 
    946                     vnumber &= 0xFFFF; 
    947                     break; 
    948  
    949                 case Mangle.Tint: 
    950                     vnumber &= 0xFFFFFFFF; 
    951                     break; 
    952  
    953                 default: 
    954                     break; 
    955             } 
    956         } 
    957  
    958         if (flags & FLprecision && fc != 'p') 
    959             flags &= ~FL0pad; 
    960  
    961         if (vnumber < base) 
    962         { 
    963             if (vnumber == 0 && precision == 0 && flags & FLprecision && 
    964                 !(fc == 'o' && flags & FLhash)) 
    965             { 
    966                 putstr(null); 
    967                 return; 
    968             } 
    969             if (precision == 0 || !(flags & FLprecision)) 
    970             {        vchar = cast(char)('0' + vnumber); 
    971                 if (vnumber < 10) 
    972                     vchar = cast(char)('0' + vnumber); 
    973                 else 
    974                     vchar = cast(char)((uc ? 'A' - 10 : 'a' - 10) + vnumber); 
    975                 goto L2; 
    976             } 
    977         } 
    978  
    979         int n = tmpbuf.length; 
    980         char c; 
    981         int hexoffset = uc ? ('A' - ('9' + 1)) : ('a' - ('9' + 1)); 
    982  
    983         while (vnumber) 
    984         { 
    985             c = cast(char)((vnumber % base) + '0'); 
    986             if (c > '9') 
    987                 c += hexoffset; 
    988             vnumber /= base; 
    989             tmpbuf[--n] = c; 
    990         } 
    991         if (tmpbuf.length - n < precision && precision < tmpbuf.length) 
    992         { 
    993             int m = tmpbuf.length - precision; 
    994             tmpbuf[m .. n] = '0'; 
    995             n = m; 
    996         } 
    997         else if (flags & FLhash && fc == 'o') 
    998             prefix = "0"; 
    999         putstr(tmpbuf[n .. tmpbuf.length]); 
    1000         return; 
    1001  
    1002     Lreal: 
    1003         putreal(vreal); 
    1004         return; 
    1005  
    1006     Lcomplex: 
    1007         putreal(vcreal.re); 
    1008         putc('+'); 
    1009         putreal(vcreal.im); 
    1010         putc('i'); 
    1011         return; 
    1012  
    1013     Lerror: 
    1014         throw new FormatError("formatArg"); 
    1015     } 
    1016  
    1017     for (int j = 0; j < arguments.length; ) 
    1018     { 
    1019         ti = arguments[j++]; 
    1020         //printf("test1: '%.*s' %d\n", ti.classinfo.name, 
    1021         //ti.classinfo.name.length); ti.print(); 
    1022  
    1023         flags = 0; 
    1024         precision = 0; 
    1025         field_width = 0; 
    1026  
    1027         ti = skipCI(ti); 
    1028         int mi = 9; 
    1029         do 
    1030         { 
    1031             if (ti.classinfo.name.length <= mi) 
    1032                 goto Lerror; 
    1033             m = cast(Mangle)ti.classinfo.name[mi++]; 
    1034         } while (m == Mangle.Tconst || m == Mangle.Timmutable); 
    1035  
    1036         if (m == Mangle.Tarray) 
    1037         { 
    1038             if (ti.classinfo.name.length == 14 && 
    1039                     ti.classinfo.name[9..14] == "Array") 
    1040             { 
    1041                 TypeInfo tn = (cast(TypeInfo_Array)ti).next; 
    1042                 tn = skipCI(tn); 
    1043                 switch (cast(Mangle)tn.classinfo.name[9]) 
    1044                 { 
    1045                 case Mangle.Tchar: 
    1046                 case Mangle.Twchar: 
    1047                 case Mangle.Tdchar: 
    1048                     ti = tn; 
    1049                     mi = 9; 
    1050                     break; 
    1051                 default: 
    1052                     break; 
    1053                 } 
    1054             } 
    1055           L1: 
    1056             Mangle m2 = cast(Mangle)ti.classinfo.name[mi]; 
    1057             string  fmt;                        // format string 
    1058             wstring wfmt; 
    1059             dstring dfmt; 
    1060  
    1061             /* For performance reasons, this code takes advantage of the 
    1062              * fact that most format strings will be ASCII, and that the 
    1063              * format specifiers are always ASCII. This means we only need 
    1064              * to deal with UTF in a couple of isolated spots. 
    1065              */ 
    1066  
    1067             switch (m2) 
    1068             { 
    1069             case Mangle.Tchar: 
    1070                 fmt = va_arg!(string)(argptr); 
    1071                 break; 
    1072  
    1073             case Mangle.Twchar: 
    1074                 wfmt = va_arg!(wstring)(argptr); 
    1075                 fmt = toUTF8(wfmt); 
    1076                 break; 
    1077  
    1078             case Mangle.Tdchar: 
    1079                 dfmt = va_arg!(dstring)(argptr); 
    1080                 fmt = toUTF8(dfmt); 
    1081                 break; 
    1082  
    1083             case Mangle.Tconst: 
    1084             case Mangle.Timmutable: 
    1085                 mi++; 
    1086                 goto L1; 
    1087  
    1088             default: 
    1089                 formatArg('s'); 
    1090                 continue; 
    1091             } 
    1092  
    1093             for (size_t i = 0; i < fmt.length; ) 
    1094             {        dchar c = fmt[i++]; 
    1095  
    1096                 dchar getFmtChar() 
    1097                 {   // Valid format specifier characters will never be UTF 
    1098                     if (i == fmt.length) 
    1099                         throw new FormatError("invalid specifier"); 
    1100                     return fmt[i++]; 
    1101                 } 
    1102  
    1103                 int getFmtInt() 
    1104                 {   int n; 
    1105  
    1106                     while (1) 
    1107                     { 
    1108                         n = n * 10 + (c - '0'); 
    1109                         if (n < 0)        // overflow 
    1110                             throw new FormatError("int overflow"); 
    1111                         c = getFmtChar(); 
    1112                         if (c < '0' || c > '9') 
    1113                             break; 
    1114                     } 
    1115                     return n; 
    1116                 } 
    1117  
    1118                 int getFmtStar() 
    1119                 {   Mangle m; 
    1120                     TypeInfo ti; 
    1121  
    1122                     if (j == arguments.length) 
    1123                         throw new FormatError("too few arguments"); 
    1124                     ti = arguments[j++]; 
    1125                     m = cast(Mangle)ti.classinfo.name[9]; 
    1126                     if (m != Mangle.Tint) 
    1127                         throw new FormatError("int argument expected"); 
    1128                     return va_arg!(int)(argptr); 
    1129                 } 
    1130  
    1131                 if (c != '%') 
    1132                 { 
    1133                     if (c > 0x7F)        // if UTF sequence 
    1134                     { 
    1135                         i--;                // back up and decode UTF sequence 
    1136                         c = std.utf.decode(fmt, i); 
    1137                     } 
    1138                   Lputc: 
    1139                     putc(c); 
    1140                     continue; 
    1141                 } 
    1142  
    1143                 // Get flags {-+ #} 
    1144                 flags = 0; 
    1145                 while (1) 
    1146                 { 
    1147                     c = getFmtChar(); 
    1148                     switch (c) 
    1149                     { 
    1150                     case '-':        flags |= FLdash;        continue; 
    1151                     case '+':        flags |= FLplus;        continue; 
    1152                     case ' ':        flags |= FLspace;        continue; 
    1153                     case '#':        flags |= FLhash;        continue; 
    1154                     case '0':        flags |= FL0pad;        continue; 
    1155  
    1156                     case '%':        if (flags == 0) 
    1157                             goto Lputc; 
    1158                     default:        break; 
    1159                     } 
    1160                     break; 
    1161                 } 
    1162  
    1163                 // Get field width 
    1164                 field_width = 0; 
    1165                 if (c == '*') 
    1166                 { 
    1167                     field_width = getFmtStar(); 
    1168                     if (field_width < 0) 
    1169                     {   flags |= FLdash; 
    1170                         field_width = -field_width; 
    1171                     } 
    1172  
    1173                     c = getFmtChar(); 
    1174                 } 
    1175                 else if (c >= '0' && c <= '9') 
    1176                     field_width = getFmtInt(); 
    1177  
    1178                 if (flags & FLplus) 
    1179                     flags &= ~FLspace; 
    1180                 if (flags & FLdash) 
    1181                     flags &= ~FL0pad; 
    1182  
    1183                 // Get precision 
    1184                 precision = 0; 
    1185                 if (c == '.') 
    1186                 {   flags |= FLprecision; 
    1187                     //flags &= ~FL0pad; 
    1188  
    1189                     c = getFmtChar(); 
    1190                     if (c == '*') 
    1191                     { 
    1192                         precision = getFmtStar(); 
    1193                         if (precision < 0) 
    1194                         {   precision = 0; 
    1195                             flags &= ~FLprecision; 
    1196                         } 
    1197  
    1198                         c = getFmtChar(); 
    1199                     } 
    1200                     else if (c >= '0' && c <= '9') 
    1201                         precision = getFmtInt(); 
    1202                 } 
    1203  
    1204                 if (j == arguments.length) 
    1205                     goto Lerror; 
    1206                 ti = arguments[j++]; 
    1207                 ti = skipCI(ti); 
    1208                 mi = 9; 
    1209                 do 
    1210                 { 
    1211                     m = cast(Mangle)ti.classinfo.name[mi++]; 
    1212                 } while (m == Mangle.Tconst || m == Mangle.Timmutable); 
    1213  
    1214                 if (c > 0x7F)                // if UTF sequence 
    1215                     goto Lerror;        // format specifiers can't be UTF 
    1216                 formatArg(cast(char)c); 
    1217             } 
    1218428        } 
    1219429        else 
    1220430        { 
    1221             formatArg('s'); 
    1222         } 
    1223     } 
    1224     return; 
    1225  
    1226   Lerror: 
    1227     throw new FormatError(); 
    1228 
    1229  
    1230 /* ======================== Unit Tests ====================================== */ 
    1231  
    1232 unittest 
    1233 
    1234     int i; 
    1235     string s; 
    1236  
    1237     debug(format) printf("std.format.format.unittest\n"); 
    1238  
    1239     s = std.string.format("hello world! %s %s ", true, 57, 1_000_000_000, 'x', " foo"); 
    1240     assert(s == "hello world! true 57 1000000000x foo"); 
    1241  
    1242     s = std.string.format(1.67, " %A ", -1.28, float.nan); 
    1243     /* The host C library is used to format floats. 
    1244      * C99 doesn't specify what the hex digit before the decimal point 
    1245      * is for %A. 
     431            *args[0] = unformatValue!(A)(r, spec); 
     432        } 
     433        return formattedRead(r, spec.trailing, args[1 .. $]); 
     434    } 
     435
     436 
     437/** 
     438 A compiled version of an individual format specifier, backwards 
     439 compatible with $(D printf) specifiers. 
     440 */ 
     441struct FormatSpec(Char) 
     442
     443    /** 
     444       Minimum _width, default $(D 0). 
    1246445     */ 
    1247     version (linux) 
    1248         assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan"); 
    1249     else 
    1250         assert(s == "1.67 -0X1.47AE147AE147BP+0 nan"); 
    1251  
    1252     s = std.string.format("%x %X", 0x1234AF, 0xAFAFAFAF); 
    1253     assert(s == "1234af AFAFAFAF"); 
    1254  
    1255     s = std.string.format("%b %o", 0x1234AF, 0xAFAFAFAF); 
    1256     assert(s == "100100011010010101111 25753727657"); 
    1257  
    1258     s = std.string.format("%d %s", 0x1234AF, 0xAFAFAFAF); 
    1259     assert(s == "1193135 2947526575"); 
    1260  
    1261     s = std.string.format("%s", 1.2 + 3.4i); 
    1262     assert(s == "1.2+3.4i"); 
    1263  
    1264     s = std.string.format("%x %X", 1.32, 6.78f); 
    1265     assert(s == "3ff51eb851eb851f 40D8F5C3"); 
    1266  
    1267     s = std.string.format("%#06.*f",2,12.345); 
    1268     assert(s == "012.35"); 
    1269  
    1270     s = std.string.format("%#0*.*f",6,2,12.345); 
    1271     assert(s == "012.35"); 
    1272  
    1273     s = std.string.format("%7.4g:", 12.678); 
    1274     assert(s == "  12.68:"); 
    1275  
    1276     s = std.string.format("%7.4g:", 12.678L); 
    1277     assert(s == "  12.68:"); 
    1278  
    1279     s = std.string.format("%04f|%05d|%#05x|%#5x",-4.,-10,1,1); 
    1280     assert(s == "-4.000000|-0010|0x001|  0x1"); 
    1281  
    1282     i = -10; 
    1283     s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
    1284     assert(s == "-10|-10|-10|-10|-10.0000"); 
    1285  
    1286     i = -5; 
    1287     s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
    1288     assert(s == "-5| -5|-05|-5|-5.0000"); 
    1289  
    1290     i = 0; 
    1291     s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
    1292     assert(s == "0|  0|000|0|0.0000"); 
    1293  
    1294     i = 5; 
    1295     s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
    1296     assert(s == "5|  5|005|5|5.0000"); 
    1297  
    1298     i = 10; 
    1299     s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
    1300     assert(s == "10| 10|010|10|10.0000"); 
    1301  
    1302     s = std.string.format("%.0d", 0); 
    1303     assert(s == ""); 
    1304  
    1305     s = std.string.format("%.g", .34); 
    1306     assert(s == "0.3"); 
    1307  
    1308     s = std.string.format("%.0g", .34); 
    1309     assert(s == "0.3"); 
    1310  
    1311     s = std.string.format("%.2g", .34); 
    1312     assert(s == "0.34"); 
    1313  
    1314     s = std.string.format("%0.0008f", 1e-08); 
    1315     assert(s == "0.00000001"); 
    1316  
    1317     s = std.string.format("%0.0008f", 1e-05); 
    1318     assert(s == "0.00001000"); 
    1319  
    1320     s = "helloworld"; 
    1321     string r; 
    1322     r = std.string.format("%.2s", s[0..5]); 
    1323     assert(r == "he"); 
    1324     r = std.string.format("%.20s", s[0..5]); 
    1325     assert(r == "hello"); 
    1326     r = std.string.format("%8s", s[0..5]); 
    1327     assert(r == "   hello"); 
    1328  
    1329     byte[] arrbyte = new byte[4]; 
    1330     arrbyte[0] = 100; 
    1331     arrbyte[1] = -99; 
    1332     arrbyte[3] = 0; 
    1333     r = std.string.format(arrbyte); 
    1334     assert(r == "[100,-99,0,0]"); 
    1335  
    1336     ubyte[] arrubyte = new ubyte[4]; 
    1337     arrubyte[0] = 100; 
    1338     arrubyte[1] = 200; 
    1339     arrubyte[3] = 0; 
    1340     r = std.string.format(arrubyte); 
    1341     assert(r == "[100,200,0,0]"); 
    1342  
    1343     short[] arrshort = new short[4]; 
    1344     arrshort[0] = 100; 
    1345     arrshort[1] = -999; 
    1346     arrshort[3] = 0; 
    1347     r = std.string.format(arrshort); 
    1348     assert(r == "[100,-999,0,0]"); 
    1349     r = std.string.format("%s",arrshort); 
    1350     assert(r == "[100,-999,0,0]"); 
    1351  
    1352     ushort[] arrushort = new ushort[4]; 
    1353     arrushort[0] = 100; 
    1354     arrushort[1] = 20_000; 
    1355     arrushort[3] = 0; 
    1356     r = std.string.format(arrushort); 
    1357     assert(r == "[100,20000,0,0]"); 
    1358  
    1359     int[] arrint = new int[4]; 
    1360     arrint[0] = 100; 
    1361     arrint[1] = -999; 
    1362     arrint[3] = 0; 
    1363     r = std.string.format(arrint); 
    1364     assert(r == "[100,-999,0,0]"); 
    1365     r = std.string.format("%s",arrint); 
    1366     assert(r == "[100,-999,0,0]"); 
    1367  
    1368     long[] arrlong = new long[4]; 
    1369     arrlong[0] = 100; 
    1370     arrlong[1] = -999; 
    1371     arrlong[3] = 0; 
    1372     r = std.string.format(arrlong); 
    1373     assert(r == "[100,-999,0,0]"); 
    1374     r = std.string.format("%s",arrlong); 
    1375     assert(r == "[100,-999,0,0]"); 
    1376  
    1377     ulong[] arrulong = new ulong[4]; 
    1378     arrulong[0] = 100; 
    1379     arrulong[1] = 999; 
    1380     arrulong[3] = 0; 
    1381     r = std.string.format(arrulong); 
    1382     assert(r == "[100,999,0,0]"); 
    1383  
    1384     string[] arr2 = new string[4]; 
    1385     arr2[0] = "hello"; 
    1386     arr2[1] = "world"; 
    1387     arr2[3] = "foo"; 
    1388     r = std.string.format(arr2); 
    1389     assert(r == "[hello,world,,foo]"); 
    1390  
    1391     r = std.string.format("%.8d", 7); 
    1392     assert(r == "00000007"); 
    1393     r = std.string.format("%.8x", 10); 
    1394     assert(r == "0000000a"); 
    1395  
    1396     r = std.string.format("%-3d", 7); 
    1397     assert(r == "7  "); 
    1398  
    1399     r = std.string.format("%*d", -3, 7); 
    1400     assert(r == "7  "); 
    1401  
    1402     r = std.string.format("%.*d", -3, 7); 
    1403     assert(r == "7"); 
    1404  
    1405     typedef int myint; 
    1406     myint m = -7; 
    1407     r = std.string.format(m); 
    1408     assert(r == "-7"); 
    1409  
    1410     r = std.string.format("abc"c); 
    1411     assert(r == "abc"); 
    1412     r = std.string.format("def"w); 
    1413     assert(r == "def"); 
    1414     r = std.string.format("ghi"d); 
    1415     assert(r == "ghi"); 
    1416  
    1417     void* p = cast(void*)0xDEADBEEF; 
    1418     r = std.string.format(p); 
    1419     assert(r == "DEADBEEF"); 
    1420  
    1421     r = std.string.format("%#x", 0xabcd); 
    1422     assert(r == "0xabcd"); 
    1423     r = std.string.format("%#X", 0xABCD); 
    1424     assert(r == "0XABCD"); 
    1425  
    1426     r = std.string.format("%#o", 012345); 
    1427     assert(r == "012345"); 
    1428     r = std.string.format("%o", 9); 
    1429     assert(r == "11"); 
    1430  
    1431     r = std.string.format("%+d", 123); 
    1432     assert(r == "+123"); 
    1433     r = std.string.format("%+d", -123); 
    1434     assert(r == "-123"); 
    1435     r = std.string.format("% d", 123); 
    1436     assert(r == " 123"); 
    1437     r = std.string.format("% d", -123); 
    1438     assert(r == "-123"); 
    1439  
    1440     r = std.string.format("%%"); 
    1441     assert(r == "%"); 
    1442  
    1443     r = std.string.format("%d", true); 
    1444     assert(r == "1"); 
    1445     r = std.string.format("%d", false); 
    1446     assert(r == "0"); 
    1447  
    1448     r = std.string.format("%d", 'a'); 
    1449     assert(r == "97"); 
    1450     wchar wc = 'a'; 
    1451     r = std.string.format("%d", wc); 
    1452     assert(r == "97"); 
    1453     dchar dc = 'a'; 
    1454     r = std.string.format("%d", dc); 
    1455     assert(r == "97"); 
    1456  
    1457     byte b = byte.max; 
    1458     r = std.string.format("%x", b); 
    1459     assert(r == "7f"); 
    1460     r = std.string.format("%x", ++b); 
    1461     assert(r == "80"); 
    1462     r = std.string.format("%x", ++b); 
    1463     assert(r == "81"); 
    1464  
    1465     short sh = short.max; 
    1466     r = std.string.format("%x", sh); 
    1467     assert(r == "7fff"); 
    1468     r = std.string.format("%x", ++sh); 
    1469     assert(r == "8000"); 
    1470     r = std.string.format("%x", ++sh); 
    1471     assert(r == "8001"); 
    1472  
    1473     i = int.max; 
    1474     r = std.string.format("%x", i); 
    1475     assert(r == "7fffffff"); 
    1476     r = std.string.format("%x", ++i); 
    1477     assert(r == "80000000"); 
    1478     r = std.string.format("%x", ++i); 
    1479     assert(r == "80000001"); 
    1480  
    1481     r = std.string.format("%x", 10); 
    1482     assert(r == "a"); 
    1483     r = std.string.format("%X", 10); 
    1484     assert(r == "A"); 
    1485     r = std.string.format("%x", 15); 
    1486     assert(r == "f"); 
    1487     r = std.string.format("%X", 15); 
    1488     assert(r == "F"); 
    1489  
    1490     Object c = null; 
    1491     r = std.string.format(c); 
    1492     assert(r == "null"); 
    1493  
    1494     enum TestEnum 
    1495     { 
    1496             Value1, Value2 
    1497     } 
    1498     r = std.string.format("%s", TestEnum.Value2); 
    1499     assert(r == "1"); 
    1500  
    1501     immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); 
    1502     r = std.string.format("%s", aa.values); 
    1503     assert(r == "[[h,e,l,l,o],[b,e,t,t,y]]"); 
    1504     r = std.string.format("%s", aa); 
    1505     assert(r == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]"); 
    1506  
    1507     static const dchar[] ds = ['a','b']; 
    1508     for (int j = 0; j < ds.length; ++j) 
    1509     { 
    1510         r = std.string.format(" %d", ds[j]); 
    1511         if (j == 0) 
    1512             assert(r == " 97"); 
    1513         else 
    1514             assert(r == " 98"); 
    1515     } 
    1516  
    1517     r = std.string.format(">%14d<, ", 15, [1,2,3]); 
    1518     assert(r == ">            15<, [1,2,3]"); 
    1519  
    1520     assert(std.string.format("%8s", "bar") == "     bar"); 
    1521     assert(std.string.format("%8s", "b\u00e9ll\u00f4") == "   b\u00e9ll\u00f4"); 
    1522 
    1523  
    1524 // Andrei 
    1525 //------------------------------------------------------------------------------ 
    1526  
    1527 /* 
    1528  * A compiled version of an individual writef format 
    1529  * specifier. FormatInfo only focuses on representation, without 
    1530  * assigning any semantics to the fields. 
    1531  */ 
    1532 struct FormatInfo 
    1533 
     446    int width = 0; 
    1534447    /** 
    1535      * Special values for width and precision, DYNAMIC width or 
    1536      * precision means that they were specified with '*' in the format 
    1537      * string. 
     448       Precision. Its semantics depends on the argument type. For 
     449       floating point numbers, _precision dictates the number of 
     450       decimals printed. 
     451     */ 
     452    int precision = UNSPECIFIED; 
     453    /** 
     454       Special value for width and precision. $(D DYNAMIC) width or 
     455       precision means that they were specified with $(D '*') in the 
     456       format string and are passed at runtime through the varargs. 
    1538457     */ 
    1539458    enum int DYNAMIC = int.max; 
    1540     /** Special value for precision */ 
     459    /** 
     460       Special value for precision, meaning the format specifier 
     461       contained no explicit precision. 
     462     */ 
    1541463    enum int UNSPECIFIED = DYNAMIC - 1; 
    1542     /** minimum width, default 0.  */ 
    1543     int width = 0; 
    1544     /** precision. */ 
    1545     int precision = UNSPECIFIED; 
    1546     /** The actual format specifier, 's' by default. */ 
     464    /** 
     465       The actual format specifier, $(D 's') by default. 
     466    */ 
    1547467    char spec = 's'; 
    1548     /** Index of the argument for positional parameters, from 1 to 
    1549      * ubyte.max. (0 means not used) */ 
     468    /** 
     469       Index of the argument for positional parameters, from $(D 1) to 
     470       $(D ubyte.max). ($(D 0) means not used). 
     471    */ 
    1550472    ubyte index; 
    1551     /* Flags: flDash for '-', flZero for '0', flSpace for ' ', flPlus 
    1552      *  for '+', flHash for '#'. */ 
    1553     mixin(bitfields!( 
    1554                 bool, "flDash", 1, 
    1555                 bool, "flZero", 1, 
    1556                 bool, "flSpace", 1, 
    1557                 bool, "flPlus", 1, 
    1558                 bool, "flHash", 1, 
    1559                 ubyte, "", 3)); 
    1560     /* For arrays only: the trailing  */ 
    1561     const(char)[] innerTrailing, trailing; 
     473    version(ddoc) { 
     474        /** 
     475         The format specifier contained a $(D '-') ($(D printf) 
     476         compatibility). 
     477         */ 
     478        bool flDash; 
     479        /** 
     480         The format specifier contained a $(D '0') ($(D printf) 
     481         compatibility). 
     482         */ 
     483        bool flZero; 
     484        /** 
     485         The format specifier contained a $(D ' ') ($(D printf) 
     486         compatibility). 
     487         */ 
     488        bool flSpace; 
     489        /** 
     490         The format specifier contained a $(D '+') ($(D printf) 
     491         compatibility). 
     492         */ 
     493        bool flPlus; 
     494        /** 
     495         The format specifier contained a $(D '#') ($(D printf) 
     496         compatibility). 
     497         */ 
     498        bool flHash; 
     499        // Fake field to allow compilation 
     500        ubyte allFlags; 
     501    } 
     502    else 
     503    { 
     504        union 
     505        { 
     506            ubyte allFlags; 
     507            mixin(bitfields!( 
     508                        bool, "flDash", 1, 
     509                        bool, "flZero", 1, 
     510                        bool, "flSpace", 1, 
     511                        bool, "flPlus", 1, 
     512                        bool, "flHash", 1, 
     513                        ubyte, "", 3)); 
     514        } 
     515    } 
    1562516 
    1563517    /** 
    1564      * Given a string format specification fmt, parses a format 
    1565      * specifier. The string is assumed to start with the character 
    1566      * immediately following the '%'. The string is advanced to right 
    1567      * after the end of the format specifier. 
     518       In case of a compound format specifier starting with $(D 
     519       "%$(LPAREN)") and ending with $(D "%$(RPAREN)"), $(D _nested) 
     520       contains the string contained within the two separators. 
    1568521     */ 
    1569     this(S)(ref S fmt) 
    1570     { 
    1571         if (fmt.empty) return; 
    1572         scope(success) trailing = to!(typeof(trailing))(fmt); 
    1573         for (size_t i = 0; i < fmt.length; ) 
    1574         { 
    1575             switch (fmt[i]) 
     522    const(Char)[] nested; 
     523 
     524    /** 
     525       $(D _trailing) contains the rest of the format string. 
     526     */ 
     527    const(Char)[] trailing; 
     528 
     529    /* 
     530       This string is inserted before each sequence (e.g. array) 
     531       formatted (by default $(D "[")). 
     532     */ 
     533    static const(Char)[] seqBefore = "["; 
     534 
     535    /* 
     536       This string is inserted after each sequence formatted (by 
     537       default $(D "]")). 
     538     */ 
     539    static const(Char)[] seqAfter = "]"; 
     540 
     541    /* 
     542       This string is inserted in between elements of a sequence (by 
     543       default $(D ", ")). 
     544     */ 
     545    static const(Char)[] seqSeparator = ", "; 
     546 
     547    /** 
     548       Given a string format specification fmt, parses a format 
     549       specifier. The string is assumed to start with the character 
     550       immediately following the $(D '%'). The string is advanced to 
     551       right after the end of the format specifier. 
     552     */ 
     553    this(in Char[] fmt) 
     554    { 
     555        trailing = fmt; 
     556    } 
     557 
     558    bool writeUpToNextSpec(OutputRange)(OutputRange writer) 
     559    { 
     560        if (trailing.empty) return false; 
     561        for (size_t i = 0; i < trailing.length; ++i) 
     562        { 
     563            if (trailing[i] != '%') continue; 
     564            if (trailing[++i] != '%') 
     565            { 
     566                // Spec found. Print, fill up the spec, and bailout 
     567                put(writer, trailing[0 .. i - 1]); 
     568                trailing = trailing[i .. $]; 
     569                fillUp(); 
     570                return true; 
     571            } 
     572            // Doubled! Now print whatever we had, then update the 
     573            // string and move on 
     574            put(writer, trailing[0 .. i]); 
     575            trailing = trailing[i + 1 .. $]; 
     576            i = 0; 
     577        } 
     578        // no format spec found 
     579        put(writer, trailing); 
     580        trailing = null; 
     581        return false; 
     582    } 
     583 
     584    unittest 
     585    { 
     586        string s; 
     587        auto w = appender(&s); 
     588        auto f = FormatSpec("abc%sdef%sghi"); 
     589        f.writeUpToNextSpec(w); 
     590        assert(w.data == "abc", w.data); 
     591        assert(f.trailing == "def%sghi", f.trailing); 
     592        f.writeUpToNextSpec(w); 
     593        assert(w.data == "abcdef", w.data); 
     594        assert(f.trailing == "ghi"); 
     595        // test with embedded %%s 
     596        f = FormatSpec("ab%%cd%%ef%sg%%h%sij"); 
     597        w.clear; 
     598        f.writeUpToNextSpec(w); 
     599        assert(w.data == "ab%cd%ef" && f.trailing == "g%%h%sij", w.data); 
     600        f.writeUpToNextSpec(w); 
     601        assert(w.data == "ab%cd%efg%h" && f.trailing == "ij"); 
     602    } 
     603 
     604    private void fillUp() 
     605    { 
     606        // Reset content 
     607        allFlags = 0; 
     608        width = 0; 
     609        precision = UNSPECIFIED; 
     610        nested = null; 
     611        // Parse the spec (we assume we're past '%' already) 
     612        for (size_t i = 0; i < trailing.length; ) 
     613        { 
     614            switch (trailing[i]) 
    1576615            { 
    1577616                case '(': 
    1578617                { 
    1579                     // embedded format specifier 
     618                    // Embedded format specifier. 
    1580619                    auto j = i + 1; 
    1581620                    void check(bool condition) 
    1582621                    { 
    1583622                        enforce( 
    1584623                            condition, 
    1585                             text("Incorrect format specifier: %", fmt[i .. $])); 
     624                            text("Incorrect format specifier: %", 
     625                                    trailing[i .. $])); 
    1586626                    } 
     627                    // Get the matching balanced paren 
    1587628                    for (uint innerParens;; ++j) 
    1588629                    { 
    1589                         check(j < fmt.length); 
    1590                         if (fmt[j] != '%') 
     630                        check(j < trailing.length); 
     631                        if (trailing[j] != '%') 
    1591632                        { 
    1592633                            // skip, we're waiting for %( and %) 
    1593634                            continue; 
    1594635                        } 
    1595                         ++j; 
    1596                         if (fmt[j] == ')') 
     636                        if (trailing[++j] == ')') 
    1597637                        { 
    1598638                            if (innerParens-- == 0) break; 
    1599639                        } 
    1600                         else if (fmt[j] == '(') 
     640                        else if (trailing[j] == '(') 
    1601641                        { 
    1602642                            ++innerParens; 
    1603643                        } 
    1604644                    } 
    1605                     auto innerFmtSpec = fmt[i + 1 .. j - 1]
    1606                     this = FormatInfo(innerFmtSpec); 
    1607                     innerTrailing = to!(typeof(innerTrailing))(innerFmtSpec)
     645                    nested = to!(typeof(nested))(trailing[i + 1 .. j - 1])
     646                    //this = FormatSpec(innerTrailingSpec); 
     647                    spec = '('
    1608648                    // We practically found the format specifier 
    1609                     i = j + 1; 
    1610                     //raw = to!(const(char)[])(fmt[0 .. i]); 
    1611                     fmt = fmt[i .. $]; 
     649                    trailing = trailing[j + 1 .. $]; 
    1612650                    return; 
    1613651                } 
    1614652                case '-': flDash = true; ++i; break; 
    1615653                case '+': flPlus = true; ++i; break; 
    1616654                case '#': flHash = true; ++i; break; 
    1617655                case '0': flZero = true; ++i; break; 
    1618656                case ' ': flSpace = true; ++i; break; 
    1619657                case '*': 
    1620                     if (isdigit(fmt[++i])) 
     658                    if (isdigit(trailing[++i])) 
    1621659                    { 
    1622660                        // a '*' followed by digits and '$' is a 
    1623661                        // positional format 
    1624                         fmt = fmt[1 .. $]; 
    1625                         width = -.parse!(typeof(width))(fmt); 
     662                        trailing = trailing[1 .. $]; 
     663                        width = -.parse!(typeof(width))(trailing); 
    1626664                        i = 0; 
    1627                         enforce(fmt[i++] == '$', new FormatError("$ expected")); 
     665                        enforce(trailing[i++] == '$', 
     666                                new FormatError("$ expected")); 
    1628667                    } 
    1629668                    else 
    1630669                    { 
    1631670                        // read result 
    1632671                        width = DYNAMIC; 
    1633672                    } 
    1634673                    break; 
    1635674                case '1': .. case '9': 
    1636                     auto tmp = fmt[i .. $]; 
     675                    auto tmp = trailing[i .. $]; 
    1637676                    const widthOrArgIndex = .parse!(uint)(tmp); 
    1638677                    assert(tmp.length, 
    1639                             text("Incorrect format specifier %", fmt[i .. $])); 
    1640                     i = tmp.ptr - fmt.ptr; 
     678                            text("Incorrect format specifier %", 
     679                                    trailing[i .. $])); 
     680                    i = tmp.ptr - trailing.ptr; 
    1641681                    if (tmp.length && tmp[0] == '$') 
    1642682                    { 
    1643683                        // index! 
    1644684                        index = to!(ubyte)(widthOrArgIndex); 
    1645685                        ++i; 
    1646686                    } 
    1647687                    else 
    1648688                    { 
    1649689                        // width 
    1650690                        width = to!int(widthOrArgIndex); 
    1651691                    } 
    1652692                    break; 
    1653693                case '.': 
    1654                     if (fmt[++i] == '*') 
     694                    if (trailing[++i] == '*') 
    1655695                    { 
    1656                         if (isdigit(fmt[++i])) 
     696                        if (isdigit(trailing[++i])) 
    1657697                        { 
    1658698                            // a '.*' followed by digits and '$' is a 
    1659699                            // positional precision 
    1660                             fmt = fmt[i .. $]; 
     700                            trailing = trailing[i .. $]; 
    1661701                            i = 0; 
    1662                             precision = -.parse!(int)(fmt); 
    1663                             if (fmt[i++] != '$') 
     702                            precision = -.parse!(int)(trailing); 
     703                            if (trailing[i++] != '$') 
    1664704                            { 
    1665705                                throw new FormatError("$ expected"); 
    1666706                            } 
    1667707                        } 
    1668708                        else 
    1669709                        { 
    1670710                            // read result 
    1671711                            precision = DYNAMIC; 
    1672712                        } 
    1673713                    } 
    1674                     else if (fmt[i] == '-') 
     714                    else if (trailing[i] == '-') 
    1675715                    { 
    1676716                        // negative precision, as good as 0 
    1677717                        precision = 0; 
    1678                         auto tmp = fmt[i .. $]; 
     718                        auto tmp = trailing[i .. $]; 
    1679719                        .parse!(int)(tmp); // skip digits 
    1680                         i = tmp.ptr - fmt.ptr; 
     720                        i = tmp.ptr - trailing.ptr; 
    1681721                    } 
    1682                     else if (isdigit(fmt[i])) 
     722                    else if (isdigit(trailing[i])) 
    1683723                    { 
    1684                         auto tmp = fmt[i .. $]; 
     724                        auto tmp = trailing[i .. $]; 
    1685725                        precision = .parse!int(tmp); 
    1686                         i = tmp.ptr - fmt.ptr; 
     726                        i = tmp.ptr - trailing.ptr; 
    1687727                    } 
    1688728                    else 
    1689729                    { 
    1690730                        // "." was specified, but nothing after it 
    1691731                        precision = 0; 
    1692732                    } 
    1693733                    break; 
    1694734                default: 
    1695735                    // this is the format char 
    1696                     spec = cast(char)fmt[i++]; 
    1697                     fmt = fmt[i .. $]; 
     736                    spec = cast(char) trailing[i++]; 
     737                    trailing = trailing[i .. $]; 
    1698738                    return; 
    1699739            } // end switch 
    1700740        } // end for 
    1701         enforce(false, text("Incorrect format specifier: ", fmt)); 
    1702     } 
    1703 
    1704  
    1705 //------------------------------------------------------------------------------ 
    1706 // Writes characters in the format strings up to the first format 
    1707 // specifier and updates the format specifier to remove the written 
    1708 // portion The updated format fmt does not include the '%' 
    1709 private void writeUpToFormatSpec(OutRange, S)(OutRange w, ref S fmt) 
    1710 
    1711     for (size_t i = 0; i < fmt.length; ++i) 
    1712     { 
    1713         if (fmt[i] != '%') continue; 
    1714         if (fmt[++i] != '%') 
    1715         { 
    1716             // spec found, print and bailout 
    1717             w.put(fmt[0 .. i - 1]); 
    1718             fmt = fmt[i .. $]; 
    1719             return; 
    1720         } 
    1721         // doubled! Now print whatever we had, then update the string 
    1722         // and move on 
    1723         w.put(fmt[0 .. i]); 
    1724         fmt = fmt[i + 1 .. $]; 
    1725         i = 0; 
    1726     } 
    1727     // no format spec found 
    1728     w.put(fmt); 
    1729     fmt = null; 
     741        enforce(false, text("Incorrect format specifier: ", trailing)); 
     742    } 
     743 
     744    //-------------------------------------------------------------------------- 
     745    private bool readUpToNextSpec(R)(ref R r) 
     746    { 
     747        // Reset content 
     748        allFlags = 0; 
     749        width = 0; 
     750        precision = UNSPECIFIED; 
     751        nested = null; 
     752        // Parse the spec 
     753        while (trailing.length) 
     754        { 
     755            if (*trailing.ptr == '%') 
     756            { 
     757                if (trailing.length > 1 && trailing.ptr[1] == '%') 
     758                { 
     759                    assert(!r.empty); 
     760                    // Require a '%' 
     761                    if (r.front != '%') break; 
     762                    trailing = trailing[2 .. $]; 
     763                    r.popFront(); 
     764                } 
     765                else 
     766                { 
     767                    enforce(islower(trailing[1]) || trailing[1] == '*', 
     768                            text("'%", trailing[1], 
     769                                    "' not supported with formatted read")); 
     770                    trailing = trailing[1 .. $]; 
     771                    fillUp(); 
     772                    return true; 
     773                } 
     774            } 
     775            else 
     776            { 
     777                if (trailing.ptr[0] == ' ') 
     778                { 
     779                    while (!r.empty && isspace(r.front)) r.popFront(); 
     780                    //r = std.algorithm.find!(not!isspace)(r); 
     781                } 
     782                else 
     783                { 
     784                    enforce(!r.empty, 
     785                            text("parseToFormatSpec: Cannot find character `", 
     786                                    trailing.ptr[0], "' in the input string.")); 
     787                    if (r.front != trailing.front) break; 
     788                    r.popFront; 
     789                } 
     790                trailing.popFront(); 
     791            } 
     792        } 
     793        return false; 
     794    } 
     795 
     796    string toString() 
     797    { 
     798        return text("width = ", width, 
     799                "\nprecision = ", precision, 
     800                "\nspec = ", spec, 
     801                "\nindex = ", index, 
     802                "\nflDash = ", flDash, 
     803                "\nflZero = ", flZero, 
     804                "\nflSpace = ", flSpace, 
     805                "\nflPlus = ", flPlus, 
     806                "\nflHash = ", flHash, 
     807                "\nnested = ", nested, 
     808                "\ntrailing = ", trailing, "\n"); 
     809    } 
     810
     811 
     812/** 
     813   $(D void[]) is formatted like $(D ubyte[]). 
     814 */ 
     815void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f) 
     816if (is(const(T) == const(void[]))) 
     817
     818    w.put(cast(const ubyte[]) val); 
    1730819} 
    1731820 
    1732821unittest 
    1733822{ 
    1734     string s; 
    1735     auto w = appender(&s); 
    1736     string fmt = "abc%sdef%sghi"; 
    1737     writeUpToFormatSpec(w, fmt); 
    1738     assert(w.data == "abc"); 
    1739     assert(fmt == "sdef%sghi"); 
    1740     writeUpToFormatSpec(w, fmt); 
    1741     assert(w.data == "abcsdef" && fmt == "sghi"); 
    1742     // test with embedded %%s 
    1743     fmt = "ab%%cd%%ef%sg%%h%sij"; 
    1744     w.clear; 
    1745     writeUpToFormatSpec(w, fmt); 
    1746     assert(w.data == "ab%cd%ef" && fmt == "sg%%h%sij"); 
    1747     writeUpToFormatSpec(w, fmt); 
    1748     assert(w.data == "ab%cd%efsg%h" && fmt == "sij"); 
    1749 
    1750  
    1751 /* 
    1752  * Formats an integral number 'arg' according to 'f' and writes it to 
    1753  * 'w'. 
     823    FormatSpec!char f; 
     824    auto a = appender!string(); 
     825    void[] val; 
     826    formatValue(a, val, f); 
     827
     828 
     829/** 
     830   $(D enum) is formatted like its base value. 
    1754831 */ 
    1755 private void formatImpl(Writer, D)(Writer w, D argx, FormatInfo f) 
    1756 if (isIntegral!(D)) 
    1757 
    1758     Unqual!(D) arg = argx; 
     832void formatValue(Writer, T, Char)(Writer w, T val, 
     833        ref FormatSpec!Char f) 
     834if (is(T == enum)) 
     835
     836    static if (is(T Original == enum)) 
     837        formatValue(w, cast(Original) val, f); 
     838    else 
     839        static assert(0); 
     840
     841 
     842/** 
     843   Integrals are formatted like $(D printf) does. 
     844 */ 
     845void formatValue(Writer, T, Char)(Writer w, T val, 
     846        ref FormatSpec!Char f) 
     847if (isIntegral!T) 
     848
     849    Unqual!T arg = val; 
    1759850    if (f.spec == 'r') 
    1760851    { 
    1761852        // raw write, skip all else and write the thing 
    1762853        auto begin = cast(const char*) &arg; 
    1763854        if (std.system.endian == Endian.LittleEndian && f.flPlus 
    1764855            || std.system.endian == Endian.BigEndian && f.flDash) 
    1765856        { 
    1766857            // must swap bytes 
    1767858            foreach_reverse (i; 0 .. arg.sizeof) 
    1768859                w.put(begin[i]); 
    1769860        } 
    1770861        else 
    1771862        { 
    1772863            foreach (i; 0 .. arg.sizeof) 
    1773864                w.put(begin[i]); 
    1774865        } 
    1775866        return; 
    1776867    } 
    1777     if (f.precision == FormatInfo.UNSPECIFIED) 
     868    if (f.precision == f.UNSPECIFIED) 
    1778869    { 
    1779870        // default precision for integrals is 1 
    1780871        f.precision = 1; 
    1781872    } 
    1782873    else 
    1783874    { 
    1784875        // if a precision is specified, the '0' flag is ignored. 
    1785876        f.flZero = false; 
    1786877    } 
    1787878    char leftPad = void; 
     
    1813904    { 
    1814905        // argument is signed 
    1815906        forcedPrefix = '-'; 
    1816907        arg = -arg; 
    1817908    } 
    1818909    // fill the digits 
    1819910    char[] digits = void; 
    1820911    { 
    1821912        char buffer[64]; // 64 bits in base 2 at most 
    1822913        uint i = buffer.length; 
    1823         auto n = cast(Unsigned!(Unqual!(D))) arg; 
     914        auto n = cast(Unsigned!(Unqual!T)) arg; 
    1824915        do 
    1825916        { 
    1826917            --i; 
    1827918            buffer[i] = cast(char) (n % base); 
    1828919            n /= base; 
    1829920            if (buffer[i] < 10) buffer[i] += '0'; 
    1830921            else buffer[i] += (f.spec == 'x' ? 'a' : 'A') - 10; 
    1831922        } while (n); 
    1832923        digits = buffer[i .. $]; // got the digits without the sign 
    1833924    } 
     
    1873964    if (arg || f.precision) 
    1874965    { 
    1875966        int zerosToPrint = f.precision - digits.length; 
    1876967        foreach (i ; 0 .. zerosToPrint) w.put('0'); 
    1877968        w.put(digits); 
    1878969    } 
    1879970    // write the spaces to the right if left-align 
    1880971    if (!leftPad) foreach (i ; 0 .. spacesToPrint) w.put(' '); 
    1881972} 
    1882973 
    1883 /* 
    1884  * Formats a floating point number 'arg' according to 'f' and writes 
    1885  * it to 'w'. 
     974/** 
     975 * Floating-point values are formatted like $(D printf) does. 
    1886976 */ 
    1887 private void formatImpl(Writer, D)(Writer w, D obj, FormatInfo f) 
    1888 if (isFloatingPoint!(D)) 
     977void formatValue(Writer, D, Char)(Writer w, D obj, 
     978        ref FormatSpec!Char f) 
     979if (isFloatingPoint!D) 
    1889980{ 
    1890981    if (f.spec == 'r') 
    1891982    { 
    1892983        // raw write, skip all else and write the thing 
    1893984        auto begin = cast(const char*) &obj; 
    1894985        if (std.system.endian == Endian.LittleEndian && f.flPlus 
    1895986            || std.system.endian == Endian.BigEndian && f.flDash) 
    1896987        { 
    1897988            // must swap bytes 
    1898989            foreach_reverse (i; 0 .. obj.sizeof) 
     
    19231014    if (is(Unqual!D == real)) sprintfSpec[i++] = 'L'; 
    19241015    sprintfSpec[i++] = f.spec; 
    19251016    sprintfSpec[i] = 0; 
    19261017    //printf("format: '%s'; geeba: %g\n", sprintfSpec.ptr, obj); 
    19271018    char[512] buf; 
    19281019    //writeln("Spec is: ", sprintfSpec); 
    19291020    immutable n = snprintf(buf.ptr, buf.length, 
    19301021            sprintfSpec.ptr, 
    19311022            f.width, 
    19321023            // negative precision is same as no precision specified 
    1933             f.precision == FormatInfo.UNSPECIFIED ? -1 : f.precision, 
     1024            f.precision == f.UNSPECIFIED ? -1 : f.precision, 
    19341025            obj); 
    19351026    if (n < 0) throw new FormatError("floating point formatting failure"); 
    19361027    w.put(buf[0 .. strlen(buf.ptr)]); 
    19371028} 
    19381029 
    19391030unittest 
    19401031{ 
    19411032    auto a = appender!(string)(); 
    19421033    immutable real x = 5.5; 
    1943     FormatInfo f; 
    1944     formatImpl(a, x, f); 
     1034    FormatSpec!char f; 
     1035    formatValue(a, x, f); 
    19451036    assert(a.data == "5.5"); 
    19461037} 
    19471038 
    1948 /* 
    1949  * Formats an object of type 'D' according to 'f' and writes it to 
    1950  * 'w'. The pointer 'arg' is assumed to point to an object of type 
    1951  * 'D'. 
     1039/** 
     1040   $(D bool) is formatted as "true" or "false" with %s and as "1" or 
     1041   "0" with integral-specific format specs. 
    19521042 */ 
    1953 private void formatGeneric(Writer, D)(Writer w, const(void)* arg, 
    1954     FormatInfo f) 
    1955 
    1956     auto obj = *cast(D*) arg; 
    1957     static if (is(const(D) == const(void[]))) { 
    1958         auto s = cast(const char[]) obj; 
     1043void formatValue(Writer, T, Char)(Writer w, T val, 
     1044        ref FormatSpec!Char f) 
     1045if (is(T : bool)) 
     1046
     1047    if (f.spec == 's') { 
     1048        w.put(val ? "true" : "false"); 
     1049    } else { 
     1050        formatValue(w, cast(int) val, f); 
     1051    } 
     1052
     1053 
     1054/** 
     1055   Individual characters ($(D char), $(D wchar), or $(D dchar)) are 
     1056   formatted as Unicode characters with %s and as integers with 
     1057   integral-specific format specs. 
     1058 */ 
     1059void formatValue(Writer, T, Char)(Writer w, T val, 
     1060        ref FormatSpec!Char f) 
     1061if (isSomeChar!T) 
     1062
     1063    if (f.spec == 's') { 
     1064        w.put(val); 
     1065    } else { 
     1066        formatValue(w, cast(uint) val, f); 
     1067    } 
     1068
     1069 
     1070/** 
     1071   Strings are formatted like printf does. 
     1072 */ 
     1073void formatValue(Writer, T, Char)(Writer w, T val, 
     1074        ref FormatSpec!Char f) 
     1075if (isSomeString!T && !isStaticArray!T) 
     1076
     1077    auto s = val[0 .. f.precision < $ ? f.precision : $]; 
     1078    if (!f.flDash) 
     1079    { 
     1080        // right align 
     1081        if (f.width > s.length) 
     1082            foreach (i ; 0 .. f.width - s.length) w.put(' '); 
    19591083        w.put(s); 
    1960     } else static if (is(D Original == enum)) { 
    1961         formatGeneric!(Writer, Original)(w, arg, f); 
    1962     } else static if (is(D Original == typedef)) { 
    1963         formatGeneric!(Writer, Original)(w, arg, f); 
    1964     } else static if (isFloatingPoint!(D)) { 
    1965         formatImpl(w, obj, f); 
    1966     } else static if (is(const(D) == const ifloat)) { 
    1967         formatImpl(w, *cast(float*) &obj, f); 
    1968     } else static if (is(const(D) == const idouble)) { 
    1969         formatImpl(w, *cast(double*) &obj, f); 
    1970     } else static if (is(const(D) == const ireal)) { 
    1971         formatImpl(w, *cast(real*) &obj, f); 
    1972     } else static if (is(const(D) == const cfloat) 
    1973                       || is(const(D) == const cdouble) 
    1974                       || is(const(D) == const creal)) { 
    1975         formatImpl(w, obj.re, f); 
    1976         // @@@BUG 2367@@@ 
    1977         //w.write("+"); 
    1978         w.put('+'); 
    1979         formatImpl(w, obj.im, f); 
    1980         // @@@BUG 2367@@@ 
    1981         //w.put("i"); 
    1982         w.put('i'); 
    1983     } else static if (is(const D == const bool)) { 
    1984         if (f.spec == 's') { 
    1985             //@@@BUG 2606 
    1986             //w.put(obj ? "true" : "false"); 
    1987             auto s = obj ? "true" : "false"; 
    1988             w.put(s); 
    1989         } else { 
    1990             formatImpl(w, cast(int) obj, f); 
    1991         } 
    1992     } else static if (is(const(D) == const char) 
    1993             || is(const(D) == const wchar) 
    1994             || is(const(D) == const dchar)) { 
    1995         if (f.spec == 's') { 
    1996             w.put(obj); 
    1997         } else { 
    1998             formatImpl(w, cast(uint) obj, f); 
    1999         } 
    2000     } else static if (isIntegral!(D)) { 
    2001         formatImpl(w, obj, f); 
    2002     } else static if (is(D : const(char)[]) || is(D : const(wchar)[]) 
    2003                       || is(D : const(dchar)[])) { 
    2004         auto s = obj[0 .. f.precision < $ ? f.precision : $]; 
    2005         if (!f.flDash) 
    2006         { 
    2007             // right align 
    2008             if (f.width > s.length) 
    2009                 foreach (i ; 0 .. f.width - s.length) w.put(' '); 
    2010             w.put(s); 
    2011         } 
    2012         else 
    2013         { 
    2014             // left align 
    2015             w.put(s); 
    2016             if (f.width > s.length) 
    2017                 foreach (i ; 0 .. f.width - s.length) w.put(' '); 
    2018         } 
    2019     } else static if (is(D == void[0])) { 
    2020         w.put('['); 
    2021         w.put(']'); 
    2022     } else static if (isArray!(D)) { 
    2023         auto arr = obj[]; 
    2024         auto memberSpec = f; 
    2025         memberSpec.innerTrailing = null; 
    2026         if (f.spec == 'r') 
    2027         { 
    2028             // raw writes 
    2029             foreach (i, e; arr) formatGeneric!(Writer, typeof(e)) 
    2030                                     (w, &e, memberSpec); 
    2031         } 
    2032         else 
    2033         { 
    2034             if (arr.length == 0) return; 
    2035             // formatted writes 
    2036             formatGeneric!(Writer, typeof(*arr.ptr))(w, arr.ptr, memberSpec); 
    2037             if (!f.innerTrailing) 
     1084    } 
     1085    else 
     1086    { 
     1087        // left align 
     1088        w.put(s); 
     1089        if (f.width > s.length) 
     1090            foreach (i ; 0 .. f.width - s.length) w.put(' '); 
     1091    } 
     1092
     1093 
     1094/** 
     1095   Input ranges are formatted like arrays. 
     1096 */ 
     1097void formatValue(Writer, T, Char)(Writer w, T val, 
     1098        ref FormatSpec!Char f) 
     1099if (isInputRange!T && !isSomeChar!(ElementType!T)) 
     1100
     1101    auto arr = val; 
     1102    if (f.spec == 'r') 
     1103    { 
     1104        // raw writes 
     1105        for (size_t i; !arr.empty; arr.popFront(), ++i) 
     1106        { 
     1107            if (f.spec == '(') 
    20381108            { 
    2039                 foreach (i, e; arr[1 .. $]) 
    2040                 { 
    2041                     w.put(' '); 
    2042                     formatGeneric!(Writer, typeof(e))(w, &e, f); 
    2043                 } 
     1109                // It's a nested format specifier 
     1110                formattedWrite(w, f.nested, arr.front); 
    20441111            } 
    20451112            else 
    20461113            { 
    2047                 const hasEscapes = 
    2048                     std.algorithm.find(f.innerTrailing, '%').length > 0; 
    2049                 foreach (i, e; arr[1 .. $]) 
    2050                 { 
    2051                     if (hasEscapes) 
    2052                     { 
    2053                         foreach (dchar c; f.innerTrailing) 
    2054                         { 
    2055                             if (c == '%') continue; 
    2056                             w.put(c); 
    2057                         } 
    2058                     } 
    2059                     else 
    2060                     { 
    2061                         w.put(f.innerTrailing); 
    2062                     } 
    2063                     formatGeneric!(Writer, typeof(e))(w, &e, f); 
    2064                 } 
     1114                formatValue(w, arr.front, f); 
    20651115            } 
    20661116        } 
    2067     } else static if (is(const(D) : const void*)) { 
    2068         if (f.spec!='x') f.spec = 'X'; 
    2069         const fake = cast(ulong) obj; 
    2070         formatGeneric!(Writer, ulong)(w, &fake, f); 
    2071     } else static if (is(const(D) : const Object)) { 
    2072         // @@@BUG 2367@@@ 
    2073         //if (obj is null) w.write("null"); 
    2074         if (obj is null) w.put("null"[]); 
    2075         else w.put(obj.toString); 
    2076     } else static if (isAssociativeArray!(D)) { 
    2077         // somebody rid me of this hack 
    2078         w.put(std.string.format("%s", obj)); 
    2079     } else static if (is(D : const(char))) { 
    2080         // somebody rid me of this hack 
    2081         w.put(obj); 
    2082     } else static if (is(typeof(to!string(obj)))) { 
    2083         w.put(to!string(obj)); 
    2084     } else static if (is(typeof(&obj.toString))) { 
    2085         auto s = obj.toString; 
    2086         w.put(s); 
    2087     } else { 
    2088         // last resort: just print type name 
    2089         w.put(D.stringof); 
    2090     } 
     1117    } 
     1118    else 
     1119    { 
     1120        if (arr.empty) return; 
     1121        // formatted writes 
     1122        if (!f.nested) 
     1123        { 
     1124            w.put(f.seqBefore); 
     1125            scope(exit) w.put(f.seqAfter); 
     1126            formatValue(w, arr.front, f); 
     1127            arr.popFront(); 
     1128            for (size_t i; !arr.empty; arr.popFront(), ++i) 
     1129            { 
     1130                w.put(f.seqSeparator); 
     1131                formatValue(w, arr.front, f); 
     1132            } 
     1133        } 
     1134        else 
     1135        { 
     1136            // Nested specifier is to be used 
     1137            for (;;) 
     1138            { 
     1139                auto f = FormatSpec!Char(f.nested); 
     1140                f.writeUpToNextSpec(w); 
     1141                formatValue(w, arr.front, f); 
     1142                arr.popFront(); 
     1143                if (arr.empty) break; 
     1144                f.writeUpToNextSpec(w); 
     1145            } 
     1146 
     1147            // auto itemFormatString = f.nested; 
     1148            // // First, parse and figure the format spec 
     1149            // // Skip to the format spec 
     1150            // size_t i = 0; 
     1151            // while (i < itemFormatString.length) 
     1152            // { 
     1153            //     if (itemFormatString[i++] != '%') continue; 
     1154            //     enforce(i < itemFormatString.length); 
     1155            //     if (itemFormatString[i] != '%') break; 
     1156            //     ++i; 
     1157            // } 
     1158            // auto head = itemFormatString[0 .. i - 1]; 
     1159            // itemFormatString = itemFormatString[i .. $]; 
     1160            // auto itemFmt = FormatSpec(itemFormatString); 
     1161            // auto tail = itemFormatString; 
     1162            // for (;;) 
     1163            // { 
     1164            //     auto headCopy = head; 
     1165            //     writeUpToFormatSpec(w, headCopy); 
     1166            //     formatValue(w, arr.front, itemFmt); 
     1167            //     arr.popFront(); 
     1168            //     if (arr.empty) break; 
     1169            //     w.put(tail); 
     1170            // } 
     1171        } 
     1172    } 
     1173
     1174 
     1175/** 
     1176   $(D void[0]) is formatted as "[]". 
     1177 */ 
     1178void formatValue(Writer, T, Char)(Writer w, T val, 
     1179        ref FormatSpec!Char f) 
     1180if (is(D == void[0])) 
     1181
     1182    w.put(seqBefore); 
     1183    w.put(seqAfter); 
     1184
     1185 
     1186/** 
     1187   Pointers are formatted as hex integers. 
     1188 */ 
     1189void formatValue(Writer, T, Char)(Writer w, T val, 
     1190        ref FormatSpec!Char f) 
     1191if (isPointer!T) 
     1192
     1193    const void * p = val; 
     1194    f.spec = 'X'; 
     1195    formatValue(w, cast(ulong) p, f); 
     1196
     1197 
     1198/** 
     1199   Objects are formatted by calling $(D toString). 
     1200 */ 
     1201void formatValue(Writer, T, Char)(Writer w, T val, 
     1202        ref FormatSpec!Char f) 
     1203if (is(T == class)) 
     1204
     1205    if (val is null) w.put("null"); 
     1206    else w.put(val.toString); 
     1207
     1208 
     1209/** 
     1210   Associative arrays are formatted by using $(D ':') and $(D ' ') as 
     1211   separators. 
     1212 */ 
     1213void formatValue(Writer, T, Char)(Writer w, T val, 
     1214        ref FormatSpec!Char f) 
     1215if (isAssociativeArray!T) 
     1216
     1217    bool firstTime = true; 
     1218    foreach (ref k, v; val) 
     1219    { 
     1220        if (firstTime) firstTime = false; 
     1221        else w.put(' '); 
     1222        formatValue(w, k, f); 
     1223        w.put(':'); 
     1224        formatValue(w, v, f); 
     1225    } 
     1226
     1227 
     1228/** 
     1229   Associative arrays are formatted by using $(D ':') and $(D ' ') as 
     1230   separators. 
     1231 */ 
     1232void formatValue(Writer, T, Char)(Writer w, T val, 
     1233        ref FormatSpec!Char f) 
     1234if (is(T == struct) && !isInputRange!T) 
     1235
     1236    if (is(typeof(w.put(val.toString)))) 
     1237    { 
     1238        w.put(val.toString); 
     1239    } 
     1240    else 
     1241    { 
     1242        w.put(S.stringof); 
     1243    } 
     1244
     1245 
     1246/** 
     1247   Static-size arrays are formatted just like arrays. 
     1248 */ 
     1249void formatValue(Writer, T, Char)(Writer w, ref T val, 
     1250        ref FormatSpec!Char f) 
     1251if (isStaticArray!T) 
     1252
     1253    formatValue(w, val[], f); 
     1254
     1255 
     1256/* 
     1257  Formats an object of type 'D' according to 'f' and writes it to 
     1258  'w'. The pointer 'arg' is assumed to point to an object of type 
     1259  'D'. The untyped signature is for the sake of taking this function's 
     1260  address. 
     1261 */ 
     1262private void formatGeneric(Writer, D, Char)(Writer w, const(void)* arg, 
     1263        ref FormatSpec!Char f) 
     1264
     1265    formatValue(w, *cast(D*) arg, f); 
    20911266} 
    20921267 
    20931268unittest 
    20941269{ 
    20951270    string s; 
    20961271    auto w = appender(&s); 
    20971272    int[] a = [ 1, 3, 2 ]; 
    2098            formattedWrite(w, "testing %(s, %) embedded", a); 
    2099     assert(w.data == "testing 1, 3, 2 embedded", w.data); 
     1273    formattedWrite(w, "testing %(%s & %) embedded", a); 
     1274    assert(w.data == "testing 1 & 3 & 2 embedded", w.data); 
    21001275    w.clear; 
    2101     formattedWrite(w, "testing (%(s) (%)) embedded", a); 
    2102     assert(w.data == "testing (1) (3) (2) embedded", w.data); 
     1276    formattedWrite(w, "testing %((%s) %)) wyda3", a); 
     1277    assert(w.data == "testing (1) (3) (2) wyda3", w.data); 
    21031278 
    21041279    int[0] empt = []; 
    21051280    w.clear; 
    21061281    formattedWrite(w, "(%s)", empt); 
    21071282    assert(w.data == "()", w.data); 
    21081283} 
    21091284 
    21101285//------------------------------------------------------------------------------ 
    21111286// Fix for issue 1591 
    21121287private int getNthInt(A...)(uint index, A args) 
     
    21251300        { 
    21261301            throw new FormatError("int expected"); 
    21271302        } 
    21281303    } 
    21291304    else 
    21301305    { 
    21311306        throw new FormatError("int expected"); 
    21321307    } 
    21331308} 
    21341309 
    2135 /* 
    2136 (Not public yet.)  Formats arguments $(D args) according to the format 
    2137 string $(D fmt) and writes the result to $(D w). $(D F) must be $(D 
    2138 char), $(D wchar), or $(D dchar). 
    2139  
    2140 Example: 
    2141 ------------------------- 
    2142 import std.c.stdio; 
    2143 import std.format; 
    2144  
    2145 string myFormat(A...)(A args) 
    2146 { 
    2147     auto writer = appender!string(); 
    2148     std.format.formattedWrite(writer, 
    2149         "%s et %s numeris romanis non sunt", args); 
    2150     return writer.data; 
    2151 } 
    2152 ... 
    2153 int x = 42; 
    2154 assert(myFormat(x, 0) == "42 et 0 numeris romanis non sunt"); 
    2155 ------------------------ 
    2156  
    2157 $(D formattedWrite) supports positional parameter syntax in $(WEB 
    2158 opengroup.org/onlinepubs/009695399/functions/printf.html, POSIX) 
    2159 style.  Example: 
    2160  
    2161 ------------------------- 
    2162 auto writer = appender!string(); 
    2163 std.format.formattedWrite(writer, "Date: %2$s %1$s", "October", 5); 
    2164 assert(writer.data == "Date: 5 October"); 
    2165 ------------------------ 
    2166  
    2167 The positional and non-positional styles can be mixed in the same 
    2168 format string. (POSIX leaves this behavior undefined.) The internal 
    2169 counter for non-positional parameters tracks the next parameter after 
    2170 the largest positional parameter already used. 
    2171  
    2172 Warning: 
    2173 This is the function internally used by writef* but it's still 
    2174 undergoing active development. Do not rely on it. 
    2175  */ 
    2176  
    2177 void formattedWrite(Writer, F, A...)(Writer w, const(F)[] fmt, A args) 
    2178 { 
    2179     enum len = args.length; 
    2180     void function(Writer, const(void)*, FormatInfo) funs[len] = void; 
    2181     const(void)* argsAddresses[len] = void; 
    2182     foreach (i, arg; args) 
    2183     { 
    2184         funs[i] = &formatGeneric!(Writer, typeof(arg)); 
    2185         argsAddresses[i] = &arg; 
    2186     } 
    2187     // Are we already done with formats? Then just dump each parameter in turn 
    2188     uint currentArg = 0; 
    2189     for (;;) 
    2190     { 
    2191         writeUpToFormatSpec(w, fmt); 
    2192         if (fmt.length == 0) 
    2193         { 
    2194             // New behavior: break if there are too few specifiers AND 
    2195             // at least one specifier did exist 
    2196             break; 
    2197         } 
    2198         auto spec = FormatInfo(fmt); 
    2199         if (currentArg == funs.length && !spec.index) 
    2200         { 
    2201             // leftover spec? 
    2202             enforce(fmt.length == 0, new FormatError( 
    2203                     cast(string) ("Orphan format specifier: %" ~ fmt))); 
    2204             break; 
    2205         } 
    2206         if (spec.width == FormatInfo.DYNAMIC) 
    2207         { 
    2208             auto width = to!(typeof(spec.width))(getNthInt(currentArg, args)); 
    2209             if (width < 0) 
    2210             { 
    2211                 spec.flDash = true; 
    2212                 width = -width; 
    2213             } 
    2214             spec.width = width; 
    2215             ++currentArg; 
    2216         } 
    2217         else if (spec.width < 0) 
    2218         { 
    2219             // means: get width as a positional parameter 
    2220             auto index = cast(uint) -spec.width; 
    2221             auto width = to!(typeof(spec.width))(getNthInt(index, args)); 
    2222             if (currentArg < index) currentArg = index; 
    2223             if (width < 0) 
    2224             { 
    2225                 spec.flDash = true; 
    2226                 width = -width; 
    2227             } 
    2228             spec.width = width; 
    2229         } 
    2230         if (spec.precision == FormatInfo.DYNAMIC) 
    2231         { 
    2232             auto precision = to!(typeof(spec.precision))( 
    2233                 getNthInt(currentArg, args)); 
    2234             if (precision >= 0) spec.precision = precision; 
    2235             // else negative precision is same as no precision 
    2236             else spec.precision = FormatInfo.UNSPECIFIED; 
    2237             ++currentArg; 
    2238         } 
    2239         else if (spec.precision < 0) 
    2240         { 
    2241             // means: get precision as a positional parameter 
    2242             auto index = cast(uint) -spec.precision; 
    2243             auto precision = to!(typeof(spec.precision))( 
    2244                 getNthInt(index, args)); 
    2245             if (currentArg < index) currentArg = index; 
    2246             if (precision >= 0) spec.precision = precision; 
    2247             // else negative precision is same as no precision 
    2248             else spec.precision = FormatInfo.UNSPECIFIED; 
    2249         } 
    2250         // Format! 
    2251         if (spec.index > 0) 
    2252         { 
    2253             // using positional parameters! 
    2254             funs[spec.index - 1](w, argsAddresses[spec.index - 1], spec); 
    2255             if (currentArg < spec.index) currentArg = spec.index; 
    2256         } 
    2257         else 
    2258         { 
    2259             funs[currentArg](w, argsAddresses[currentArg], spec); 
    2260             ++currentArg; 
    2261         } 
    2262     } 
    2263 } 
    2264  
    22651310/* ======================== Unit Tests ====================================== */ 
    22661311 
    22671312unittest 
    22681313{ 
    22691314    auto stream = appender!string(); 
    22701315    formattedWrite(stream, "%s", 1.1); 
    22711316    assert(stream.data == "1.1", stream.data); 
     1317 
     1318    stream = appender!string(); 
     1319    formattedWrite(stream, "%s", map!"a*a"([2, 3, 5])); 
     1320    assert(stream.data == "[4, 9, 25]", stream.data); 
    22721321} 
    22731322 
    22741323unittest 
    22751324{ 
    22761325    auto stream = appender!string(); 
    22771326    formattedWrite(stream, "%u", 42); 
    22781327    assert(stream.data == "42", stream.data); 
    22791328} 
    22801329 
    22811330unittest 
     
    23171366    auto stream = appender!string(); 
    23181367    //goto here; 
    23191368 
    23201369    formattedWrite(stream, 
    23211370            "hello world! %s %s ", true, 57, 1_000_000_000, 'x', " foo"); 
    23221371    assert(stream.data == "hello world! true 57 ", 
    23231372        stream.data); 
    23241373 
    23251374    stream.clear; 
    23261375    formattedWrite(stream, "%g %A %s", 1.67, -1.28, float.nan); 
    2327   //std.c.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr); 
    2328   /* The host C library is used to format floats. 
    2329    * C99 doesn't specify what the hex digit before the decimal point 
    2330    * is for %A. 
    2331    */ 
    2332   version (linux) 
    2333       assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan", stream.data); 
    2334   else 
    2335       assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan"); 
    2336   stream.clear; 
    2337  
    2338   formattedWrite(stream, "%x %X", 0x1234AF, 0xAFAFAFAF); 
    2339   assert(stream.data == "1234af AFAFAFAF"); 
    2340   stream.clear; 
    2341  
    2342   formattedWrite(stream, "%b %o", 0x1234AF, 0xAFAFAFAF); 
    2343   assert(stream.data == "100100011010010101111 25753727657"); 
    2344   stream.clear; 
    2345  
    2346   formattedWrite(stream, "%d %s", 0x1234AF, 0xAFAFAFAF); 
    2347   assert(stream.data == "1193135 2947526575"); 
    2348   stream.clear; 
    2349  
    2350   formattedWrite(stream, "%s", 1.2 + 3.4i); 
    2351   assert(stream.data == "1.2+3.4i"); 
    2352   stream.clear; 
    2353  
    2354   formattedWrite(stream, "%a %A", 1.32, 6.78f); 
    2355   //formattedWrite(stream, "%x %X", 1.32); 
    2356   assert(stream.data == "0x1.51eb851eb851fp+0 0X1.B1EB86P+2"); 
    2357   stream.clear; 
    2358  
    2359   formattedWrite(stream, "%#06.*f",2,12.345); 
    2360   assert(stream.data == "012.35"); 
    2361   stream.clear; 
    2362  
    2363   formattedWrite(stream, "%#0*.*f",6,2,12.345); 
    2364   assert(stream.data == "012.35"); 
    2365   stream.clear; 
    2366  
    2367   const real constreal = 1; 
    2368   formattedWrite(stream, "%g",constreal); 
    2369   assert(stream.data == "1"); 
    2370   stream.clear; 
    2371  
    2372   formattedWrite(stream, "%7.4g:", 12.678); 
    2373   assert(stream.data == "  12.68:"); 
    2374   stream.clear; 
    2375  
    2376   formattedWrite(stream, "%7.4g:", 12.678L); 
    2377   assert(stream.data == "  12.68:"); 
    2378   stream.clear; 
    2379  
    2380   formattedWrite(stream, "%04f|%05d|%#05x|%#5x",-4.,-10,1,1); 
    2381   assert(stream.data == "-4.000000|-0010|0x001|  0x1", 
    2382       stream.data); 
    2383   stream.clear; 
    2384  
    2385   int i; 
    2386   string s; 
    2387  
    2388   i = -10; 
    2389   formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
    2390   assert(stream.data == "-10|-10|-10|-10|-10.0000"); 
    2391   stream.clear; 
    2392  
    2393   i = -5; 
    2394   formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
    2395   assert(stream.data == "-5| -5|-05|-5|-5.0000"); 
    2396   stream.clear; 
    2397  
    2398   i = 0; 
    2399   formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
    2400   assert(stream.data == "0|  0|000|0|0.0000"); 
    2401   stream.clear; 
    2402  
    2403   i = 5; 
    2404   formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
    2405   assert(stream.data == "5|  5|005|5|5.0000"); 
    2406   stream.clear; 
    2407  
    2408   i = 10; 
    2409   formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
    2410   assert(stream.data == "10| 10|010|10|10.0000"); 
    2411   stream.clear; 
    2412  
    2413   formattedWrite(stream, "%.0d", 0); 
    2414   assert(stream.data == ""); 
    2415   stream.clear; 
    2416  
    2417   formattedWrite(stream, "%.g", .34); 
    2418   assert(stream.data == "0.3"); 
    2419   stream.clear; 
    2420  
    2421   stream.clear; formattedWrite(stream, "%.0g", .34); 
    2422   assert(stream.data == "0.3"); 
    2423  
    2424   stream.clear; formattedWrite(stream, "%.2g", .34); 
    2425   assert(stream.data == "0.34"); 
    2426  
    2427   stream.clear; formattedWrite(stream, "%0.0008f", 1e-08); 
    2428   assert(stream.data == "0.00000001"); 
    2429  
    2430   stream.clear; formattedWrite(stream, "%0.0008f", 1e-05); 
    2431   assert(stream.data == "0.00001000"); 
    2432  
    2433   //return; 
    2434   //std.c.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr); 
    2435  
    2436   s = "helloworld"; 
    2437   string r; 
    2438   stream.clear; formattedWrite(stream, "%.2s", s[0..5]); 
    2439   assert(stream.data == "he"); 
    2440   stream.clear; formattedWrite(stream, "%.20s", s[0..5]); 
    2441   assert(stream.data == "hello"); 
    2442   stream.clear; formattedWrite(stream, "%8s", s[0..5]); 
    2443   assert(stream.data == "   hello"); 
     1376    // std.c.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr); 
     1377 
     1378    /* The host C library is used to format floats.  C99 doesn't 
     1379    * specify what the hex digit before the decimal point is for 
     1380    * %A.  */ 
     1381 
     1382    version (linux) 
     1383    { 
     1384        assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan", 
     1385                stream.data); 
     1386    } 
     1387    else 
     1388    { 
     1389        assert(stream.data == "1.67-0X1.47AE147AE147BP+0 nan"); 
     1390    } 
     1391    stream.clear; 
     1392 
     1393    formattedWrite(stream, "%x %X", 0x1234AF, 0xAFAFAFAF); 
     1394    assert(stream.data == "1234af AFAFAFAF"); 
     1395    stream.clear; 
     1396 
     1397    formattedWrite(stream, "%b %o", 0x1234AF, 0xAFAFAFAF); 
     1398    assert(stream.data == "100100011010010101111 25753727657"); 
     1399    stream.clear; 
     1400 
     1401    formattedWrite(stream, "%d %s", 0x1234AF, 0xAFAFAFAF); 
     1402    assert(stream.data == "1193135 2947526575"); 
     1403    stream.clear; 
     1404 
     1405    // formattedWrite(stream, "%s", 1.2 + 3.4i); 
     1406    // assert(stream.data == "1.2+3.4i"); 
     1407    // stream.clear; 
     1408 
     1409    formattedWrite(stream, "%a %A", 1.32, 6.78f); 
     1410    //formattedWrite(stream, "%x %X", 1.32); 
     1411    assert(stream.data == "0x1.51eb851eb851fp+0 0X1.B1EB86P+2"); 
     1412    stream.clear; 
     1413 
     1414    formattedWrite(stream, "%#06.*f",2,12.345); 
     1415    assert(stream.data == "012.35"); 
     1416    stream.clear; 
     1417 
     1418    formattedWrite(stream, "%#0*.*f",6,2,12.345); 
     1419    assert(stream.data == "012.35"); 
     1420    stream.clear; 
     1421 
     1422    const real constreal = 1; 
     1423    formattedWrite(stream, "%g",constreal); 
     1424    assert(stream.data == "1"); 
     1425    stream.clear; 
     1426 
     1427    formattedWrite(stream, "%7.4g:", 12.678); 
     1428    assert(stream.data == "  12.68:"); 
     1429    stream.clear; 
     1430 
     1431    formattedWrite(stream, "%7.4g:", 12.678L); 
     1432    assert(stream.data == "  12.68:"); 
     1433    stream.clear; 
     1434 
     1435    formattedWrite(stream, "%04f|%05d|%#05x|%#5x",-4.,-10,1,1); 
     1436    assert(stream.data == "-4.000000|-0010|0x001|  0x1", 
     1437            stream.data); 
     1438    stream.clear; 
     1439 
     1440    int i; 
     1441    string s; 
     1442 
     1443    i = -10; 
     1444    formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
     1445    assert(stream.data == "-10|-10|-10|-10|-10.0000"); 
     1446    stream.clear; 
     1447 
     1448    i = -5; 
     1449    formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
     1450    assert(stream.data == "-5| -5|-05|-5|-5.0000"); 
     1451    stream.clear; 
     1452 
     1453    i = 0; 
     1454    formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
     1455    assert(stream.data == "0|  0|000|0|0.0000"); 
     1456    stream.clear; 
     1457 
     1458    i = 5; 
     1459    formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
     1460    assert(stream.data == "5|  5|005|5|5.0000"); 
     1461    stream.clear; 
     1462 
     1463    i = 10; 
     1464    formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
     1465    assert(stream.data == "10| 10|010|10|10.0000"); 
     1466    stream.clear; 
     1467 
     1468    formattedWrite(stream, "%.0d", 0); 
     1469    assert(stream.data == ""); 
     1470    stream.clear; 
     1471 
     1472    formattedWrite(stream, "%.g", .34); 
     1473    assert(stream.data == "0.3"); 
     1474    stream.clear; 
     1475 
     1476    stream.clear; formattedWrite(stream, "%.0g", .34); 
     1477    assert(stream.data == "0.3"); 
     1478 
     1479    stream.clear; formattedWrite(stream, "%.2g", .34); 
     1480    assert(stream.data == "0.34"); 
     1481 
     1482    stream.clear; formattedWrite(stream, "%0.0008f", 1e-08); 
     1483    assert(stream.data == "0.00000001"); 
     1484 
     1485    stream.clear; formattedWrite(stream, "%0.0008f", 1e-05); 
     1486    assert(stream.data == "0.00001000"); 
     1487 
     1488    //return; 
     1489    //std.c.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr); 
     1490 
     1491    s = "helloworld"; 
     1492    string r; 
     1493    stream.clear; formattedWrite(stream, "%.2s", s[0..5]); 
     1494    assert(stream.data == "he"); 
     1495    stream.clear; formattedWrite(stream, "%.20s", s[0..5]); 
     1496    assert(stream.data == "hello"); 
     1497    stream.clear; formattedWrite(stream, "%8s", s[0..5]); 
     1498    assert(stream.data == "   hello"); 
    24441499 
    24451500    byte[] arrbyte = new byte[4]; 
    24461501    arrbyte[0] = 100; 
    24471502    arrbyte[1] = -99; 
    24481503    arrbyte[3] = 0; 
    24491504    stream.clear; formattedWrite(stream, "%s", arrbyte); 
    2450     assert(stream.data == "100 -99 0 0", stream.data); 
    2451  
    2452   ubyte[] arrubyte = new ubyte[4]; 
    2453   arrubyte[0] = 100; 
    2454   arrubyte[1] = 200; 
    2455   arrubyte[3] = 0; 
    2456   stream.clear; formattedWrite(stream, "%s", arrubyte); 
    2457     assert(stream.data == "100 200 0 0"); 
    2458  
    2459   short[] arrshort = new short[4]; 
    2460   arrshort[0] = 100; 
    2461   arrshort[1] = -999; 
    2462   arrshort[3] = 0; 
    2463   stream.clear; formattedWrite(stream, "%s", arrshort); 
    2464   assert(stream.data == "100 -999 0 0"); 
    2465   stream.clear; formattedWrite(stream, "%s",arrshort); 
    2466   assert(stream.data == "100 -999 0 0"); 
    2467  
    2468   ushort[] arrushort = new ushort[4]; 
    2469   arrushort[0] = 100; 
    2470   arrushort[1] = 20_000; 
    2471   arrushort[3] = 0; 
    2472   stream.clear; formattedWrite(stream, "%s", arrushort); 
    2473     assert(stream.data == "100 20000 0 0"); 
    2474  
    2475   int[] arrint = new int[4]; 
    2476   arrint[0] = 100; 
    2477   arrint[1] = -999; 
    2478   arrint[3] = 0; 
    2479   stream.clear; formattedWrite(stream, "%s", arrint); 
    2480     assert(stream.data == "100 -999 0 0"); 
    2481   stream.clear; formattedWrite(stream, "%s",arrint); 
    2482   assert(stream.data == "100 -999 0 0"); 
    2483  
    2484   long[] arrlong = new long[4]; 
    2485   arrlong[0] = 100; 
    2486   arrlong[1] = -999; 
    2487   arrlong[3] = 0; 
    2488   stream.clear; formattedWrite(stream, "%s", arrlong); 
    2489   assert(stream.data == "100 -999 0 0"); 
    2490   stream.clear; formattedWrite(stream, "%s",arrlong); 
    2491   assert(stream.data == "100 -999 0 0"); 
    2492  
    2493   ulong[] arrulong = new ulong[4]; 
    2494   arrulong[0] = 100; 
    2495   arrulong[1] = 999; 
    2496   arrulong[3] = 0; 
    2497   stream.clear; formattedWrite(stream, "%s", arrulong); 
    2498   assert(stream.data == "100 999 0 0"); 
    2499  
    2500   string[] arr2 = new string[4]; 
    2501   arr2[0] = "hello"; 
    2502   arr2[1] = "world"; 
    2503   arr2[3] = "foo"; 
    2504   stream.clear; formattedWrite(stream, "%s", arr2); 
    2505   assert(stream.data == "hello world  foo"); 
    2506  
    2507   stream.clear; formattedWrite(stream, "%.8d", 7); 
    2508   assert(stream.data == "00000007"); 
    2509  
    2510   stream.clear; formattedWrite(stream, "%.8x", 10); 
    2511   assert(stream.data == "0000000a"); 
    2512  
    2513   stream.clear; formattedWrite(stream, "%-3d", 7); 
    2514   assert(stream.data == "7  "); 
    2515  
    2516   stream.clear; formattedWrite(stream, "%*d", -3, 7); 
    2517   assert(stream.data == "7  "); 
    2518  
    2519   stream.clear; formattedWrite(stream, "%.*d", -3, 7); 
    2520   //writeln(stream.data); 
    2521   assert(stream.data == "7"); 
     1505    assert(stream.data == "[100, -99, 0, 0]", stream.data); 
     1506 
     1507    ubyte[] arrubyte = new ubyte[4]; 
     1508    arrubyte[0] = 100; 
     1509    arrubyte[1] = 200; 
     1510    arrubyte[3] = 0; 
     1511    stream.clear; formattedWrite(stream, "%s", arrubyte); 
     1512    assert(stream.data == "[100, 200, 0, 0]", stream.data); 
     1513 
     1514    short[] arrshort = new short[4]; 
     1515    arrshort[0] = 100; 
     1516    arrshort[1] = -999; 
     1517    arrshort[3] = 0; 
     1518    stream.clear; formattedWrite(stream, "%s", arrshort); 
     1519    assert(stream.data == "[100, -999, 0, 0]"); 
     1520    stream.clear; formattedWrite(stream, "%s",arrshort); 
     1521    assert(stream.data == "[100, -999, 0, 0]"); 
     1522 
     1523    ushort[] arrushort = new ushort[4]; 
     1524    arrushort[0] = 100; 
     1525    arrushort[1] = 20_000; 
     1526    arrushort[3] = 0; 
     1527    stream.clear; formattedWrite(stream, "%s", arrushort); 
     1528    assert(stream.data == "[100, 20000, 0, 0]"); 
     1529 
     1530    int[] arrint = new int[4]; 
     1531    arrint[0] = 100; 
     1532    arrint[1] = -999; 
     1533    arrint[3] = 0; 
     1534    stream.clear; formattedWrite(stream, "%s", arrint); 
     1535    assert(stream.data == "[100, -999, 0, 0]"); 
     1536    stream.clear; formattedWrite(stream, "%s",arrint); 
     1537    assert(stream.data == "[100, -999, 0, 0]"); 
     1538 
     1539    long[] arrlong = new long[4]; 
     1540    arrlong[0] = 100; 
     1541    arrlong[1] = -999; 
     1542    arrlong[3] = 0; 
     1543    stream.clear; formattedWrite(stream, "%s", arrlong); 
     1544    assert(stream.data == "[100, -999, 0, 0]"); 
     1545    stream.clear; formattedWrite(stream, "%s",arrlong); 
     1546    assert(stream.data == "[100, -999, 0, 0]"); 
     1547 
     1548    ulong[] arrulong = new ulong[4]; 
     1549    arrulong[0] = 100; 
     1550    arrulong[1] = 999; 
     1551    arrulong[3] = 0; 
     1552    stream.clear; formattedWrite(stream, "%s", arrulong); 
     1553    assert(stream.data == "[100, 999, 0, 0]"); 
     1554 
     1555    string[] arr2 = new string[4]; 
     1556    arr2[0] = "hello"; 
     1557    arr2[1] = "world"; 
     1558    arr2[3] = "foo"; 
     1559    stream.clear; formattedWrite(stream, "%s", arr2); 
     1560    assert(stream.data == "[hello, world, , foo]", stream.data); 
     1561 
     1562    stream.clear; formattedWrite(stream, "%.8d", 7); 
     1563    assert(stream.data == "00000007"); 
     1564 
     1565    stream.clear; formattedWrite(stream, "%.8x", 10); 
     1566    assert(stream.data == "0000000a"); 
     1567 
     1568    stream.clear; formattedWrite(stream, "%-3d", 7); 
     1569    assert(stream.data == "7  "); 
     1570 
     1571    stream.clear; formattedWrite(stream, "%*d", -3, 7); 
     1572    assert(stream.data == "7  "); 
     1573 
     1574    stream.clear; formattedWrite(stream, "%.*d", -3, 7); 
     1575    //writeln(stream.data); 
     1576    assert(stream.data == "7"); 
    25221577 
    25231578//  assert(false); 
    25241579//   typedef int myint; 
    25251580//   myint m = -7; 
    25261581//   stream.clear; formattedWrite(stream, "", m); 
    25271582//   assert(stream.data == "-7"); 
    25281583 
    25291584    stream.clear; formattedWrite(stream, "%s", "abc"c); 
    25301585    assert(stream.data == "abc"); 
    25311586    stream.clear; formattedWrite(stream, "%s", "def"w); 
    25321587    assert(stream.data == "def", text(stream.data.length)); 
    25331588    stream.clear; formattedWrite(stream, "%s", "ghi"d); 
    25341589    assert(stream.data == "ghi"); 
    25351590 
    25361591 here: 
    25371592    void* p = cast(void*)0xDEADBEEF; 
    25381593    stream.clear; formattedWrite(stream, "%s", p); 
    2539     assert(stream.data == "DEADBEEF"); 
     1594    assert(stream.data == "DEADBEEF", stream.data); 
    25401595 
    25411596    stream.clear; formattedWrite(stream, "%#x", 0xabcd); 
    25421597    assert(stream.data == "0xabcd"); 
    25431598    stream.clear; formattedWrite(stream, "%#X", 0xABCD); 
    25441599    assert(stream.data == "0XABCD"); 
    25451600 
    25461601    stream.clear; formattedWrite(stream, "%#o", 012345); 
    25471602    assert(stream.data == "012345"); 
    25481603    stream.clear; formattedWrite(stream, "%o", 9); 
    25491604    assert(stream.data == "11"); 
     
    27591814    //     writefln("%s", aa.values); 
    27601815    //     //writefln("%s", aa); 
    27611816    //     wstring a = "abcd"; 
    27621817    //     writefln(a); 
    27631818    //     dstring b = "abcd"; 
    27641819    //     writefln(b); 
    27651820    // } 
    27661821 
    27671822    auto stream = appender!string(); 
    27681823    alias TypeTuple!(byte, ubyte, short, ushort, int, uint, long, ulong, 
    2769             float, double, real, 
    2770             ifloat, idouble, ireal, cfloat, cdouble, creal) AllNumerics; 
     1824            float, double, real) AllNumerics; 
    27711825    foreach (T; AllNumerics) 
    27721826    { 
    2773         static if (is(T : ireal)) 
    2774             T value = 1i; 
    2775         else static if (is(T : creal)) 
    2776             T value = 1 + 1i; 
    2777         else 
    2778             T value = 1; 
    2779         stream.clear; formattedWrite(stream, "%s", value); 
    2780         static if (is(T : creal)) 
    2781             assert(stream.data == "1+1i"); 
    2782         else 
    2783             assert(stream.data == "1"); 
    2784         // test typedefs too 
    2785         typedef T Wyda; 
    2786         Wyda another = 1; 
    2787         stream.clear; formattedWrite(stream, "%s", another); 
     1827        T value = 1; 
     1828        stream.clear(); 
     1829        formattedWrite(stream, "%s", value); 
    27881830        assert(stream.data == "1"); 
    27891831    } 
    27901832 
    27911833    //auto r = std.string.format("%s", aa.values); 
    27921834    stream.clear; formattedWrite(stream, "%s", aa); 
    2793     assert(stream.data == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]", stream.data); 
     1835    //assert(stream.data == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]", stream.data); 
    27941836//    r = std.string.format("%s", aa); 
    27951837//   assert(r == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]"); 
    27961838} 
    27971839 
     1840unittest 
     1841{ 
     1842    string s = "hello!124:34.5"; 
     1843    string a; 
     1844    int b; 
     1845    double c; 
     1846    formattedRead(s, "%s!%s:%s", &a, &b, &c); 
     1847    assert(a == "hello" && b == 124 && c == 34.5); 
     1848} 
     1849 
    27981850//------------------------------------------------------------------------------ 
    2799 void formattedRead(R, S...)(ref R r, const(char)[] fmt, S args) 
    2800 
    2801     static if (!S.length) 
    2802     { 
    2803         parseToFormatSpec(r, fmt); 
    2804         enforce(fmt.length == 0); 
    2805     } 
    2806     else 
    2807     { 
    2808         FormatInfo spec; 
    2809         // The loop below accounts for '*' == fields meant to be read 
    2810         // and skipped 
    2811         for (;;) 
    2812         { 
    2813             parseToFormatSpec(r, fmt); 
    2814             spec = FormatInfo(fmt); 
    2815             if (spec.width != spec.DYNAMIC) break; 
    2816             // must skip this field 
    2817             skipData(r, spec); 
    2818         } 
    2819         alias typeof(*args[0]) A; 
    2820         //@@@BUG 2725 
    2821         //static if (is(A X == Tuple!(T), T)) 
    2822         static if (is(A.Types[0])) 
    2823         { 
    2824             //@@@BUG 
    2825             //args[0].field[0] = parse!(A.Types[0])(r); 
    2826             // static if (A.Types.length > 1) 
    2827             //     return formattedRead(r, fmt, 
    2828             //             args[0].slice!(1, A.Types.length)(), 
    2829             //             args[1 .. $]); 
    2830             // else 
    2831             //     return formattedRead(r, fmt, args[1 .. $]); 
    2832             // assume it's a tuple 
    2833             foreach (i, T; A.Types) 
    2834             { 
    2835                 //writeln("Parsing ", r, " with format ", fmt); 
    2836                 args[0].field[i] = unformat!(T)(r, spec); 
    2837                 for (;;) 
    2838                 { 
    2839                     parseToFormatSpec(r, fmt); 
    2840                     spec = FormatInfo(fmt); 
    2841                     if (spec.width != spec.DYNAMIC) break; 
    2842                     // must skip this guy 
    2843                     skipData(r, spec); 
    2844                 } 
    2845             } 
    2846             return formattedRead(r, fmt, args[1 .. $]); 
    2847         } 
    2848         else 
    2849         { 
    2850             *args[0] = unformat!(A)(r, spec); 
    2851             return formattedRead(r, fmt, args[1 .. $]); 
    2852         } 
    2853     } 
    2854 
    2855  
    2856 //------------------------------------------------------------------------------ 
    2857 void skipData(Range)(ref Range input, FormatInfo spec) 
     1851private void skipData(Range, Char)(ref Range input, ref FormatSpec!Char spec) 
    28581852{ 
    28591853    switch (spec.spec) 
    28601854    { 
    2861     case 'c': input.popFront; break; 
    2862     case 'd': assert(false, "Not implemented"); 
    2863     case 'u': while (!input.empty && isdigit(input.front)) input.popFront; 
    2864         break; 
    2865     default: 
    2866         assert(false, text("Not understood: ", spec.spec)); 
    2867     } 
    2868 
    2869  
    2870 //------------------------------------------------------------------------------ 
    2871 T unformat(T, Range)(ref Range input, FormatInfo spec) 
     1855        case 'c': input.popFront; break; 
     1856        case 'd': 
     1857            if (input.front == '+' || input.front == '-') input.popFront(); 
     1858            goto case 'u'; 
     1859        case 'u': 
     1860            while (!input.empty && isdigit(input.front)) input.popFront; 
     1861            break; 
     1862        default: 
     1863            assert(false, 
     1864                    text("Format specifier not understood: %", spec.spec)); 
     1865    } 
     1866
     1867 
     1868/** 
     1869   Reads an array (except for string types) and returns it. 
     1870 */ 
     1871T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) 
    28721872    if (isArray!T && !isSomeString!T) 
    28731873{ 
    28741874    auto app = appender!T(); 
    28751875    for (;;) 
    28761876    { 
    28771877        auto e = parse!(ElementType!(T))(input); 
    28781878        app.put(e); 
    2879         if (!std.string.startsWith(input, spec.innerTrailing)) break; // done 
    2880         input = input[spec.innerTrailing.length .. $]; 
     1879        if (!std.string.startsWith(input, spec.nested)) break; // done 
     1880        input = input[spec.nested.length .. $]; 
    28811881        if (input.empty) break; // the trailing is terminator, not 
    28821882                                // separator 
    28831883    } 
    28841884    return app.data; 
    28851885} 
    28861886 
    2887 //------------------------------------------------------------------------------ 
    2888 T unformat(T, Range)(ref Range input, FormatInfo spec) 
     1887/** 
     1888   Reads a string and returns it. 
     1889 */ 
     1890T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) 
    28891891if (isInputRange!Range && isSomeString!T) 
    28901892{ 
    28911893    auto app = appender!T(); 
    28921894    if (spec.trailing.empty) 
    28931895    { 
    28941896        for (; !input.empty; input.popFront()) 
    28951897        { 
    28961898            app.put(input.front); 
    28971899        } 
    28981900    } 
     
    29211923    assert(s2 == "yah", s2); 
    29221924} 
    29231925 
    29241926private template acceptedSpecs(T) 
    29251927{ 
    29261928    static if (isIntegral!T) enum acceptedSpecs = "sdu";// + "coxX" (todo) 
    29271929    else static if (isFloatingPoint!T) enum acceptedSpecs = "seEfgG"; 
    29281930    else enum acceptedSpecs = ""; 
    29291931} 
    29301932 
    2931 //------------------------------------------------------------------------------ 
    2932 T unformat(T, Range)(ref Range input, FormatInfo spec) 
     1933/** 
     1934   Reads an integral value and returns it. 
     1935 */ 
     1936T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) 
    29331937if (isIntegral!T && isInputRange!Range) 
    29341938{ 
    29351939    enforce(std.algorithm.find("cdosuxX", spec.spec).length, 
    29361940            text("Wrong integral type specifier: `", spec.spec, "'")); 
    29371941    if (std.algorithm.find("dsu", spec.spec).length) 
    29381942    { 
    29391943        return parse!T(input); 
    29401944    } 
    29411945    assert(0, "Parsing spec '"~spec.spec~"' not implemented."); 
    29421946} 
    29431947 
    2944 //------------------------------------------------------------------------------ 
    2945 T unformat(T, Range)(ref Range input, FormatInfo spec) 
     1948/** 
     1949   Reads a floating-point value and returns it. 
     1950 */ 
     1951T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec) 
    29461952if (isFloatingPoint!T) 
    29471953{ 
    29481954    if (spec.spec == 'r') 
    29491955    { 
    29501956        // raw read 
    29511957        //enforce(input.length >= T.sizeof); 
    29521958        enforce(isSomeString!Range || ElementType!(Range).sizeof == 1); 
    29531959        union X 
    29541960        { 
    29551961            ubyte[T.sizeof] raw; 
     
    29661972            else 
    29671973            { 
    29681974                // TODO: recheck this 
    29691975                x.raw[i] = cast(ubyte) input.front; 
    29701976                input.popFront(); 
    29711977            } 
    29721978        } 
    29731979        return x.typed; 
    29741980    } 
    29751981    enforce(std.algorithm.find(acceptedSpecs!T, spec.spec).length, 
    2976             text("Format specifier `", spec.spec, 
     1982            text("Format specifier `%", spec.spec, 
    29771983                    "' not accepted for floating point types")); 
    29781984    return parse!T(input); 
    29791985} 
    29801986 
    2981 unittest 
     1987version(none)unittest 
    29821988{ 
    29831989    union A 
    29841990    { 
    29851991        char[float.sizeof] untyped; 
    29861992        float typed; 
    29871993    }; 
    29881994    A a; 
    29891995    a.typed = 5.5; 
    29901996    char[] input = a.untyped[]; 
    29911997    float witness; 
    29921998    formattedRead(input, "%r", &witness); 
    29931999    assert(witness == a.typed); 
    29942000} 
    29952001 
    2996 //------------------------------------------------------------------------------ 
    2997 private void parseToFormatSpec(R)(ref R r, ref const(char)[] fmt) 
    2998 
    2999     while (fmt.length) 
    3000     { 
    3001         if (fmt[0] == '%') 
    3002         { 
    3003             if (fmt.length > 1 && fmt[1] == '%') 
     2002unittest 
     2003
     2004    char[] line = "1 2".dup; 
     2005    int a, b; 
     2006    formattedRead(line, "%s %s", &a, &b); 
     2007    assert(a == 1 && b == 2); 
     2008 
     2009    line = "10 2 3".dup; 
     2010    formattedRead(line, "%d ", &a); 
     2011    assert(a == 10); 
     2012    assert(line == "2 3"); 
     2013 
     2014    Tuple!(int, float) t; 
     2015    line = "1 2.125".dup; 
     2016    formattedRead(line, "%d %g", &t); 
     2017    assert(t.field[0] == 1 && t.field[1] == 2.125); 
     2018 
     2019    line = "1 7643 2.125".dup; 
     2020    formattedRead(line, "%s %*u %s", &t); 
     2021    assert(t.field[0] == 1 && t.field[1] == 2.125); 
     2022
     2023 
     2024// Legacy implementation 
     2025 
     2026enum Mangle : char 
     2027
     2028    Tvoid     = 'v', 
     2029    Tbool     = 'b', 
     2030    Tbyte     = 'g', 
     2031    Tubyte    = 'h', 
     2032    Tshort    = 's', 
     2033    Tushort   = 't', 
     2034    Tint      = 'i', 
     2035    Tuint     = 'k', 
     2036    Tlong     = 'l', 
     2037    Tulong    = 'm', 
     2038    Tfloat    = 'f', 
     2039    Tdouble   = 'd', 
     2040    Treal     = 'e', 
     2041 
     2042    Tifloat   = 'o', 
     2043    Tidouble  = 'p', 
     2044    Tireal    = 'j', 
     2045    Tcfloat   = 'q', 
     2046    Tcdouble  = 'r', 
     2047    Tcreal    = 'c', 
     2048 
     2049    Tchar     = 'a', 
     2050    Twchar    = 'u', 
     2051    Tdchar    = 'w', 
     2052 
     2053    Tarray    = 'A', 
     2054    Tsarray   = 'G', 
     2055    Taarray   = 'H', 
     2056    Tpointer  = 'P', 
     2057    Tfunction = 'F', 
     2058    Tident    = 'I', 
     2059    Tclass    = 'C', 
     2060    Tstruct   = 'S', 
     2061    Tenum     = 'E', 
     2062    Ttypedef  = 'T', 
     2063    Tdelegate = 'D', 
     2064 
     2065    Tconst    = 'x', 
     2066    Timmutable = 'y', 
     2067
     2068 
     2069// return the TypeInfo for a primitive type and null otherwise.  This 
     2070// is required since for arrays of ints we only have the mangled char 
     2071// to work from. If arrays always subclassed TypeInfo_Array this 
     2072// routine could go away. 
     2073private TypeInfo primitiveTypeInfo(Mangle m) 
     2074
     2075    // BUG: should fix this in static this() to avoid double checked locking bug 
     2076    __gshared TypeInfo[Mangle] dic; 
     2077    if (!dic.length) { 
     2078        dic = [ 
     2079            Mangle.Tvoid : typeid(void), 
     2080            Mangle.Tbool : typeid(bool), 
     2081            Mangle.Tbyte : typeid(byte), 
     2082            Mangle.Tubyte : typeid(ubyte), 
     2083            Mangle.Tshort : typeid(short), 
     2084            Mangle.Tushort : typeid(ushort), 
     2085            Mangle.Tint : typeid(int), 
     2086            Mangle.Tuint : typeid(uint), 
     2087            Mangle.Tlong : typeid(long), 
     2088            Mangle.Tulong : typeid(ulong), 
     2089            Mangle.Tfloat : typeid(float), 
     2090            Mangle.Tdouble : typeid(double), 
     2091            Mangle.Treal : typeid(real), 
     2092            Mangle.Tifloat : typeid(ifloat), 
     2093            Mangle.Tidouble : typeid(idouble), 
     2094            Mangle.Tireal : typeid(ireal), 
     2095            Mangle.Tcfloat : typeid(cfloat), 
     2096            Mangle.Tcdouble : typeid(cdouble), 
     2097            Mangle.Tcreal : typeid(creal), 
     2098            Mangle.Tchar : typeid(char), 
     2099            Mangle.Twchar : typeid(wchar), 
     2100            Mangle.Tdchar : typeid(dchar) 
     2101            ]; 
     2102    } 
     2103    auto p = m in dic; 
     2104    return p ? *p : null; 
     2105
     2106 
     2107// This stuff has been removed from the docs and is planned for deprecation. 
     2108/* 
     2109 * Interprets variadic argument list pointed to by argptr whose types 
     2110 * are given by arguments[], formats them according to embedded format 
     2111 * strings in the variadic argument list, and sends the resulting 
     2112 * characters to putc. 
     2113 * 
     2114 * The variadic arguments are consumed in order.  Each is formatted 
     2115 * into a sequence of chars, using the default format specification 
     2116 * for its type, and the characters are sequentially passed to putc. 
     2117 * If a $(D char[]), $(D wchar[]), or $(D dchar[]) argument is 
     2118 * encountered, it is interpreted as a format string. As many 
     2119 * arguments as specified in the format string are consumed and 
     2120 * formatted according to the format specifications in that string and 
     2121 * passed to putc. If there are too few remaining arguments, a 
     2122 * FormatError is thrown. If there are more remaining arguments than 
     2123 * needed by the format specification, the default processing of 
     2124 * arguments resumes until they are all consumed. 
     2125 * 
     2126 * Params: 
     2127 *        putc =        Output is sent do this delegate, character by character. 
     2128 *        arguments = Array of $(D TypeInfo)s, one for each argument to be formatted. 
     2129 *        argptr = Points to variadic argument list. 
     2130 * 
     2131 * Throws: 
     2132 *        Mismatched arguments and formats result in a $(D FormatError) being thrown. 
     2133 * 
     2134 * Format_String: 
     2135 *        <a name="format-string">$(I Format strings)</a> 
     2136 *        consist of characters interspersed with 
     2137 *        $(I format specifications). Characters are simply copied 
     2138 *        to the output (such as putc) after any necessary conversion 
     2139 *        to the corresponding UTF-8 sequence. 
     2140 * 
     2141 *        A $(I format specification) starts with a '%' character, 
     2142 *        and has the following grammar: 
     2143 
     2144<pre> 
     2145$(I FormatSpecification): 
     2146    $(B '%%') 
     2147    $(B '%') $(I Flags) $(I Width) $(I Precision) $(I FormatChar) 
     2148 
     2149$(I Flags): 
     2150    $(I empty) 
     2151    $(B '-') $(I Flags) 
     2152    $(B '+') $(I Flags) 
     2153    $(B '#') $(I Flags) 
     2154    $(B '0') $(I Flags) 
     2155    $(B ' ') $(I Flags) 
     2156 
     2157$(I Width): 
     2158    $(I empty) 
     2159    $(I Integer) 
     2160    $(B '*') 
     2161 
     2162$(I Precision): 
     2163    $(I empty) 
     2164    $(B '.') 
     2165    $(B '.') $(I Integer) 
     2166    $(B '.*') 
     2167 
     2168$(I Integer): 
     2169    $(I Digit) 
     2170    $(I Digit) $(I Integer) 
     2171 
     2172$(I Digit): 
     2173    $(B '0') 
     2174    $(B '1') 
     2175    $(B '2') 
     2176    $(B '3') 
     2177    $(B '4') 
     2178    $(B '5') 
     2179    $(B '6') 
     2180    $(B '7') 
     2181    $(B '8') 
     2182    $(B '9') 
     2183 
     2184$(I FormatChar): 
     2185    $(B 's') 
     2186    $(B 'b') 
     2187    $(B 'd') 
     2188    $(B 'o') 
     2189    $(B 'x') 
     2190    $(B 'X') 
     2191    $(B 'e') 
     2192    $(B 'E') 
     2193    $(B 'f') 
     2194    $(B 'F') 
     2195    $(B 'g') 
     2196    $(B 'G') 
     2197    $(B 'a') 
     2198    $(B 'A') 
     2199</pre> 
     2200    <dl> 
     2201    <dt>$(I Flags) 
     2202    <dl> 
     2203        <dt>$(B '-') 
     2204        <dd> 
     2205        Left justify the result in the field. 
     2206        It overrides any $(B 0) flag. 
     2207 
     2208        <dt>$(B '+') 
     2209        <dd>Prefix positive numbers in a signed conversion with a $(B +). 
     2210        It overrides any $(I space) flag. 
     2211 
     2212        <dt>$(B '#') 
     2213        <dd>Use alternative formatting: 
     2214        <dl> 
     2215            <dt>For $(B 'o'): 
     2216            <dd> Add to precision as necessary so that the first digit 
     2217            of the octal formatting is a '0', even if both the argument 
     2218            and the $(I Precision) are zero. 
     2219            <dt> For $(B 'x') ($(B 'X')): 
     2220            <dd> If non-zero, prefix result with $(B 0x) ($(B 0X)). 
     2221            <dt> For floating point formatting: 
     2222            <dd> Always insert the decimal point. 
     2223            <dt> For $(B 'g') ($(B 'G')): 
     2224            <dd> Do not elide trailing zeros. 
     2225        </dl> 
     2226 
     2227        <dt>$(B '0') 
     2228        <dd> For integer and floating point formatting when not nan or 
     2229        infinity, use leading zeros 
     2230        to pad rather than spaces. 
     2231        Ignore if there's a $(I Precision). 
     2232 
     2233        <dt>$(B ' ') 
     2234        <dd>Prefix positive numbers in a signed conversion with a space. 
     2235    </dl> 
     2236 
     2237    <dt>$(I Width) 
     2238    <dd> 
     2239    Specifies the minimum field width. 
     2240    If the width is a $(B *), the next argument, which must be 
     2241    of type $(B int), is taken as the width. 
     2242    If the width is negative, it is as if the $(B -) was given 
     2243    as a $(I Flags) character. 
     2244 
     2245    <dt>$(I Precision) 
     2246    <dd> Gives the precision for numeric conversions. 
     2247    If the precision is a $(B *), the next argument, which must be 
     2248    of type $(B int), is taken as the precision. If it is negative, 
     2249    it is as if there was no $(I Precision). 
     2250 
     2251    <dt>$(I FormatChar) 
     2252    <dd> 
     2253    <dl> 
     2254        <dt>$(B 's') 
     2255        <dd>The corresponding argument is formatted in a manner consistent 
     2256        with its type: 
     2257        <dl> 
     2258            <dt>$(B bool) 
     2259            <dd>The result is <tt>'true'</tt> or <tt>'false'</tt>. 
     2260            <dt>integral types 
     2261            <dd>The $(B %d) format is used. 
     2262            <dt>floating point types 
     2263            <dd>The $(B %g) format is used. 
     2264            <dt>string types 
     2265            <dd>The result is the string converted to UTF-8. 
     2266            A $(I Precision) specifies the maximum number of characters 
     2267            to use in the result. 
     2268            <dt>classes derived from $(B Object) 
     2269            <dd>The result is the string returned from the class instance's 
     2270            $(B .toString()) method. 
     2271            A $(I Precision) specifies the maximum number of characters 
     2272            to use in the result. 
     2273            <dt>non-string static and dynamic arrays 
     2274            <dd>The result is [s<sub>0</sub>, s<sub>1</sub>, ...] 
     2275            where s<sub>k</sub> is the kth element 
     2276            formatted with the default format. 
     2277        </dl> 
     2278 
     2279        <dt>$(B 'b','d','o','x','X') 
     2280        <dd> The corresponding argument must be an integral type 
     2281        and is formatted as an integer. If the argument is a signed type 
     2282        and the $(I FormatChar) is $(B d) it is converted to 
     2283        a signed string of characters, otherwise it is treated as 
     2284        unsigned. An argument of type $(B bool) is formatted as '1' 
     2285        or '0'. The base used is binary for $(B b), octal for $(B o), 
     2286        decimal 
     2287        for $(B d), and hexadecimal for $(B x) or $(B X). 
     2288        $(B x) formats using lower case letters, $(B X) uppercase. 
     2289        If there are fewer resulting digits than the $(I Precision), 
     2290        leading zeros are used as necessary. 
     2291        If the $(I Precision) is 0 and the number is 0, no digits 
     2292        result. 
     2293 
     2294        <dt>$(B 'e','E') 
     2295        <dd> A floating point number is formatted as one digit before 
     2296        the decimal point, $(I Precision) digits after, the $(I FormatChar), 
     2297        &plusmn;, followed by at least a two digit exponent: $(I d.dddddd)e$(I &plusmn;dd). 
     2298        If there is no $(I Precision), six 
     2299        digits are generated after the decimal point. 
     2300        If the $(I Precision) is 0, no decimal point is generated. 
     2301 
     2302        <dt>$(B 'f','F') 
     2303        <dd> A floating point number is formatted in decimal notation. 
     2304        The $(I Precision) specifies the number of digits generated 
     2305        after the decimal point. It defaults to six. At least one digit 
     2306        is generated before the decimal point. If the $(I Precision) 
     2307        is zero, no decimal point is generated. 
     2308 
     2309        <dt>$(B 'g','G') 
     2310        <dd> A floating point number is formatted in either $(B e) or 
     2311        $(B f) format for $(B g); $(B E) or $(B F) format for 
     2312        $(B G). 
     2313        The $(B f) format is used if the exponent for an $(B e) format 
     2314        is greater than -5 and less than the $(I Precision). 
     2315        The $(I Precision) specifies the number of significant 
     2316        digits, and defaults to six. 
     2317        Trailing zeros are elided after the decimal point, if the fractional 
     2318        part is zero then no decimal point is generated. 
     2319 
     2320        <dt>$(B 'a','A') 
     2321        <dd> A floating point number is formatted in hexadecimal 
     2322        exponential notation 0x$(I h.hhhhhh)p$(I &plusmn;d). 
     2323        There is one hexadecimal digit before the decimal point, and as 
     2324        many after as specified by the $(I Precision). 
     2325        If the $(I Precision) is zero, no decimal point is generated. 
     2326        If there is no $(I Precision), as many hexadecimal digits as 
     2327        necessary to exactly represent the mantissa are generated. 
     2328        The exponent is written in as few digits as possible, 
     2329        but at least one, is in decimal, and represents a power of 2 as in 
     2330        $(I h.hhhhhh)*2<sup>$(I &plusmn;d)</sup>. 
     2331        The exponent for zero is zero. 
     2332        The hexadecimal digits, x and p are in upper case if the 
     2333        $(I FormatChar) is upper case. 
     2334    </dl> 
     2335 
     2336    Floating point NaN's are formatted as $(B nan) if the 
     2337    $(I FormatChar) is lower case, or $(B NAN) if upper. 
     2338    Floating point infinities are formatted as $(B inf) or 
     2339    $(B infinity) if the 
     2340    $(I FormatChar) is lower case, or $(B INF) or $(B INFINITY) if upper. 
     2341    </dl> 
     2342 
     2343Example: 
     2344 
     2345------------------------- 
     2346import std.c.stdio; 
     2347import std.format; 
     2348 
     2349void myPrint(...) 
     2350
     2351    void putc(char c) 
     2352    { 
     2353        fputc(c, stdout); 
     2354    } 
     2355 
     2356    std.format.doFormat(&putc, _arguments, _argptr); 
     2357
     2358 
     2359... 
     2360 
     2361int x = 27; 
     2362// prints 'The answer is 27:6' 
     2363myPrint("The answer is %s:", x, 6); 
     2364------------------------ 
     2365 */ 
     2366void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr) 
     2367
     2368    TypeInfo ti; 
     2369    Mangle m; 
     2370    uint flags; 
     2371    int field_width; 
     2372    int precision; 
     2373 
     2374    enum : uint 
     2375    { 
     2376        FLdash = 1, 
     2377        FLplus = 2, 
     2378        FLspace = 4, 
     2379        FLhash = 8, 
     2380        FLlngdbl = 0x20, 
     2381        FL0pad = 0x40, 
     2382        FLprecision = 0x80, 
     2383    } 
     2384 
     2385    static TypeInfo skipCI(TypeInfo valti) 
     2386    { 
     2387        for (;;) 
     2388        { 
     2389            if (valti.classinfo.name.length == 18 && 
     2390                    valti.classinfo.name[9..18] == "Invariant") 
     2391                valti =        (cast(TypeInfo_Invariant)valti).next; 
     2392            else if (valti.classinfo.name.length == 14 && 
     2393                    valti.classinfo.name[9..14] == "Const") 
     2394                valti =        (cast(TypeInfo_Const)valti).next; 
     2395            else 
     2396                break; 
     2397        } 
     2398        return valti; 
     2399    } 
     2400 
     2401    void formatArg(char fc) 
     2402    { 
     2403        bool vbit; 
     2404        ulong vnumber; 
     2405        char vchar; 
     2406        dchar vdchar; 
     2407        Object vobject; 
     2408        real vreal; 
     2409        creal vcreal; 
     2410        Mangle m2; 
     2411        int signed = 0; 
     2412        uint base = 10; 
     2413        int uc; 
     2414        char[ulong.sizeof * 8] tmpbuf; // long enough to print long in binary 
     2415        const(char)* prefix = ""; 
     2416        string s; 
     2417 
     2418        void putstr(const char[] s) 
     2419        { 
     2420            //printf("flags = x%x\n", flags); 
     2421            int prepad = 0; 
     2422            int postpad = 0; 
     2423            int padding = field_width - 
     2424                (strlen(prefix) + toUCSindex(s, s.length)); 
     2425            if (padding > 0) 
    30042426            { 
    3005                 assert(!r.empty); 
    3006                 // Require a '%' 
    3007                 if (r.front != '%') break; 
    3008                 fmt = fmt[1 .. $]; 
    3009                 r.popFront(); 
     2427                if (flags & FLdash) 
     2428                    postpad = padding; 
     2429                else 
     2430                    prepad = padding; 
     2431            } 
     2432 
     2433            if (flags & FL0pad) 
     2434            { 
     2435                while (*prefix) 
     2436                    putc(*prefix++); 
     2437                while (prepad--) 
     2438                    putc('0'); 
    30102439            } 
    30112440            else 
    30122441            { 
    3013                 fmt.popFront; 
    3014                 break; 
     2442                while (prepad--) 
     2443                    putc(' '); 
     2444                while (*prefix) 
     2445                    putc(*prefix++); 
    30152446            } 
    3016         } 
    3017         else 
    3018         { 
    3019             if (fmt.front == ' ') 
     2447 
     2448            foreach (dchar c; s) 
     2449                putc(c); 
     2450 
     2451            while (postpad--) 
     2452                putc(' '); 
     2453        } 
     2454 
     2455        void putreal(real v) 
     2456        { 
     2457            //printf("putreal %Lg\n", vreal); 
     2458 
     2459            switch (fc) 
    30202460            { 
    3021                 while (!r.empty && isspace(r.front)) r.popFront(); 
    3022                 //r = std.algorithm.find!(not!isspace)(r); 
     2461                case 's': 
     2462                    fc = 'g'; 
     2463                    break; 
     2464 
     2465                case 'f', 'F', 'e', 'E', 'g', 'G', 'a', 'A': 
     2466                    break; 
     2467 
     2468                default: 
     2469                    //printf("fc = '%c'\n", fc); 
     2470                Lerror: 
     2471                    throw new FormatError("floating"); 
     2472            } 
     2473            version (DigitalMarsC) 
     2474            { 
     2475                uint sl; 
     2476                char[] fbuf = tmpbuf; 
     2477                if (!(flags & FLprecision)) 
     2478                    precision = 6; 
     2479                while (1) 
     2480                { 
     2481                    sl = fbuf.length; 
     2482                    prefix = (*__pfloatfmt)(fc, flags | FLlngdbl, 
     2483                            precision, &v, cast(char*)fbuf, &sl, field_width); 
     2484                    if (sl != -1) 
     2485                        break; 
     2486                    sl = fbuf.length * 2; 
     2487                    fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl]; 
     2488                } 
     2489                putstr(fbuf[0 .. sl]); 
    30232490            } 
    30242491            else 
    30252492            { 
    3026                 enforce(!r.empty, 
    3027                         text("parseToFormatSpec: Cannot find character `", 
    3028                                 fmt.front, "' in the input string.")); 
    3029                 //static assert(0); 
    3030                 if (r.front != fmt.front) break; 
    3031                 r.popFront; 
     2493                int sl; 
     2494                char[] fbuf = tmpbuf; 
     2495                char[12] format; 
     2496                format[0] = '%'; 
     2497                int i = 1; 
     2498                if (flags & FLdash) 
     2499                    format[i++] = '-'; 
     2500                if (flags & FLplus) 
     2501                    format[i++] = '+'; 
     2502                if (flags & FLspace) 
     2503                    format[i++] = ' '; 
     2504                if (flags & FLhash) 
     2505                    format[i++] = '#'; 
     2506                if (flags & FL0pad) 
     2507                    format[i++] = '0'; 
     2508                format[i + 0] = '*'; 
     2509                format[i + 1] = '.'; 
     2510                format[i + 2] = '*'; 
     2511                format[i + 3] = 'L'; 
     2512                format[i + 4] = fc; 
     2513                format[i + 5] = 0; 
     2514                if (!(flags & FLprecision)) 
     2515                    precision = -1; 
     2516                while (1) 
     2517                { 
     2518                    sl = fbuf.length; 
     2519                    auto n = snprintf(fbuf.ptr, sl, format.ptr, field_width, 
     2520                            precision, v); 
     2521                    //printf("format = '%s', n = %d\n", cast(char*)format, n); 
     2522                    if (n >= 0 && n < sl) 
     2523                    {        sl = n; 
     2524                        break; 
     2525                    } 
     2526                    if (n < 0) 
     2527                        sl = sl * 2; 
     2528                    else 
     2529                        sl = n + 1; 
     2530                    fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl]; 
     2531                } 
     2532                putstr(fbuf[0 .. sl]); 
    30322533            } 
    3033             fmt.popFront; 
    3034         } 
    3035     } 
    3036 
     2534            return; 
     2535        } 
     2536 
     2537        static Mangle getMan(TypeInfo ti) 
     2538        { 
     2539          auto m = cast(Mangle)ti.classinfo.name[9]; 
     2540          if (ti.classinfo.name.length == 20 && 
     2541              ti.classinfo.name[9..20] == "StaticArray") 
     2542                m = cast(Mangle)'G'; 
     2543          return m; 
     2544        } 
     2545 
     2546        void putArray(void* p, size_t len, TypeInfo valti) 
     2547        { 
     2548          //printf("\nputArray(len = %u), tsize = %u\n", len, valti.tsize()); 
     2549          putc('['); 
     2550          valti = skipCI(valti); 
     2551          size_t tsize = valti.tsize(); 
     2552          auto argptrSave = argptr; 
     2553          auto tiSave = ti; 
     2554          auto mSave = m; 
     2555          ti = valti; 
     2556          //printf("\n%.*s\n", valti.classinfo.name); 
     2557          m = getMan(valti); 
     2558          while (len--) 
     2559          { 
     2560            //doFormat(putc, (&valti)[0 .. 1], p); 
     2561            argptr = p; 
     2562            formatArg('s'); 
     2563 
     2564            p += tsize; 
     2565            if (len > 0) putc(','); 
     2566          } 
     2567          m = mSave; 
     2568          ti = tiSave; 
     2569          argptr = argptrSave; 
     2570          putc(']'); 
     2571        } 
     2572 
     2573        void putAArray(ubyte[long] vaa, TypeInfo valti, TypeInfo keyti) 
     2574        { 
     2575            putc('['); 
     2576            bool comma=false; 
     2577            auto argptrSave = argptr; 
     2578            auto tiSave = ti; 
     2579            auto mSave = m; 
     2580            valti = skipCI(valti); 
     2581            keyti = skipCI(keyti); 
     2582            foreach(ref fakevalue; vaa) 
     2583            { 
     2584                if (comma) putc(','); 
     2585                comma = true; 
     2586                // the key comes before the value 
     2587                ubyte* key = &fakevalue - long.sizeof; 
     2588 
     2589                //doFormat(putc, (&keyti)[0..1], key); 
     2590                argptr = key; 
     2591                ti = keyti; 
     2592                m = getMan(keyti); 
     2593                formatArg('s'); 
     2594 
     2595                putc(':'); 
     2596                auto keysize = keyti.tsize; 
     2597                keysize = (keysize + 3) & ~3; 
     2598                ubyte* value = key + keysize; 
     2599                //doFormat(putc, (&valti)[0..1], value); 
     2600                argptr = value; 
     2601                ti = valti; 
     2602                m = getMan(valti); 
     2603                formatArg('s'); 
     2604            } 
     2605            m = mSave; 
     2606            ti = tiSave; 
     2607            argptr = argptrSave; 
     2608            putc(']'); 
     2609        } 
     2610 
     2611        //printf("formatArg(fc = '%c', m = '%c')\n", fc, m); 
     2612        switch (m) 
     2613        { 
     2614            case Mangle.Tbool: 
     2615                vbit = va_arg!(bool)(argptr); 
     2616                if (fc != 's') 
     2617                {   vnumber = vbit; 
     2618                    goto Lnumber; 
     2619                } 
     2620                putstr(vbit ? "true" : "false"); 
     2621                return; 
     2622 
     2623 
     2624            case Mangle.Tchar: 
     2625                vchar = va_arg!(char)(argptr); 
     2626                if (fc != 's') 
     2627                {   vnumber = vchar; 
     2628                    goto Lnumber; 
     2629                } 
     2630            L2: 
     2631                putstr((&vchar)[0 .. 1]); 
     2632                return; 
     2633 
     2634            case Mangle.Twchar: 
     2635                vdchar = va_arg!(wchar)(argptr); 
     2636                goto L1; 
     2637 
     2638            case Mangle.Tdchar: 
     2639                vdchar = va_arg!(dchar)(argptr); 
     2640            L1: 
     2641                if (fc != 's') 
     2642                {   vnumber = vdchar; 
     2643                    goto Lnumber; 
     2644                } 
     2645                if (vdchar <= 0x7F) 
     2646                {   vchar = cast(char)vdchar; 
     2647                    goto L2; 
     2648                } 
     2649                else 
     2650                {   if (!isValidDchar(vdchar)) 
     2651                        throw new UtfException("invalid dchar in format", 0); 
     2652                    char[4] vbuf; 
     2653                    putstr(toUTF8(vbuf, vdchar)); 
     2654                } 
     2655                return; 
     2656 
     2657 
     2658            case Mangle.Tbyte: 
     2659                signed = 1; 
     2660                vnumber = va_arg!(byte)(argptr); 
     2661                goto Lnumber; 
     2662 
     2663            case Mangle.Tubyte: 
     2664                vnumber = va_arg!(ubyte)(argptr); 
     2665                goto Lnumber; 
     2666 
     2667            case Mangle.Tshort: 
     2668                signed = 1; 
     2669                vnumber = va_arg!(short)(argptr); 
     2670                goto Lnumber; 
     2671 
     2672            case Mangle.Tushort: 
     2673                vnumber = va_arg!(ushort)(argptr); 
     2674                goto Lnumber; 
     2675 
     2676            case Mangle.Tint: 
     2677                signed = 1; 
     2678                vnumber = va_arg!(int)(argptr); 
     2679                goto Lnumber; 
     2680 
     2681            case Mangle.Tuint: 
     2682            Luint: 
     2683                vnumber = va_arg!(uint)(argptr); 
     2684                goto Lnumber; 
     2685 
     2686            case Mangle.Tlong: 
     2687                signed = 1; 
     2688                vnumber = cast(ulong)va_arg!(long)(argptr); 
     2689                goto Lnumber; 
     2690 
     2691            case Mangle.Tulong: 
     2692            Lulong: 
     2693                vnumber = va_arg!(ulong)(argptr); 
     2694                goto Lnumber; 
     2695 
     2696            case Mangle.Tclass: 
     2697                vobject = va_arg!(Object)(argptr); 
     2698                if (vobject is null) 
     2699                    s = "null"; 
     2700                else 
     2701                    s = vobject.toString(); 
     2702                goto Lputstr; 
     2703 
     2704            case Mangle.Tpointer: 
     2705                vnumber = cast(ulong)va_arg!(void*)(argptr); 
     2706                if (fc != 'x')  uc = 1; 
     2707                flags |= FL0pad; 
     2708                if (!(flags & FLprecision)) 
     2709                {   flags |= FLprecision; 
     2710                    precision = (void*).sizeof; 
     2711                } 
     2712                base = 16; 
     2713                goto Lnumber; 
     2714 
     2715 
     2716            case Mangle.Tfloat: 
     2717            case Mangle.Tifloat: 
     2718                if (fc == 'x' || fc == 'X') 
     2719                    goto Luint; 
     2720                vreal = va_arg!(float)(argptr); 
     2721                goto Lreal; 
     2722 
     2723            case Mangle.Tdouble: 
     2724            case Mangle.Tidouble: 
     2725                if (fc == 'x' || fc == 'X') 
     2726                    goto Lulong; 
     2727                vreal = va_arg!(double)(argptr); 
     2728                goto Lreal; 
     2729 
     2730            case Mangle.Treal: 
     2731            case Mangle.Tireal: 
     2732                vreal = va_arg!(real)(argptr); 
     2733                goto Lreal; 
     2734 
     2735 
     2736            case Mangle.Tcfloat: 
     2737                vcreal = va_arg!(cfloat)(argptr); 
     2738                goto Lcomplex; 
     2739 
     2740            case Mangle.Tcdouble: 
     2741                vcreal = va_arg!(cdouble)(argptr); 
     2742                goto Lcomplex; 
     2743 
     2744            case Mangle.Tcreal: 
     2745                vcreal = va_arg!(creal)(argptr); 
     2746                goto Lcomplex; 
     2747 
     2748            case Mangle.Tsarray: 
     2749                putArray(argptr, (cast(TypeInfo_StaticArray)ti).len, 
     2750                        (cast(TypeInfo_StaticArray)ti).next); 
     2751                return; 
     2752 
     2753            case Mangle.Tarray: 
     2754                int mi = 10; 
     2755                if (ti.classinfo.name.length == 14 && 
     2756                    ti.classinfo.name[9..14] == "Array") 
     2757                { // array of non-primitive types 
     2758                  TypeInfo tn = (cast(TypeInfo_Array)ti).next; 
     2759                  tn = skipCI(tn); 
     2760                  switch (cast(Mangle)tn.classinfo.name[9]) 
     2761                  { 
     2762                    case Mangle.Tchar:  goto LarrayChar; 
     2763                    case Mangle.Twchar: goto LarrayWchar; 
     2764                    case Mangle.Tdchar: goto LarrayDchar; 
     2765                    default: 
     2766                        break; 
     2767                  } 
     2768                  void[] va = va_arg!(void[])(argptr); 
     2769                  putArray(va.ptr, va.length, tn); 
     2770                  return; 
     2771                } 
     2772                if (ti.classinfo.name.length == 25 && 
     2773                    ti.classinfo.name[9..25] == "AssociativeArray") 
     2774                { // associative array 
     2775                  ubyte[long] vaa = va_arg!(ubyte[long])(argptr); 
     2776                  putAArray(vaa, 
     2777                        (cast(TypeInfo_AssociativeArray)ti).next, 
     2778                        (cast(TypeInfo_AssociativeArray)ti).key); 
     2779                  return; 
     2780                } 
     2781 
     2782                while (1) 
     2783                { 
     2784                    m2 = cast(Mangle)ti.classinfo.name[mi]; 
     2785                    switch (m2) 
     2786                    { 
     2787                        case Mangle.Tchar: 
     2788                        LarrayChar: 
     2789                            s = va_arg!(string)(argptr); 
     2790                            goto Lputstr; 
     2791 
     2792                        case Mangle.Twchar: 
     2793                        LarrayWchar: 
     2794                            wchar[] sw = va_arg!(wchar[])(argptr); 
     2795                            s = toUTF8(sw); 
     2796                            goto Lputstr; 
     2797 
     2798                        case Mangle.Tdchar: 
     2799                        LarrayDchar: 
     2800                            auto sd = va_arg!(dstring)(argptr); 
     2801                            s = toUTF8(sd); 
     2802                        Lputstr: 
     2803                            if (fc != 's') 
     2804                                throw new FormatError("string"); 
     2805                            if (flags & FLprecision && precision < s.length) 
     2806                                s = s[0 .. precision]; 
     2807                            putstr(s); 
     2808                            break; 
     2809 
     2810                        case Mangle.Tconst: 
     2811                        case Mangle.Timmutable: 
     2812                            mi++; 
     2813                            continue; 
     2814 
     2815                        default: 
     2816                            TypeInfo ti2 = primitiveTypeInfo(m2); 
     2817                            if (!ti2) 
     2818                              goto Lerror; 
     2819                            void[] va = va_arg!(void[])(argptr); 
     2820                            putArray(va.ptr, va.length, ti2); 
     2821                    } 
     2822                    return; 
     2823                } 
     2824 
     2825            case Mangle.Ttypedef: 
     2826                ti = (cast(TypeInfo_Typedef)ti).base; 
     2827                m = cast(Mangle)ti.classinfo.name[9]; 
     2828                formatArg(fc); 
     2829                return; 
     2830 
     2831            case Mangle.Tenum: 
     2832                ti = (cast(TypeInfo_Enum)ti).base; 
     2833                m = cast(Mangle)ti.classinfo.name[9]; 
     2834                formatArg(fc); 
     2835                return; 
     2836 
     2837            case Mangle.Tstruct: 
     2838            {        TypeInfo_Struct tis = cast(TypeInfo_Struct)ti; 
     2839                if (tis.xtoString is null) 
     2840                    throw new FormatError("Can't convert " ~ tis.toString() 
     2841                            ~ " to string: \"string toString()\" not defined"); 
     2842                s = tis.xtoString(argptr); 
     2843                argptr += (tis.tsize() + 3) & ~3; 
     2844                goto Lputstr; 
     2845            } 
     2846 
     2847            default: 
     2848                goto Lerror; 
     2849        } 
     2850 
     2851    Lnumber: 
     2852        switch (fc) 
     2853        { 
     2854            case 's': 
     2855            case 'd': 
     2856                if (signed) 
     2857                {   if (cast(long)vnumber < 0) 
     2858                    {        prefix = "-"; 
     2859                        vnumber = -vnumber; 
     2860                    } 
     2861                    else if (flags & FLplus) 
     2862                        prefix = "+"; 
     2863                    else if (flags & FLspace) 
     2864                        prefix = " "; 
     2865                } 
     2866                break; 
     2867 
     2868            case 'b': 
     2869                signed = 0; 
     2870                base = 2; 
     2871                break; 
     2872 
     2873            case 'o': 
     2874                signed = 0; 
     2875                base = 8; 
     2876                break; 
     2877 
     2878            case 'X': 
     2879                uc = 1; 
     2880                if (flags & FLhash && vnumber) 
     2881                    prefix = "0X"; 
     2882                signed = 0; 
     2883                base = 16; 
     2884                break; 
     2885 
     2886            case 'x': 
     2887                if (flags & FLhash && vnumber) 
     2888                    prefix = "0x"; 
     2889                signed = 0; 
     2890                base = 16; 
     2891                break; 
     2892 
     2893            default: 
     2894                goto Lerror; 
     2895        } 
     2896 
     2897        if (!signed) 
     2898        { 
     2899            switch (m) 
     2900            { 
     2901                case Mangle.Tbyte: 
     2902                    vnumber &= 0xFF; 
     2903                    break; 
     2904 
     2905                case Mangle.Tshort: 
     2906                    vnumber &= 0xFFFF; 
     2907                    break; 
     2908 
     2909                case Mangle.Tint: 
     2910                    vnumber &= 0xFFFFFFFF; 
     2911                    break; 
     2912 
     2913                default: 
     2914                    break; 
     2915            } 
     2916        } 
     2917 
     2918        if (flags & FLprecision && fc != 'p') 
     2919            flags &= ~FL0pad; 
     2920 
     2921        if (vnumber < base) 
     2922        { 
     2923            if (vnumber == 0 && precision == 0 && flags & FLprecision && 
     2924                !(fc == 'o' && flags & FLhash)) 
     2925            { 
     2926                putstr(null); 
     2927                return; 
     2928            } 
     2929            if (precision == 0 || !(flags & FLprecision)) 
     2930            {        vchar = cast(char)('0' + vnumber); 
     2931                if (vnumber < 10) 
     2932                    vchar = cast(char)('0' + vnumber); 
     2933                else 
     2934                    vchar = cast(char)((uc ? 'A' - 10 : 'a' - 10) + vnumber); 
     2935                goto L2; 
     2936            } 
     2937        } 
     2938 
     2939        int n = tmpbuf.length; 
     2940        char c; 
     2941        int hexoffset = uc ? ('A' - ('9' + 1)) : ('a' - ('9' + 1)); 
     2942 
     2943        while (vnumber) 
     2944        { 
     2945            c = cast(char)((vnumber % base) + '0'); 
     2946            if (c > '9') 
     2947                c += hexoffset; 
     2948            vnumber /= base; 
     2949            tmpbuf[--n] = c; 
     2950        } 
     2951        if (tmpbuf.length - n < precision && precision < tmpbuf.length) 
     2952        { 
     2953            int m = tmpbuf.length - precision; 
     2954            tmpbuf[m .. n] = '0'; 
     2955            n = m; 
     2956        } 
     2957        else if (flags & FLhash && fc == 'o') 
     2958            prefix = "0"; 
     2959        putstr(tmpbuf[n .. tmpbuf.length]); 
     2960        return; 
     2961 
     2962    Lreal: 
     2963        putreal(vreal); 
     2964        return; 
     2965 
     2966    Lcomplex: 
     2967        putreal(vcreal.re); 
     2968        putc('+'); 
     2969        putreal(vcreal.im); 
     2970        putc('i'); 
     2971        return; 
     2972 
     2973    Lerror: 
     2974        throw new FormatError("formatArg"); 
     2975    } 
     2976 
     2977    for (int j = 0; j < arguments.length; ) 
     2978    { 
     2979        ti = arguments[j++]; 
     2980        //printf("test1: '%.*s' %d\n", ti.classinfo.name, 
     2981        //ti.classinfo.name.length); ti.print(); 
     2982 
     2983        flags = 0; 
     2984        precision = 0; 
     2985        field_width = 0; 
     2986 
     2987        ti = skipCI(ti); 
     2988        int mi = 9; 
     2989        do 
     2990        { 
     2991            if (ti.classinfo.name.length <= mi) 
     2992                goto Lerror; 
     2993            m = cast(Mangle)ti.classinfo.name[mi++]; 
     2994        } while (m == Mangle.Tconst || m == Mangle.Timmutable); 
     2995 
     2996        if (m == Mangle.Tarray) 
     2997        { 
     2998            if (ti.classinfo.name.length == 14 && 
     2999                    ti.classinfo.name[9..14] == "Array") 
     3000            { 
     3001                TypeInfo tn = (cast(TypeInfo_Array)ti).next; 
     3002                tn = skipCI(tn); 
     3003                switch (cast(Mangle)tn.classinfo.name[9]) 
     3004                { 
     3005                case Mangle.Tchar: 
     3006                case Mangle.Twchar: 
     3007                case Mangle.Tdchar: 
     3008                    ti = tn; 
     3009                    mi = 9; 
     3010                    break; 
     3011                default: 
     3012                    break; 
     3013                } 
     3014            } 
     3015          L1: 
     3016            Mangle m2 = cast(Mangle)ti.classinfo.name[mi]; 
     3017            string  fmt;                        // format string 
     3018            wstring wfmt; 
     3019            dstring dfmt; 
     3020 
     3021            /* For performance reasons, this code takes advantage of the 
     3022             * fact that most format strings will be ASCII, and that the 
     3023             * format specifiers are always ASCII. This means we only need 
     3024             * to deal with UTF in a couple of isolated spots. 
     3025             */ 
     3026 
     3027            switch (m2) 
     3028            { 
     3029            case Mangle.Tchar: 
     3030                fmt = va_arg!(string)(argptr); 
     3031                break; 
     3032 
     3033            case Mangle.Twchar: 
     3034                wfmt = va_arg!(wstring)(argptr); 
     3035                fmt = toUTF8(wfmt); 
     3036                break; 
     3037 
     3038            case Mangle.Tdchar: 
     3039                dfmt = va_arg!(dstring)(argptr); 
     3040                fmt = toUTF8(dfmt); 
     3041                break; 
     3042 
     3043            case Mangle.Tconst: 
     3044            case Mangle.Timmutable: 
     3045                mi++; 
     3046                goto L1; 
     3047 
     3048            default: 
     3049                formatArg('s'); 
     3050                continue; 
     3051            } 
     3052 
     3053            for (size_t i = 0; i < fmt.length; ) 
     3054            {        dchar c = fmt[i++]; 
     3055 
     3056                dchar getFmtChar() 
     3057                {   // Valid format specifier characters will never be UTF 
     3058                    if (i == fmt.length) 
     3059                        throw new FormatError("invalid specifier"); 
     3060                    return fmt[i++]; 
     3061                } 
     3062 
     3063                int getFmtInt() 
     3064                {   int n; 
     3065 
     3066                    while (1) 
     3067                    { 
     3068                        n = n * 10 + (c - '0'); 
     3069                        if (n < 0)        // overflow 
     3070                            throw new FormatError("int overflow"); 
     3071                        c = getFmtChar(); 
     3072                        if (c < '0' || c > '9') 
     3073                            break; 
     3074                    } 
     3075                    return n; 
     3076                } 
     3077 
     3078                int getFmtStar() 
     3079                {   Mangle m; 
     3080                    TypeInfo ti; 
     3081 
     3082                    if (j == arguments.length) 
     3083                        throw new FormatError("too few arguments"); 
     3084                    ti = arguments[j++]; 
     3085                    m = cast(Mangle)ti.classinfo.name[9]; 
     3086                    if (m != Mangle.Tint) 
     3087                        throw new FormatError("int argument expected"); 
     3088                    return va_arg!(int)(argptr); 
     3089                } 
     3090 
     3091                if (c != '%') 
     3092                { 
     3093                    if (c > 0x7F)        // if UTF sequence 
     3094                    { 
     3095                        i--;                // back up and decode UTF sequence 
     3096                        c = std.utf.decode(fmt, i); 
     3097                    } 
     3098                  Lputc: 
     3099                    putc(c); 
     3100                    continue; 
     3101                } 
     3102 
     3103                // Get flags {-+ #} 
     3104                flags = 0; 
     3105                while (1) 
     3106                { 
     3107                    c = getFmtChar(); 
     3108                    switch (c) 
     3109                    { 
     3110                    case '-':        flags |= FLdash;        continue; 
     3111                    case '+':        flags |= FLplus;        continue; 
     3112                    case ' ':        flags |= FLspace;        continue; 
     3113                    case '#':        flags |= FLhash;        continue; 
     3114                    case '0':        flags |= FL0pad;        continue; 
     3115 
     3116                    case '%':        if (flags == 0) 
     3117                            goto Lputc; 
     3118                    default:        break; 
     3119                    } 
     3120                    break; 
     3121                } 
     3122 
     3123                // Get field width 
     3124                field_width = 0; 
     3125                if (c == '*') 
     3126                { 
     3127                    field_width = getFmtStar(); 
     3128                    if (field_width < 0) 
     3129                    {   flags |= FLdash; 
     3130                        field_width = -field_width; 
     3131                    } 
     3132 
     3133                    c = getFmtChar(); 
     3134                } 
     3135                else if (c >= '0' && c <= '9') 
     3136                    field_width = getFmtInt(); 
     3137 
     3138                if (flags & FLplus) 
     3139                    flags &= ~FLspace; 
     3140                if (flags & FLdash) 
     3141                    flags &= ~FL0pad; 
     3142 
     3143                // Get precision 
     3144                precision = 0; 
     3145                if (c == '.') 
     3146                {   flags |= FLprecision; 
     3147                    //flags &= ~FL0pad; 
     3148 
     3149                    c = getFmtChar(); 
     3150                    if (c == '*') 
     3151                    { 
     3152                        precision = getFmtStar(); 
     3153                        if (precision < 0) 
     3154                        {   precision = 0; 
     3155                            flags &= ~FLprecision; 
     3156                        } 
     3157 
     3158                        c = getFmtChar(); 
     3159                    } 
     3160                    else if (c >= '0' && c <= '9') 
     3161                        precision = getFmtInt(); 
     3162                } 
     3163 
     3164                if (j == arguments.length) 
     3165                    goto Lerror; 
     3166                ti = arguments[j++]; 
     3167                ti = skipCI(ti); 
     3168                mi = 9; 
     3169                do 
     3170                { 
     3171                    m = cast(Mangle)ti.classinfo.name[mi++]; 
     3172                } while (m == Mangle.Tconst || m == Mangle.Timmutable); 
     3173 
     3174                if (c > 0x7F)                // if UTF sequence 
     3175                    goto Lerror;        // format specifiers can't be UTF 
     3176                formatArg(cast(char)c); 
     3177            } 
     3178        } 
     3179        else 
     3180        { 
     3181            formatArg('s'); 
     3182        } 
     3183    } 
     3184    return; 
     3185 
     3186  Lerror: 
     3187    throw new FormatError(); 
     3188
     3189 
     3190/* ======================== Unit Tests ====================================== */ 
    30373191 
    30383192unittest 
    30393193{ 
    3040     char[] line = "1 2".dup; 
    3041     string format = "%s %s"; 
    3042     int a, b; 
    3043     formattedRead(line, format, &a, &b); 
    3044     assert(a == 1 && b == 2); 
    3045  
    3046     line = "1 2 3".dup; 
    3047     format = "%d "; 
    3048     formattedRead(line, format, &a); 
    3049     assert(a == 1); 
    3050  
    3051     Tuple!(int, float) t; 
    3052     line = "1 2.125".dup; 
    3053     formattedRead(line, format, &t); 
    3054     assert(t.field[0] == 1 && t.field[1] == 2.125); 
    3055  
    3056     line = "1 7643 2.125".dup; 
    3057     formattedRead(line, "%s %*u %s", &t); 
    3058     assert(t.field[0] == 1 && t.field[1] == 2.125); 
    3059 
     3194    int i; 
     3195    string s; 
     3196 
     3197    debug(format) printf("std.format.format.unittest\n"); 
     3198 
     3199    s = std.string.format("hello world! %s %s ", true, 57, 1_000_000_000, 'x', " foo"); 
     3200    assert(s == "hello world! true 57 1000000000x foo"); 
     3201 
     3202    s = std.string.format(1.67, " %A ", -1.28, float.nan); 
     3203    /* The host C library is used to format floats. 
     3204     * C99 doesn't specify what the hex digit before the decimal point 
     3205     * is for %A. 
     3206     */ 
     3207    version (linux) 
     3208        assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan"); 
     3209    else 
     3210        assert(s == "1.67 -0X1.47AE147AE147BP+0 nan"); 
     3211 
     3212    s = std.string.format("%x %X", 0x1234AF, 0xAFAFAFAF); 
     3213    assert(s == "1234af AFAFAFAF"); 
     3214 
     3215    s = std.string.format("%b %o", 0x1234AF, 0xAFAFAFAF); 
     3216    assert(s == "100100011010010101111 25753727657"); 
     3217 
     3218    s = std.string.format("%d %s", 0x1234AF, 0xAFAFAFAF); 
     3219    assert(s == "1193135 2947526575"); 
     3220 
     3221    s = std.string.format("%s", 1.2 + 3.4i); 
     3222    assert(s == "1.2+3.4i"); 
     3223 
     3224    s = std.string.format("%x %X", 1.32, 6.78f); 
     3225    assert(s == "3ff51eb851eb851f 40D8F5C3"); 
     3226 
     3227    s = std.string.format("%#06.*f",2,12.345); 
     3228    assert(s == "012.35"); 
     3229 
     3230    s = std.string.format("%#0*.*f",6,2,12.345); 
     3231    assert(s == "012.35"); 
     3232 
     3233    s = std.string.format("%7.4g:", 12.678); 
     3234    assert(s == "  12.68:"); 
     3235 
     3236    s = std.string.format("%7.4g:", 12.678L); 
     3237    assert(s == "  12.68:"); 
     3238 
     3239    s = std.string.format("%04f|%05d|%#05x|%#5x",-4.,-10,1,1); 
     3240    assert(s == "-4.000000|-0010|0x001|  0x1"); 
     3241 
     3242    i = -10; 
     3243    s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
     3244    assert(s == "-10|-10|-10|-10|-10.0000"); 
     3245 
     3246    i = -5; 
     3247    s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
     3248    assert(s == "-5| -5|-05|-5|-5.0000"); 
     3249 
     3250    i = 0; 
     3251    s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
     3252    assert(s == "0|  0|000|0|0.0000"); 
     3253 
     3254    i = 5; 
     3255    s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
     3256    assert(s == "5|  5|005|5|5.0000"); 
     3257 
     3258    i = 10; 
     3259    s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 
     3260    assert(s == "10| 10|010|10|10.0000"); 
     3261 
     3262    s = std.string.format("%.0d", 0); 
     3263    assert(s == ""); 
     3264 
     3265    s = std.string.format("%.g", .34); 
     3266    assert(s == "0.3"); 
     3267 
     3268    s = std.string.format("%.0g", .34); 
     3269    assert(s == "0.3"); 
     3270 
     3271    s = std.string.format("%.2g", .34); 
     3272    assert(s == "0.34"); 
     3273 
     3274    s = std.string.format("%0.0008f", 1e-08); 
     3275    assert(s == "0.00000001"); 
     3276 
     3277    s = std.string.format("%0.0008f", 1e-05); 
     3278    assert(s == "0.00001000"); 
     3279 
     3280    s = "helloworld"; 
     3281    string r; 
     3282    r = std.string.format("%.2s", s[0..5]); 
     3283    assert(r == "he"); 
     3284    r = std.string.format("%.20s", s[0..5]); 
     3285    assert(r == "hello"); 
     3286    r = std.string.format("%8s", s[0..5]); 
     3287    assert(r == "   hello"); 
     3288 
     3289    byte[] arrbyte = new byte[4]; 
     3290    arrbyte[0] = 100; 
     3291    arrbyte[1] = -99; 
     3292    arrbyte[3] = 0; 
     3293    r = std.string.format(arrbyte); 
     3294    assert(r == "[100,-99,0,0]"); 
     3295 
     3296    ubyte[] arrubyte = new ubyte[4]; 
     3297    arrubyte[0] = 100; 
     3298    arrubyte[1] = 200; 
     3299    arrubyte[3] = 0; 
     3300    r = std.string.format(arrubyte); 
     3301    assert(r == "[100,200,0,0]"); 
     3302 
     3303    short[] arrshort = new short[4]; 
     3304    arrshort[0] = 100; 
     3305    arrshort[1] = -999; 
     3306    arrshort[3] = 0; 
     3307    r = std.string.format(arrshort); 
     3308    assert(r == "[100,-999,0,0]"); 
     3309    r = std.string.format("%s",arrshort); 
     3310    assert(r == "[100,-999,0,0]"); 
     3311 
     3312    ushort[] arrushort = new ushort[4]; 
     3313    arrushort[0] = 100; 
     3314    arrushort[1] = 20_000; 
     3315    arrushort[3] = 0; 
     3316    r = std.string.format(arrushort); 
     3317    assert(r == "[100,20000,0,0]"); 
     3318 
     3319    int[] arrint = new int[4]; 
     3320    arrint[0] = 100; 
     3321    arrint[1] = -999; 
     3322    arrint[3] = 0; 
     3323    r = std.string.format(arrint); 
     3324    assert(r == "[100,-999,0,0]"); 
     3325    r = std.string.format("%s",arrint); 
     3326    assert(r == "[100,-999,0,0]"); 
     3327 
     3328    long[] arrlong = new long[4]; 
     3329    arrlong[0] = 100; 
     3330    arrlong[1] = -999; 
     3331    arrlong[3] = 0; 
     3332    r = std.string.format(arrlong); 
     3333    assert(r == "[100,-999,0,0]"); 
     3334    r = std.string.format("%s",arrlong); 
     3335    assert(r == "[100,-999,0,0]"); 
     3336 
     3337    ulong[] arrulong = new ulong[4]; 
     3338    arrulong[0] = 100; 
     3339    arrulong[1] = 999; 
     3340    arrulong[3] = 0; 
     3341    r = std.string.format(arrulong); 
     3342    assert(r == "[100,999,0,0]"); 
     3343 
     3344    string[] arr2 = new string[4]; 
     3345    arr2[0] = "hello"; 
     3346    arr2[1] = "world"; 
     3347    arr2[3] = "foo"; 
     3348    r = std.string.format(arr2); 
     3349    assert(r == "[hello,world,,foo]"); 
     3350 
     3351    r = std.string.format("%.8d", 7); 
     3352    assert(r == "00000007"); 
     3353    r = std.string.format("%.8x", 10); 
     3354    assert(r == "0000000a"); 
     3355 
     3356    r = std.string.format("%-3d", 7); 
     3357    assert(r == "7  "); 
     3358 
     3359    r = std.string.format("%*d", -3, 7); 
     3360    assert(r == "7  "); 
     3361 
     3362    r = std.string.format("%.*d", -3, 7); 
     3363    assert(r == "7"); 
     3364 
     3365    typedef int myint; 
     3366    myint m = -7; 
     3367    r = std.string.format(m); 
     3368    assert(r == "-7"); 
     3369 
     3370    r = std.string.format("abc"c); 
     3371    assert(r == "abc"); 
     3372    r = std.string.format("def"w); 
     3373    assert(r == "def"); 
     3374    r = std.string.format("ghi"d); 
     3375    assert(r == "ghi"); 
     3376 
     3377    void* p = cast(void*)0xDEADBEEF; 
     3378    r = std.string.format(p); 
     3379    assert(r == "DEADBEEF"); 
     3380 
     3381    r = std.string.format("%#x", 0xabcd); 
     3382    assert(r == "0xabcd"); 
     3383    r = std.string.format("%#X", 0xABCD); 
     3384    assert(r == "0XABCD"); 
     3385 
     3386    r = std.string.format("%#o", 012345); 
     3387    assert(r == "012345"); 
     3388    r = std.string.format("%o", 9); 
     3389    assert(r == "11"); 
     3390 
     3391    r = std.string.format("%+d", 123); 
     3392    assert(r == "+123"); 
     3393    r = std.string.format("%+d", -123); 
     3394    assert(r == "-123"); 
     3395    r = std.string.format("% d", 123); 
     3396    assert(r == " 123"); 
     3397    r = std.string.format("% d", -123); 
     3398    assert(r == "-123"); 
     3399 
     3400    r = std.string.format("%%"); 
     3401    assert(r == "%"); 
     3402 
     3403    r = std.string.format("%d", true); 
     3404    assert(r == "1"); 
     3405    r = std.string.format("%d", false); 
     3406    assert(r == "0"); 
     3407 
     3408    r = std.string.format("%d", 'a'); 
     3409    assert(r == "97"); 
     3410    wchar wc = 'a'; 
     3411    r = std.string.format("%d", wc); 
     3412    assert(r == "97"); 
     3413    dchar dc = 'a'; 
     3414    r = std.string.format("%d", dc); 
     3415    assert(r == "97"); 
     3416 
     3417    byte b = byte.max; 
     3418    r = std.string.format("%x", b); 
     3419    assert(r == "7f"); 
     3420    r = std.string.format("%x", ++b); 
     3421    assert(r == "80"); 
     3422    r = std.string.format("%x", ++b); 
     3423    assert(r == "81"); 
     3424 
     3425    short sh = short.max; 
     3426    r = std.string.format("%x", sh); 
     3427    assert(r == "7fff"); 
     3428    r = std.string.format("%x", ++sh); 
     3429    assert(r == "8000"); 
     3430    r = std.string.format("%x", ++sh); 
     3431    assert(r == "8001"); 
     3432 
     3433    i = int.max; 
     3434    r = std.string.format("%x", i); 
     3435    assert(r == "7fffffff"); 
     3436    r = std.string.format("%x", ++i); 
     3437    assert(r == "80000000"); 
     3438    r = std.string.format("%x", ++i); 
     3439    assert(r == "80000001"); 
     3440 
     3441    r = std.string.format("%x", 10); 
     3442    assert(r == "a"); 
     3443    r = std.string.format("%X", 10); 
     3444    assert(r == "A"); 
     3445    r = std.string.format("%x", 15); 
     3446    assert(r == "f"); 
     3447    r = std.string.format("%X", 15); 
     3448    assert(r == "F"); 
     3449 
     3450    Object c = null; 
     3451    r = std.string.format(c); 
     3452    assert(r == "null"); 
     3453 
     3454    enum TestEnum 
     3455    { 
     3456            Value1, Value2 
     3457    } 
     3458    r = std.string.format("%s", TestEnum.Value2); 
     3459    assert(r == "1"); 
     3460 
     3461    immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); 
     3462    r = std.string.format("%s", aa.values); 
     3463    assert(r == "[[h,e,l,l,o],[b,e,t,t,y]]"); 
     3464    r = std.string.format("%s", aa); 
     3465    assert(r == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]"); 
     3466 
     3467    static const dchar[] ds = ['a','b']; 
     3468    for (int j = 0; j < ds.length; ++j) 
     3469    { 
     3470        r = std.string.format(" %d", ds[j]); 
     3471        if (j == 0) 
     3472            assert(r == " 97"); 
     3473        else 
     3474            assert(r == " 98"); 
     3475    } 
     3476 
     3477    r = std.string.format(">%14d<, ", 15, [1,2,3]); 
     3478    assert(r == ">            15<, [1,2,3]"); 
     3479 
     3480    assert(std.string.format("%8s", "bar") == "     bar"); 
     3481    assert(std.string.format("%8s", "b\u00e9ll\u00f4") == "   b\u00e9ll\u00f4"); 
     3482