// Written in the D programming language.

/**
   This module implements the formatting functionality for strings and
   I/O. It's comparable to C99's $(D vsprintf()) and uses a similar
   format encoding scheme.

   Macros: WIKI = Phobos/StdFormat

   Copyright: Copyright Digital Mars 2000-.

   License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).

   Authors: $(WEB digitalmars.com, Walter Bright), $(WEB erdani.com,
   Andrei Alexandrescu), and Kenji Hara

   Source: $(PHOBOSSRC std/_format.d)
 */

/* NOTE: This file has been patched from the original DMD distribution to
   work with the GDC compiler.
 */
module std.format;

//debug=format;                // uncomment to turn on debugging printf's

import core.stdc.stdio, core.stdc.stdlib, core.stdc.string, core.vararg;
import std.algorithm, std.array, std.ascii, std.bitmanip, std.conv,
    std.exception, std.functional, std.math, std.range,
    std.string, std.system, std.traits, std.typecons, std.typetuple,
    std.utf;
version(unittest) {
    import std.stdio;
}

version (Windows) version (DigitalMars)
{
    version = DigitalMarsC;
}

version (DigitalMarsC)
{
    // This is DMC's internal floating point formatting function
    extern (C)
    {
        extern shared char* function(int c, int flags, int precision,
                in real* pdval,
                char* buf, size_t* psl, int width) __pfloatfmt;
    }
    alias core.stdc.stdio._snprintf snprintf;
}
else version (MinGW)
{
    // use snprintf from stdio
}
else
{
    // Use C99 snprintf
    extern (C) int snprintf(char* s, size_t n, in char* format, ...);
}

/**********************************************************************
 * Signals a mismatch between a format and its corresponding argument.
 */
class FormatException : Exception
{
    this()
    {
        super("format error");
    }

    this(string msg, string fn = __FILE__, size_t ln = __LINE__)
    {
        super(msg, fn, ln);
    }
}

/**
$(RED Scheduled for deprecation. Please use $(D FormatException)) instead.
 */
/*deprecated*/ alias FormatException FormatError;

/**********************************************************************
   Interprets variadic argument list $(D args), formats them according
   to $(D fmt), and sends the resulting characters to $(D w). The
   encoding of the output is the same as $(D Char). type $(D Writer)
   must satisfy $(XREF range,isOutputRange!(Writer, Char)).

   The variadic arguments are normally consumed in order. POSIX-style
   $(WEB opengroup.org/onlinepubs/009695399/functions/printf.html,
   positional parameter syntax) is also supported. Each argument is
   formatted into a sequence of chars according to the format
   specification, and the characters are passed to $(D w). As many
   arguments as specified in the format string are consumed and
   formatted. If there are fewer arguments than format specifiers, a
   $(D FormatException) is thrown. If there are more remaining arguments
   than needed by the format specification, they are ignored but only
   if at least one argument was formatted.

   Params:

   w = Output is sent do this writer. Typical output writers include
   $(XREF range,Appender!string) and $(XREF stdio,BlockingTextWriter).

   fmt = Format string.

   args = Variadic argument list.

   Throws: Mismatched arguments and formats result in a $(D
   FormatException) being thrown.

   Format_String: <a name="format-string">$(I Format strings)</a>
   consist of characters interspersed with $(I format
   specifications). Characters are simply copied to the output (such
   as putc) after any necessary conversion to the corresponding UTF-8
   sequence.

   The format string has the following grammar:

$(PRE
$(I FormatString):
    $(I FormatStringItem)*
$(I FormatStringItem):
    $(B '%%')
    $(B '%') $(I Position) $(I Flags) $(I Width) $(I Precision) $(I FormatChar)
    $(B '%$(LPAREN)') $(I FormatString) $(B '%$(RPAREN)')
    $(I OtherCharacterExceptPercent)
$(I Position):
    $(I empty)
    $(I Integer) $(B '$')
$(I Flags):
    $(I empty)
    $(B '-') $(I Flags)
    $(B '+') $(I Flags)
    $(B '#') $(I Flags)
    $(B '0') $(I Flags)
    $(B ' ') $(I Flags)
$(I Width):
    $(I empty)
    $(I Integer)
    $(B '*')
$(I Precision):
    $(I empty)
    $(B '.')
    $(B '.') $(I Integer)
    $(B '.*')
$(I Integer):
    $(I Digit)
    $(I Digit) $(I Integer)
$(I Digit):
    $(B '0')|$(B '1')|$(B '2')|$(B '3')|$(B '4')|$(B '5')|$(B '6')|$(B '7')|$(B '8')|$(B '9')
$(I FormatChar):
    $(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')
)

    $(BOOKTABLE Flags affect formatting depending on the specifier as
    follows., $(TR $(TH Flag) $(TH Types&nbsp;affected) $(TH Semantics))

    $(TR $(TD $(B '-')) $(TD numeric) $(TD Left justify the result in
        the field.  It overrides any $(B 0) flag.))

    $(TR $(TD $(B '+')) $(TD numeric) $(TD Prefix positive numbers in
    a signed conversion with a $(B +).  It overrides any $(I space)
    flag.))

    $(TR $(TD $(B '#')) $(TD integral ($(B 'o'))) $(TD Add to
    precision as necessary so that the first digit of the octal
    formatting is a '0', even if both the argument and the $(I
    Precision) are zero.))

    $(TR $(TD $(B '#')) $(TD integral ($(B 'x'), $(B 'X'))) $(TD If
       non-zero, prefix result with $(B 0x) ($(B 0X)).))

    $(TR $(TD $(B '#')) $(TD floating) $(TD Always insert the decimal
       point and print trailing zeros.))

    $(TR $(TD $(B '#')) $(TD numeric ($(B '0'))) $(TD Use leading
    zeros to pad rather than spaces (except for the floating point
    values $(D nan) and $(D infinity)).  Ignore if there's a $(I
    Precision).))

    $(TR $(TD $(B ' ')) $(TD integral ($(B 'd'))) $(TD Prefix positive
    numbers in a signed conversion with a space.)))

    <dt>$(I Width)
    <dd>
    Specifies the minimum field width.
    If the width is a $(B *), the next argument, which must be
    of type $(B int), is taken as the width.
    If the width is negative, it is as if the $(B -) was given
    as a $(I Flags) character.

    <dt>$(I Precision)
    <dd> Gives the precision for numeric conversions.
    If the precision is a $(B *), the next argument, which must be
    of type $(B int), is taken as the precision. If it is negative,
    it is as if there was no $(I Precision).

    <dt>$(I FormatChar)
    <dd>
    <dl>
        <dt>$(B 's')
        <dd>The corresponding argument is formatted in a manner consistent
        with its type:
        <dl>
            <dt>$(B bool)
            <dd>The result is <tt>'true'</tt> or <tt>'false'</tt>.
            <dt>integral types
            <dd>The $(B %d) format is used.
            <dt>floating point types
            <dd>The $(B %g) format is used.
            <dt>string types
            <dd>The result is the string converted to UTF-8.
            A $(I Precision) specifies the maximum number of characters
            to use in the result.
            <dt>classes derived from $(B Object)
            <dd>The result is the string returned from the class instance's
            $(B .toString()) method.
            A $(I Precision) specifies the maximum number of characters
            to use in the result.
            <dt>non-string static and dynamic arrays
            <dd>The result is [s<sub>0</sub>, s<sub>1</sub>, ...]
            where s<sub>k</sub> is the kth element
            formatted with the default format.
        </dl>

        <dt>$(B 'b','d','o','x','X')
        <dd> The corresponding argument must be an integral type
        and is formatted as an integer. If the argument is a signed type
        and the $(I FormatChar) is $(B d) it is converted to
        a signed string of characters, otherwise it is treated as
        unsigned. An argument of type $(B bool) is formatted as '1'
        or '0'. The base used is binary for $(B b), octal for $(B o),
        decimal
        for $(B d), and hexadecimal for $(B x) or $(B X).
        $(B x) formats using lower case letters, $(B X) uppercase.
        If there are fewer resulting digits than the $(I Precision),
        leading zeros are used as necessary.
        If the $(I Precision) is 0 and the number is 0, no digits
        result.

        <dt>$(B 'e','E')
        <dd> A floating point number is formatted as one digit before
        the decimal point, $(I Precision) digits after, the $(I FormatChar),
        &plusmn;, followed by at least a two digit exponent: $(I d.dddddd)e$(I &plusmn;dd).
        If there is no $(I Precision), six
        digits are generated after the decimal point.
        If the $(I Precision) is 0, no decimal point is generated.

        <dt>$(B 'f','F')
        <dd> A floating point number is formatted in decimal notation.
        The $(I Precision) specifies the number of digits generated
        after the decimal point. It defaults to six. At least one digit
        is generated before the decimal point. If the $(I Precision)
        is zero, no decimal point is generated.

        <dt>$(B 'g','G')
        <dd> A floating point number is formatted in either $(B e) or
        $(B f) format for $(B g); $(B E) or $(B F) format for
        $(B G).
        The $(B f) format is used if the exponent for an $(B e) format
        is greater than -5 and less than the $(I Precision).
        The $(I Precision) specifies the number of significant
        digits, and defaults to six.
        Trailing zeros are elided after the decimal point, if the fractional
        part is zero then no decimal point is generated.

        <dt>$(B 'a','A')
        <dd> A floating point number is formatted in hexadecimal
        exponential notation 0x$(I h.hhhhhh)p$(I &plusmn;d).
        There is one hexadecimal digit before the decimal point, and as
        many after as specified by the $(I Precision).
        If the $(I Precision) is zero, no decimal point is generated.
        If there is no $(I Precision), as many hexadecimal digits as
        necessary to exactly represent the mantissa are generated.
        The exponent is written in as few digits as possible,
        but at least one, is in decimal, and represents a power of 2 as in
        $(I h.hhhhhh)*2<sup>$(I &plusmn;d)</sup>.
        The exponent for zero is zero.
        The hexadecimal digits, x and p are in upper case if the
        $(I FormatChar) is upper case.
    </dl>

    Floating point NaN's are formatted as $(B nan) if the
    $(I FormatChar) is lower case, or $(B NAN) if upper.
    Floating point infinities are formatted as $(B inf) or
    $(B infinity) if the
    $(I FormatChar) is lower case, or $(B INF) or $(B INFINITY) if upper.
    </dl>

Example:

-------------------------
import std.c.stdio;
import std.format;

void main()
{
    auto writer = appender!string();
    formattedWrite(writer, "%s is the ultimate %s.", 42, "answer");
    assert(writer.data == "42 is the ultimate answer.");
    // Clear the writer
    writer = appender!string();
    formattedWrite(writer, "Date: %2$s %1$s", "October", 5);
    assert(writer.data == "Date: 5 October");
}
------------------------

The positional and non-positional styles can be mixed in the same
format string. (POSIX leaves this behavior undefined.) The internal
counter for non-positional parameters tracks the next parameter after
the largest positional parameter already used.
 */
void formattedWrite(Writer, Char, A...)(Writer w, in Char[] fmt, A args)
{
    enum len = args.length;
    void function(Writer, const(void)*, ref FormatSpec!Char) funs[len] = void;
    const(void)* argsAddresses[len] = void;
    foreach (i, arg; args)
    {
        funs[i] = &formatGeneric!(Writer, typeof(arg), Char);
      version (GNU)
      {
        size_t sz = typeof(arg).sizeof;
        void* p = (new void[sz]).ptr;
        p[0 .. sz] = (cast(void*) & arg)[0 .. sz];
        argsAddresses[i] = p;
      }
      else
      {
        // We can safely cast away shared because all data is either
        // immutable or completely owned by this function.
        argsAddresses[i] = cast(const(void*)) &args[ i ];
      }
    }
    // Are we already done with formats? Then just dump each parameter in turn
    uint currentArg = 0;
    auto spec = FormatSpec!Char(fmt);
    while (spec.writeUpToNextSpec(w))
    {
        if (currentArg == funs.length && !spec.indexStart)
        {
            // leftover spec?
            enforce(fmt.length == 0, new FormatException(
                    cast(string) ("Orphan format specifier: %" ~ fmt)));
            break;
        }
        if (spec.width == spec.DYNAMIC)
        {
            auto width = to!(typeof(spec.width))(getNthInt(currentArg, args));
            if (width < 0)
            {
                spec.flDash = true;
                width = -width;
            }
            spec.width = width;
            ++currentArg;
        }
        else if (spec.width < 0)
        {
            // means: get width as a positional parameter
            auto index = cast(uint) -spec.width;
            assert(index > 0);
            auto width = to!(typeof(spec.width))(getNthInt(index - 1, args));
            if (currentArg < index) currentArg = index;
            if (width < 0)
            {
                spec.flDash = true;
                width = -width;
            }
            spec.width = width;
        }
        if (spec.precision == spec.DYNAMIC)
        {
            auto precision = to!(typeof(spec.precision))(
                getNthInt(currentArg, args));
            if (precision >= 0) spec.precision = precision;
            // else negative precision is same as no precision
            else spec.precision = spec.UNSPECIFIED;
            ++currentArg;
        }
        else if (spec.precision < 0)
        {
            // means: get precision as a positional parameter
            auto index = cast(uint) -spec.precision;
            assert(index > 0);
            auto precision = to!(typeof(spec.precision))(
                getNthInt(index- 1, args));
            if (currentArg < index) currentArg = index;
            if (precision >= 0) spec.precision = precision;
            // else negative precision is same as no precision
            else spec.precision = spec.UNSPECIFIED;
        }
        // Format!
        if (spec.indexStart > 0)
        {
            // using positional parameters!
            foreach (i; spec.indexStart - 1 .. spec.indexEnd)
            {
                if (funs.length <= i) break;
                funs[i](w, argsAddresses[i], spec);
            }
            if (currentArg < spec.indexEnd) currentArg = spec.indexEnd;
        }
        else
        {
            funs[currentArg](w, argsAddresses[currentArg], spec);
            ++currentArg;
        }
    }
}

/**
   Reads characters from input range $(D r), converts them according
   to $(D fmt), and writes them to $(D args).

   Example:
----
string s = "hello!124:34.5";
string a;
int b;
double c;
formattedRead(s, "%s!%s:%s", &a, &b, &c);
assert(a == "hello" && b == 124 && c == 34.5);
----
 */
uint formattedRead(R, Char, S...)(ref R r, const(Char)[] fmt, S args)
{
    auto spec = FormatSpec!Char(fmt);
    static if (!S.length)
    {
        spec.readUpToNextSpec(r);
        enforce(spec.trailing.empty);
        return 0;
    }
    else
    {
        // The function below accounts for '*' == fields meant to be
        // read and skipped
        void skipUnstoredFields()
        {
            for (;;)
            {
                spec.readUpToNextSpec(r);
                if (spec.width != spec.DYNAMIC) break;
                // must skip this field
                skipData(r, spec);
            }
        }

        skipUnstoredFields();
        if (r.empty)
        {
            // Input is empty, nothing to read
            return 0;
        }
        alias typeof(*args[0]) A;
        static if (isTuple!A)
        {
            foreach (i, T; A.Types)
            {
                (*args[0])[i] = unformatValue!(T)(r, spec);
                skipUnstoredFields();
            }
        }
        else
        {
            *args[0] = unformatValue!(A)(r, spec);
        }
        return 1 + formattedRead(r, spec.trailing, args[1 .. $]);
    }
}

unittest
{
    string s = " 1.2 3.4 ";
    double x, y, z;
    assert(formattedRead(s, " %s %s %s ", &x, &y, &z) == 2);
    assert(s.empty);
    assert(x == 1.2);
    assert(y == 3.4);
    assert(isnan(z));
}

template FormatSpec(Char)
    if (!is(Unqual!Char == Char))
{
    alias FormatSpec!(Unqual!Char) FormatSpec;
}

/**
 A compiled version of an individual format specifier, backwards
 compatible with $(D printf) specifiers.
 */
struct FormatSpec(Char)
    if (is(Unqual!Char == Char))
{
    /**
       Minimum _width, default $(D 0).
     */
    int width = 0;
    /**
       Precision. Its semantics depends on the argument type. For
       floating point numbers, _precision dictates the number of
       decimals printed.
     */
    int precision = UNSPECIFIED;
    /**
       Special value for width and precision. $(D DYNAMIC) width or
       precision means that they were specified with $(D '*') in the
       format string and are passed at runtime through the varargs.
     */
    enum int DYNAMIC = int.max;
    /**
       Special value for precision, meaning the format specifier
       contained no explicit precision.
     */
    enum int UNSPECIFIED = DYNAMIC - 1;
    /**
       The actual format specifier, $(D 's') by default.
    */
    char spec = 's';
    /**
       Index of the argument for positional parameters, from $(D 1) to
       $(D ubyte.max). ($(D 0) means not used).
    */
    ubyte indexStart;
    /**
       Index of the last argument for positional parameter range, from
       $(D 1) to $(D ubyte.max). ($(D 0) means not used).
    */
    ubyte indexEnd;
    version(StdDdoc) {
        /**
         The format specifier contained a $(D '-') ($(D printf)
         compatibility).
         */
        bool flDash;
        /**
         The format specifier contained a $(D '0') ($(D printf)
         compatibility).
         */
        bool flZero;
        /**
         The format specifier contained a $(D ' ') ($(D printf)
         compatibility).
         */
        bool flSpace;
        /**
         The format specifier contained a $(D '+') ($(D printf)
         compatibility).
         */
        bool flPlus;
        /**
         The format specifier contained a $(D '#') ($(D printf)
         compatibility).
         */
        bool flHash;
        // Fake field to allow compilation
        ubyte allFlags;
    }
    else
    {
        union
        {
            ubyte allFlags;
            mixin(bitfields!(
                        bool, "flDash", 1,
                        bool, "flZero", 1,
                        bool, "flSpace", 1,
                        bool, "flPlus", 1,
                        bool, "flHash", 1,
                        ubyte, "", 3));
        }
    }

    /**
       In case of a compound format specifier starting with $(D
       "%$(LPAREN)") and ending with $(D "%$(RPAREN)"), $(D _nested)
       contains the string contained within the two separators.
     */
    const(Char)[] nested;

    /**
       $(D _trailing) contains the rest of the format string.
     */
    const(Char)[] trailing;

    /*
       This string is inserted before each sequence (e.g. array)
       formatted (by default $(D "[")).
     */
    static const(Char)[] seqBefore = "[";

    /*
       This string is inserted after each sequence formatted (by
       default $(D "]")).
     */
    static const(Char)[] seqAfter = "]";

    /*
       This string is inserted after each element keys of a sequence (by
       default $(D ":")).
     */
    static const(Char)[] keySeparator = ":";

    /*
       This string is inserted in between elements of a sequence (by
       default $(D ", ")).
     */
    static const(Char)[] seqSeparator = ", ";

    /**
       Given a string format specification fmt, parses a format
       specifier. The string is assumed to start with the character
       immediately following the $(D '%'). The string is advanced to
       right after the end of the format specifier.
     */
    this(in Char[] fmt)
    {
        trailing = fmt;
    }

    bool writeUpToNextSpec(OutputRange)(OutputRange writer)
    {
        if (trailing.empty) return false;
        for (size_t i = 0; i < trailing.length; ++i)
        {
            if (trailing[i] != '%') continue;
            if (trailing[++i] != '%')
            {
                // Spec found. Print, fill up the spec, and bailout
                put(writer, trailing[0 .. i - 1]);
                trailing = trailing[i .. $];
                fillUp();
                return true;
            }
            // Doubled! Now print whatever we had, then update the
            // string and move on
            put(writer, trailing[0 .. i - 1]);
            trailing = trailing[i .. $];
            i = 0;
        }
        // no format spec found
        put(writer, trailing);
        trailing = null;
        return false;
    }

    unittest
    {
        auto w = appender!(char[])();
        auto f = FormatSpec("abc%sdef%sghi");
        f.writeUpToNextSpec(w);
        assert(w.data == "abc", w.data);
        assert(f.trailing == "def%sghi", text(f.trailing));
        f.writeUpToNextSpec(w);
        assert(w.data == "abcdef", w.data);
        assert(f.trailing == "ghi");
        // test with embedded %%s
        f = FormatSpec("ab%%cd%%ef%sg%%h%sij");
        w.clear();
        f.writeUpToNextSpec(w);
        assert(w.data == "ab%cd%ef" && f.trailing == "g%%h%sij", w.data);
        f.writeUpToNextSpec(w);
        assert(w.data == "ab%cd%efg%h" && f.trailing == "ij");
        // bug4775
        f = FormatSpec("%%%s");
        w.clear();
        f.writeUpToNextSpec(w);
        assert(w.data == "%" && f.trailing == "");
        f = FormatSpec("%%%%%s%%");
        w.clear();
        while (f.writeUpToNextSpec(w)) continue;
        assert(w.data == "%%%");
    }

    private void fillUp()
    {
        // Reset content
        allFlags = 0;
        width = 0;
        precision = UNSPECIFIED;
        nested = null;
        // Parse the spec (we assume we're past '%' already)
        for (size_t i = 0; i < trailing.length; )
        {
            switch (trailing[i])
            {
            case '(':
                // Embedded format specifier.
                auto j = i + 1;
                void check(bool condition)
                {
                    enforce(
                        condition,
                        text("Incorrect format specifier: %",
                                trailing[i .. $]));
                }
                // Get the matching balanced paren
                for (uint innerParens;; ++j)
                {
                    check(j < trailing.length);
                    if (trailing[j] != '%')
                    {
                        // skip, we're waiting for %( and %)
                        continue;
                    }
                    if (trailing[++j] == ')')
                    {
                        if (innerParens-- == 0) break;
                    }
                    else if (trailing[j] == '(')
                    {
                        ++innerParens;
                    }
                }
                nested = to!(typeof(nested))(trailing[i + 1 .. j - 1]);
                //this = FormatSpec(innerTrailingSpec);
                spec = '(';
                // We practically found the format specifier
                trailing = trailing[j + 1 .. $];
                return;
            case '-': flDash = true; ++i; break;
            case '+': flPlus = true; ++i; break;
            case '#': flHash = true; ++i; break;
            case '0': flZero = true; ++i; break;
            case ' ': flSpace = true; ++i; break;
            case '*':
                if (isDigit(trailing[++i]))
                {
                    // a '*' followed by digits and '$' is a
                    // positional format
                    trailing = trailing[1 .. $];
                    width = -.parse!(typeof(width))(trailing);
                    i = 0;
                    enforce(trailing[i++] == '$',
                            new FormatException("$ expected"));
                }
                else
                {
                    // read result
                    width = DYNAMIC;
                }
                break;
            case '1': .. case '9':
                auto tmp = trailing[i .. $];
                const widthOrArgIndex = .parse!(uint)(tmp);
                enforce(tmp.length,
                        new FormatException(text("Incorrect format specifier %",
                                        trailing[i .. $])));
                i = tmp.ptr - trailing.ptr;
                if (tmp.startsWith('$'))
                {
                    // index of the form %n$
                    indexEnd = indexStart = to!ubyte(widthOrArgIndex);
                    ++i;
                }
                else if (tmp.length && tmp[0] == ':')
                {
                    // two indexes of the form %m:n$, or one index of the form %m:$
                    indexStart = to!ubyte(widthOrArgIndex);
                    tmp = tmp[1 .. $];
                    if (tmp.startsWith('$'))
                    {
                        indexEnd = indexEnd.max;
                    }
                    else
                    {
                        indexEnd = .parse!(typeof(indexEnd))(tmp);
                    }
                    i = tmp.ptr - trailing.ptr;
                    enforce(trailing[i++] == '$',
                            new FormatException("$ expected"));
                }
                else
                {
                    // width
                    width = to!int(widthOrArgIndex);
                }
                break;
            case '.':
                // Precision
                if (trailing[++i] == '*')
                {
                    if (isDigit(trailing[++i]))
                    {
                        // a '.*' followed by digits and '$' is a
                        // positional precision
                        trailing = trailing[i .. $];
                        i = 0;
                        precision = -.parse!int(trailing);
                        enforce(trailing[i++] == '$',
                                new FormatException("$ expected"));
                    }
                    else
                    {
                        // read result
                        precision = DYNAMIC;
                    }
                }
                else if (trailing[i] == '-')
                {
                    // negative precision, as good as 0
                    precision = 0;
                    auto tmp = trailing[i .. $];
                    .parse!(int)(tmp); // skip digits
                    i = tmp.ptr - trailing.ptr;
                }
                else if (isDigit(trailing[i]))
                {
                    auto tmp = trailing[i .. $];
                    precision = .parse!int(tmp);
                    i = tmp.ptr - trailing.ptr;
                }
                else
                {
                    // "." was specified, but nothing after it
                    precision = 0;
                }
                break;
            default:
                // this is the format char
                spec = cast(char) trailing[i++];
                trailing = trailing[i .. $];
                return;
            } // end switch
        } // end for
        enforce(false, text("Incorrect format specifier: ", trailing));
    }

    //--------------------------------------------------------------------------
    private bool readUpToNextSpec(R)(ref R r)
    {
        // Reset content
        allFlags = 0;
        width = 0;
        precision = UNSPECIFIED;
        nested = null;
        // Parse the spec
        while (trailing.length)
        {
            if (*trailing.ptr == '%')
            {
                if (trailing.length > 1 && trailing.ptr[1] == '%')
                {
                    assert(!r.empty);
                    // Require a '%'
                    if (r.front != '%') break;
                    trailing = trailing[2 .. $];
                    r.popFront();
                }
                else
                {
                    enforce(isLower(trailing[1]) || trailing[1] == '*' ||
                            trailing[1] == '(',
                            text("'%", trailing[1],
                                    "' not supported with formatted read"));
                    trailing = trailing[1 .. $];
                    fillUp();
                    return true;
                }
            }
            else
            {
                if (trailing.ptr[0] == ' ')
                {
                    while (!r.empty && std.ascii.isWhite(r.front)) r.popFront();
                    //r = std.algorithm.find!(not!(std.ascii.isWhite))(r);
                }
                else
                {
                    enforce(!r.empty,
                            text("parseToFormatSpec: Cannot find character `",
                                    trailing.ptr[0], "' in the input string."));
                    if (r.front != trailing.front) break;
                    r.popFront();
                }
                trailing.popFront();
            }
        }
        return false;
    }

    private const string getCurFmtStr()
    {
        auto w = appender!string();
        auto f = FormatSpec!Char("%s"); // for stringnize

        put(w, '%');
        if (indexStart != 0)
            formatValue(w, indexStart, f), put(w, '$');
        if (flDash)  put(w, '-');
        if (flZero)  put(w, '0');
        if (flSpace) put(w, ' ');
        if (flPlus)  put(w, '+');
        if (flHash)  put(w, '#');
        if (width != 0)
            formatValue(w, width, f);
        if (precision != FormatSpec!Char.UNSPECIFIED)
            put(w, '.'), formatValue(w, precision, f);
        put(w, spec);
        return w.data;
    }

    unittest
    {
        // issue 5237
        auto w = appender!string();
        auto f = FormatSpec!char("%.16f");
        f.writeUpToNextSpec(w); // dummy eating
        assert(f.spec == 'f');
        auto fmt = f.getCurFmtStr();
        assert(fmt == "%.16f");
    }

    private const(Char)[] headUpToNextSpec()
    {
        auto w = appender!(typeof(return))();
        auto tr = trailing;

        while (tr.length)
        {
            if (*tr.ptr == '%')
            {
                if (tr.length > 1 && tr.ptr[1] == '%')
                {
                    tr = tr[2 .. $];
                    w.put('%');
                }
                else
                    break;
            }
            else
            {
                w.put(tr.front);
                tr.popFront();
            }
        }
        return w.data;
    }

    string toString()
    {
        return text("address = ", cast(void*) &this,
                "\nwidth = ", width,
                "\nprecision = ", precision,
                "\nspec = ", spec,
                "\nindexStart = ", indexStart,
                "\nindexEnd = ", indexEnd,
                "\nflDash = ", flDash,
                "\nflZero = ", flZero,
                "\nflSpace = ", flSpace,
                "\nflPlus = ", flPlus,
                "\nflHash = ", flHash,
                "\nnested = ", nested,
                "\ntrailing = ", trailing, "\n");
    }
}

/**
   $(D void[]) is formatted like $(D ubyte[]).
 */
void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (is(const(T) == const(void[])))
{
    formatValue(w, cast(const ubyte[])val, f);
}

unittest
{
    FormatSpec!char f;
    auto a = appender!(char[])();
    void[] val0;
    formatValue(a, val0, f);
    assert(a.data == "[]");
    a.clear();

    void[] val = cast(void[])cast(ubyte[])[1, 2, 3];
    formatValue(a, val, f);
    assert(a.data == "[1, 2, 3]");
    a.clear();

    void[0] sval0;
    formatValue(a, sval0, f);
    assert(a.data == "[]");
    a.clear();

    void[3] sval = cast(void[3])cast(ubyte[3])[1, 2, 3];
    formatValue(a, sval, f);
    assert(a.data == "[1, 2, 3]");
}

/**
   $(D enum) is formatted like its base value.
 */
void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (is(T == enum))
{
    foreach (i, e; EnumMembers!T)
    {
        if (val == e) {
            put(w, __traits(allMembers, T)[i]);
            return;
        }
    }

    // val is not a member of T, output cast(T)rawValue instead.
    put(w, "cast(" ~ T.stringof ~ ")");
    static assert(!is(OriginalType!T == T));
    formatValue(w, cast(OriginalType!T)val, f);
}
unittest
{
    auto a = appender!string();
    enum A { first, second, third }
    FormatSpec!char spec;
    formatValue(a, A.second, spec);
    assert(a.data == "second");
    formatValue(a, cast(A)72, spec);
    assert(a.data == "secondcast(A)72");
}
unittest
{
    auto a = appender!string();
    enum A : string { one = "uno", two = "dos", three = "tres" }
    FormatSpec!char spec;
    formatValue(a, A.three, spec);
    assert(a.data == "three");
    formatValue(a, cast(A)"mill\&oacute;n", spec);
    assert(a.data == "threecast(A)mill\&oacute;n");
}
unittest
{
    auto a = appender!string();
    enum A : bool { no, yes }
    FormatSpec!char spec;
    formatValue(a, A.yes, spec);
    assert(a.data == "yes");
    formatValue(a, A.no, spec);
    assert(a.data == "yesno");
}

/**
   Integrals are formatted like $(D printf) does.
 */
void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (isIntegral!T)
{
    if (f.spec == 'r')
    {
        // raw write, skip all else and write the thing
        auto begin = cast(const char*) &val;
        if (std.system.endian == Endian.littleEndian && f.flPlus
            || std.system.endian == Endian.bigEndian && f.flDash)
        {
            // must swap bytes
            foreach_reverse (i; 0 .. val.sizeof)
                put(w, begin[i]);
        }
        else
        {
            foreach (i; 0 .. val.sizeof)
                put(w, begin[i]);
        }
        return;
    }

    // Forward on to formatIntegral to handle both T and const(T)
    // Saves duplication of code for both versions.
    static if (isSigned!T)
        formatIntegral(w, cast(long) val, f, Unsigned!(T).max);
    else
        formatIntegral(w, cast(ulong) val, f, T.max);
}

private void formatIntegral(Writer, T, Char)(Writer w, const(T) val, ref FormatSpec!Char f, ulong mask)
{
    FormatSpec!Char fs = f; // fs is copy for change its values.

    T arg = val;

    uint base =
        fs.spec == 'x' || fs.spec == 'X' ? 16 :
        fs.spec == 'o' ? 8 :
        fs.spec == 'b' ? 2 :
        fs.spec == 's' || fs.spec == 'd' || fs.spec == 'u' ? 10 :
        0;
    if (base == 0)
        throw new FormatException("integral");

    bool negative = (base == 10 && arg < 0);
    if (negative)
    {
        arg = -arg;
    }

    // All unsigned integral types should fit in ulong.
    formatUnsigned(w, (cast(ulong) arg) & mask, fs, base, negative);
}

private void formatUnsigned(Writer, Char)(Writer w, ulong arg, ref FormatSpec!Char fs, uint base, bool negative)
{
    if (fs.precision == fs.UNSPECIFIED)
    {
        // default precision for integrals is 1
        fs.precision = 1;
    }
    else
    {
        // if a precision is specified, the '0' flag is ignored.
        fs.flZero = false;
    }

    char leftPad = void;
    if (!fs.flDash && !fs.flZero)
        leftPad = ' ';
    else if (!fs.flDash && fs.flZero)
        leftPad = '0';
    else
        leftPad = 0;

    // figure out sign and continue in unsigned mode
    char forcedPrefix = void;
    if (fs.flPlus) forcedPrefix = '+';
    else if (fs.flSpace) forcedPrefix = ' ';
    else forcedPrefix = 0;
    if (base != 10)
    {
        // non-10 bases are always unsigned
        forcedPrefix = 0;
    }
    else if (negative)
    {
        // argument is signed
        forcedPrefix = '-';
    }
    // fill the digits
    char[] digits = void;
    {
        char buffer[64]; // 64 bits in base 2 at most
        uint i = buffer.length;
        auto n = arg;
        do
        {
            --i;
            buffer[i] = cast(char) (n % base);
            n /= base;
            if (buffer[i] < 10) buffer[i] += '0';
            else buffer[i] += (fs.spec == 'x' ? 'a' : 'A') - 10;
        } while (n);
        digits = buffer[i .. $]; // got the digits without the sign
    }
    // adjust precision to print a '0' for octal if alternate format is on
    if (base == 8 && fs.flHash
        && (fs.precision <= digits.length)) // too low precision
    {
        //fs.precision = digits.length + (arg != 0);
        forcedPrefix = '0';
    }
    // write left pad; write sign; write 0x or 0X; write digits;
    //   write right pad
    // Writing left pad
    sizediff_t spacesToPrint =
        fs.width // start with the minimum width
        - digits.length  // take away digits to print
        - (forcedPrefix != 0) // take away the sign if any
        - (base == 16 && fs.flHash && arg ? 2 : 0); // 0x or 0X
    const sizediff_t delta = fs.precision - digits.length;
    if (delta > 0) spacesToPrint -= delta;
    if (spacesToPrint > 0) // need to do some padding
    {
        if (leftPad == '0')
        {
            // pad with zeros
            fs.precision =
                cast(typeof(fs.precision)) (spacesToPrint + digits.length);
                //to!(typeof(fs.precision))(spacesToPrint + digits.length);
        }
        else if (leftPad) foreach (i ; 0 .. spacesToPrint) put(w, ' ');
    }
    // write sign
    if (forcedPrefix) put(w, forcedPrefix);
    // write 0x or 0X
    if (base == 16 && fs.flHash && arg) {
        // @@@ overcome bug in dmd;
        //w.write(fs.spec == 'x' ? "0x" : "0X"); //crashes the compiler
        put(w, '0');
        put(w, fs.spec == 'x' ? 'x' : 'X'); // x or X
    }
    // write the digits
    if (arg || fs.precision)
    {
        sizediff_t zerosToPrint = fs.precision - digits.length;
        foreach (i ; 0 .. zerosToPrint) put(w, '0');
        put(w, digits);
    }
    // write the spaces to the right if left-align
    if (!leftPad) foreach (i ; 0 .. spacesToPrint) put(w, ' ');
}

/**
 * Floating-point values are formatted like $(D printf) does.
 */
void formatValue(Writer, D, Char)(Writer w, D obj, ref FormatSpec!Char f)
if (isFloatingPoint!D)
{
    FormatSpec!Char fs = f; // fs is copy for change its values.

    if (fs.spec == 'r')
    {
        // raw write, skip all else and write the thing
        auto begin = cast(const char*) &obj;
        if (std.system.endian == Endian.littleEndian && f.flPlus
            || std.system.endian == Endian.bigEndian && f.flDash)
        {
            // must swap bytes
            foreach_reverse (i; 0 .. obj.sizeof)
                put(w, begin[i]);
        }
        else
        {
            foreach (i; 0 .. obj.sizeof)
                put(w, begin[i]);
        }
        return;
    }
    if (std.string.indexOf("fgFGaAeEs", fs.spec) < 0) {
        throw new FormatException("floating");
    }
    if (fs.spec == 's') fs.spec = 'g';
    char sprintfSpec[1 /*%*/ + 5 /*flags*/ + 3 /*width.prec*/ + 2 /*format*/
                     + 1 /*\0*/] = void;
    sprintfSpec[0] = '%';
    uint i = 1;
    if (fs.flDash) sprintfSpec[i++] = '-';
    if (fs.flPlus) sprintfSpec[i++] = '+';
    if (fs.flZero) sprintfSpec[i++] = '0';
    if (fs.flSpace) sprintfSpec[i++] = ' ';
    if (fs.flHash) sprintfSpec[i++] = '#';
    sprintfSpec[i .. i + 3] = "*.*";
    i += 3;
    if (is(Unqual!D == real)) sprintfSpec[i++] = 'L';
    sprintfSpec[i++] = fs.spec;
    sprintfSpec[i] = 0;
    //printf("format: '%s'; geeba: %g\n", sprintfSpec.ptr, obj);
    char[512] buf;
    immutable n = snprintf(buf.ptr, buf.length,
            sprintfSpec.ptr,
            fs.width,
            // negative precision is same as no precision specified
            fs.precision == fs.UNSPECIFIED ? -1 : fs.precision,
            obj);
    if (n < 0)
        throw new FormatException("floating point formatting failure");
    put(w, buf[0 .. strlen(buf.ptr)]);
}

unittest
{
    auto a = appender!(string)();
    immutable real x = 5.5;
    FormatSpec!char f;
    formatValue(a, x, f);
    assert(a.data == "5.5");
}

/**
   $(D bool) is formatted as "true" or "false" with %s and as "1" or
   "0" with integral-specific format specs.
 */
void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (is(Unqual!T == bool))
{
    if (f.spec == 's') {
        put(w, val ? "true" : "false");
    } else {
        formatValue(w, cast(int) val, f);
    }
}

/**
   Individual characters ($(D char), $(D wchar), or $(D dchar)) are
   formatted as Unicode characters with %s and as integers with
   integral-specific format specs.
 */
void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (isSomeChar!T)
{
    if (f.spec == 's')
    {
        put(w, val);
    }
    else
    {
        formatValue(w, cast(uint) val, f);
    }
}

/**
   Strings are formatted like printf does.
 */
void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (isSomeString!T && !isStaticArray!T && !is(T == enum))
{
    Unqual!(StringTypeOf!T) str = val;  // for `alias this`, see bug5371

    if (f.spec == 's')
    {
        auto s = str[0 .. f.precision < $ ? f.precision : $];
        if (!f.flDash)
        {
            // right align
            if (f.width > s.length)
                foreach (i ; 0 .. f.width - s.length) put(w, ' ');
            put(w, s);
        }
        else
        {
            // left align
            put(w, s);
            if (f.width > s.length)
                foreach (i ; 0 .. f.width - s.length) put(w, ' ');
        }
    }
    else
    {
        static if (is(typeof(str[0]) : const(char)))
        {
            formatRange(w, str, f);
        }
        else static if (is(typeof(str[0]) : const(wchar)))
        {
            formatRange(w, str, f);
        }
        else static if (is(typeof(str[0]) : const(dchar)))
        {
            formatRange(w, str, f);
        }
    }
}

unittest
{
    FormatSpec!char f;
    auto w = appender!(string)();
    string s = "abc";
    formatValue(w, s, f);
    assert(w.data == "abc");
}

unittest
{
    // 5371
    class C1
    {
        const(string) var = "C1";
        alias var this;
    }
    class C2
    {
        string var = "C2";
        alias var this;
    }
    auto c1 = new C1();
    auto c2 = new C2();

    FormatSpec!char f;
    auto a = appender!string();
    formatValue(a, c1, f);
    formatValue(a, c2, f);
}

/**
   Input ranges are formatted like arrays.
 */
void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (isInputRange!T && !isSomeString!T)
{
    static if (is(T == class) || is(T == interface) || isPointer!T)
    {
        if (val is null)
        {
            put(w, "null");
            return;
        }
    }

    static if (isSomeChar!(ElementType!T))
    if (f.spec == 's')
    {
        if (!f.flDash)
        {
            static if (hasLength!T)
            {
                // right align
                auto len = val.length;
            }
            else static if (isForwardRange!T)
            {
                auto len = walkLength(val.save);
            }
            else
            {
                enforce(f.width == 0, "Cannot right-align a range without length");
                size_t len = 0;
            }
            if (f.width > len)
                foreach (i ; 0 .. f.width - len) put(w, ' ');
            for (; !val.empty; val.popFront())
            {
                put(w, val.front);
            }
        }
        else
        {
            // left align
            size_t printed = 0;
            for (; !val.empty; val.popFront(), ++printed)
            {
                put(w, val.front);
            }
            if (f.width > printed)
                foreach (i ; 0 .. f.width - printed) put(w, ' ');
        }
        return;
    }

    formatRange(w, val, f);
}

unittest
{
    // 6640
    struct Range
    {
        string value;
        const @property bool empty(){ return !value.length; }
        const @property dchar front(){ return value.front(); }
        void popFront(){ value.popFront(); }

        const @property size_t length(){ return value.length; }
    }
    auto s = "string";
    auto r = Range("string");

    immutable table =
    [
        ["[%s]", "[string]"],
        ["[%10s]", "[    string]"],
        ["[%-10s]", "[string    ]"],
        ["[%(%02x %)]", "[73 74 72 69 6e 67]"],
        ["[%(%s %)]", "[s t r i n g]"],
    ];
    foreach (e; table)
    {
        auto w1 = appender!string();
        auto w2 = appender!string();
        formattedWrite(w1, e[0], s);
        formattedWrite(w2, e[0], r);
        assert(w1.data == w2.data);
        assert(w1.data == e[1]);
    }
}

private void formatRange(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (isInputRange!T)
{
    auto arr = val;
    if (f.spec == 'r')
    {
        // raw writes
        for (size_t i; !arr.empty; arr.popFront(), ++i)
        {
            if (f.spec == '(')
            {
                // It's a nested format specifier
                formattedWrite(w, f.nested, arr.front);
            }
            else
            {
                formatValue(w, arr.front, f);
            }
        }
    }
    else
    {
        // formatted writes
        if (!f.nested)
        {
            put(w, f.seqBefore);
            scope(exit) put(w, f.seqAfter);
            if (!arr.empty)
            {
                formatElement(w, arr.front, f);
                arr.popFront();
                for (size_t i; !arr.empty; arr.popFront(), ++i)
                {
                    put(w, f.seqSeparator);
                    formatElement(w, arr.front, f);
                }
            }
        }
        else
        {
            if (arr.empty)
                return;
            // Nested specifier is to be used
            for (;;)
            {
                auto fmt = FormatSpec!Char(f.nested);
                fmt.writeUpToNextSpec(w);
                auto spec = fmt.spec;
                formatValue(w, arr.front, fmt);
                arr.popFront();
                if (spec == '(')
                {   // If element is range
                    fmt.writeUpToNextSpec(w);   // always put trailing
                    if (arr.empty) break;
                }
                else
                {
                    if (arr.empty) break;
                    fmt.writeUpToNextSpec(w);
                }
            }
        }
    }
}

private void formatChar(Writer)(Writer w, dchar c)
{
    if (std.uni.isGraphical(c))
    {
        if (c == '\"' || c == '\\')
            put(w, '\\'), put(w, c);
        else
            put(w, c);
    }
    else if (c <= 0xFF)
    {
        put(w, '\\');
        switch (c)
        {
        case '\a':  put(w, 'a');  break;
        case '\b':  put(w, 'b');  break;
        case '\f':  put(w, 'f');  break;
        case '\n':  put(w, 'n');  break;
        case '\r':  put(w, 'r');  break;
        case '\t':  put(w, 't');  break;
        case '\v':  put(w, 'v');  break;
        default:
            formattedWrite(w, "x%02X", cast(uint)c);
        }
    }
    else if (c <= 0xFFFF)
        formattedWrite(w, "\\u%04X", cast(uint)c);
    else
        formattedWrite(w, "\\U%08X", cast(uint)c);
}

// undocumented
// string element is formatted like UTF-8 string literal.
void formatElement(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (isSomeString!T)
{
    if (f.spec == 's')
    {
        bool invalidSeq = false;
        try
        {
            // ignore other specifications and quote
            auto app = appender!(typeof(T[0])[])();

            put(app, '\"');
            for (size_t i = 0; i < val.length; )
            {
                auto c = std.utf.decode(val, i);
                // \uFFFE and \uFFFF are considered valid by isValidDchar,
                // so need checking for interchange.
                if (c == 0xFFFE || c == 0xFFFF)
                {
                    invalidSeq = true;
                    goto LinvalidSeq;
                }
                formatChar(app, c);
            }
            put(app, '\"');

            put(w, app.data());
        }
        catch (UTFException)
        {
            // If val contains invalid UTF sequence, formatted like HexString literal
            invalidSeq = true;
        }

    LinvalidSeq:
        if (invalidSeq)
        {
            static if (is(typeof(val[0]) : const(char)))
            {
                enum postfix = 'c';
                alias const(ubyte)[] IntArr;
            }
            else static if (is(typeof(val[0]) : const(wchar)))
            {
                enum postfix = 'w';
                alias const(ushort)[] IntArr;
            }
            else static if (is(typeof(val[0]) : const(dchar)))
            {
                enum postfix = 'd';
                alias const(uint)[] IntArr;
            }

            formattedWrite(w, "x\"%(%02X %)\"%s", cast(IntArr)val, postfix);
        }
    }
    else
        formatValue(w, val, f);
}

// undocumented
// character element is formatted like UTF-8 character literal.
void formatElement(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (isSomeChar!T)
{
    put(w, '\'');
    formatChar(w, val);
    put(w, '\'');
}

// undocumented
void formatElement(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (!isSomeString!T && !isSomeChar!T)
{
    formatValue(w, val, f);
}

unittest
{
    FormatSpec!char f;
    auto w = appender!(char[])();

    // string literal from valid UTF sequence is encoding free.
    foreach (StrType; TypeTuple!(string, wstring, dstring))
    {
        // Valid and printable (ASCII)
        w.clear();
        formatValue(w, [cast(StrType)"hello"], f);
        assert(w.data == `["hello"]`);

        // 1 character escape sequences
        w.clear();
        formatValue(w, [cast(StrType)"\"\\\a\b\f\n\r\t\v"], f);
        assert(w.data == `["\"\\\a\b\f\n\r\t\v"]`);

        // Valid and non-printable code point (<= U+FF)
        w.clear();
        formatValue(w, [cast(StrType)"\x00\x10\x1F\x20test"], f);
        assert(w.data == `["\x00\x10\x1F test"]`);

        // Valid and non-printable code point (<= U+FFFF)
        w.clear();
        formatValue(w, [cast(StrType)"\u200B..\u200F"], f);
        assert(w.data == `["\u200B..\u200F"]`);

        // Valid and non-printable code point (<= U+10FFFF)
        w.clear();
        formatValue(w, [cast(StrType)"\U000E0020..\U000E007F"], f);
        assert(w.data == `["\U000E0020..\U000E007F"]`);
    }

    // invalid UTF sequence needs hex-string literal postfix (c/w/d)
    {
        // U+FFFF with UTF-8 (Invalid code point for interchange)
        w.clear();
        formatValue(w, [cast(string)[0xEF, 0xBF, 0xBF]], f);
        assert(w.data == `[x"EF BF BF"c]`);

        // U+FFFF with UTF-16 (Invalid code point for interchange)
        w.clear();
        formatValue(w, [cast(wstring)[0xFFFF]], f);
        assert(w.data == `[x"FFFF"w]`);

        // U+FFFF with UTF-32 (Invalid code point for interchange)
        w.clear();
        formatValue(w, [cast(dstring)[0xFFFF]], f);
        assert(w.data == `[x"FFFF"d]`);
    }
}

void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (!isInputRange!T && isDynamicArray!T && !isSomeString!T &&
    !is(const(T) == const(void[])))
{
    alias Unqual!T U;
    static assert(isInputRange!U);
    U unq = val;
    formatValue(w, unq, f);
}

unittest
{
    FormatSpec!char f;
    auto w = appender!string();
    const short[] a = [1, 2, 3];
    formatValue(w, a, f);
    assert(w.data == "[1, 2, 3]");
}

unittest
{
    FormatSpec!char f;
    auto w = appender!(char[])();

    // class range (issue 5154)
    auto c = inputRangeObject([1,2,3,4]);
    w.clear();
    formatValue(w, c, f);
    assert(w.data == "[1, 2, 3, 4]");
    assert(c.empty);

    // interface
    InputRange!int i = inputRangeObject([1,2,3,4]);
    w.clear();
    formatValue(w, i, f);
    assert(w.data == "[1, 2, 3, 4]");
    assert(i.empty);

    // pointer
    auto r = retro([1,2,3,4]);
    w.clear();
    formatValue(w, &r, f);
    assert(w.data == "[4, 3, 2, 1]");
    assert(r.empty);

    // null
    c = null;
    w.clear();
    formatValue(w, c, f);
    assert(w.data == "null");
    assert(r.empty);
}

unittest
{
    FormatSpec!char f;
    auto w = appender!(char[])();

    auto a = ["test", "msg"];
    w.clear();
    formattedWrite(w, "%({%(%02x %)} %)", a);
    assert(w.data == `{74 65 73 74} {6d 73 67} `);
}

/**
   Pointers are formatted as hex integers.
 */
void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (isPointer!T && !isInputRange!T)
{
    const void * p = val;
    if (f.spec == 's')
    {
        FormatSpec!Char fs = f; // fs is copy for change its values.
        fs.spec = 'X';
        formatValue(w, cast(ulong) p, fs);
    }
    else
    {
        enforce(f.spec == 'X' || f.spec == 'x');
        formatValue(w, cast(ulong) p, f);
    }
}

unittest
{
    FormatSpec!char f;
    auto a = appender!string();

    struct S{ void* p; string s; }
    auto s = S(cast(void*)0xFFEECCAA, "hello");
    formatValue(a, s, f);
    assert(a.data == `S(FFEECCAA, "hello")`);
}

/**
   Objects are formatted by calling $(D toString).
   Interfaces are formatted by casting to $(D Object) and then calling
   $(D toString).
 */
void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (!isSomeString!T && is(T == class) && !isInputRange!T)
{
    // TODO: Change this once toString() works for shared objects.
    static assert(!is(T == shared), "unable to format shared objects");
    if (val is null) put(w, "null");
    else put(w, val.toString());
}

/// ditto
void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (is(T == interface) && !isInputRange!T)
{
    return formatValue(w, cast(Object)val, f);
}

unittest
{
    FormatSpec!char f;
    auto a = appender!string();
    interface Whatever {};
    class C : Whatever
    {
        override @property string toString() { return "ab"; }
    }
    Whatever val = new C;
    formatValue(a, val, f);
    assert(a.data == "ab");
}

/**
   Associative arrays are formatted by using $(D ':') and $(D ', ') as
   separators, and enclosed by $(D '[') and $(D ']').
 */
void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (isAssociativeArray!T && !is(T == enum))
{
    put(w, f.seqBefore);
    bool first = true;
    foreach (k, ref v; val) {
        if (first) first = false;
        else put(w, f.seqSeparator);
        formatElement(w, k, f);
        put(w, f.keySeparator);
        formatElement(w, v, f);
    }
    put(w, f.seqAfter);
}

unittest
{
    FormatSpec!char f;
    auto a = appender!string();
    int[string] aa;
    formatValue(a, aa, f);
    assert(a.data == `[]`);
}

unittest
{
    FormatSpec!char f;
    auto a = appender!string();
    int[string] aa = ["aaa": 1, "bbb": 2, "ccc": 3];
    formatValue(a, aa, f);
    assert(a.data == `["aaa":1, "bbb":2, "ccc":3]`);
}

unittest
{
    FormatSpec!char f;
    auto a = appender!string();
    formatValue(a, ['c':"str"], f);
    assert(a.data == `['c':"str"]`);
}

/**
   Structs and unions are formatted using by calling $(D toString) member
   function of the object. $(D toString) should have one of the following
   signatures:

---
const void toString(scope void delegate(const(char)[]) sink, FormatSpec fmt);
const void toString(scope void delegate(const(char)[]) sink, string fmt);
const string toString();
---

 */
void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if ((is(T == struct) || is(T == union)) && !isInputRange!T)
{
    static if (is(typeof(val.toString((const(char)[] s){}, f))))
    {   // Support toString( delegate(const(char)[]) sink, FormatSpec)
        val.toString((const(char)[] s) { put(w, s); }, f);
    }
    else static if (is(typeof(val.toString((const(char)[] s){}, "%s"))))
    {   // Support toString( delegate(const(char)[]) sink, string fmt)
        val.toString((const(char)[] s) { put(w, s); }, f.getCurFmtStr());
    }
    else static if (is(typeof(val.toString()) S) && isSomeString!S)
    {
        put(w, val.toString());
    }
    else static if (is(T == struct))
    {
        enum left = T.stringof~"(";
        enum separator = ", ";
        enum right = ")";

        put(w, left);
        foreach (i, e; val.tupleof)
        {
            static if (i > 0)
                put(w, separator);
            formatElement(w, e, f);
        }
        put(w, right);
    }
    else
    {
        put(w, T.stringof);
    }
}

unittest
{
    // bug 4638
    struct U8  {  string toString() { return "blah"; } }
    struct U16 { wstring toString() { return "blah"; } }
    struct U32 { dstring toString() { return "blah"; } }

    FormatSpec!char f;
    auto w = appender!string();

    formatValue(w, U8(), f);
    formatValue(w, U16(), f);
    formatValue(w, U32(), f);
    assert(w.data() == "blahblahblah");
}

unittest
{
    // 3890
    struct Int{ int n; }
    struct Pair{ string s; Int i; }

    FormatSpec!char f;
    auto w = appender!string();

    formatValue(w, Pair("hello", Int(5)), f);
    assert(w.data() == `Pair("hello", Int(5))`);
}

unittest
{
    FormatSpec!char f;
    auto a = appender!(char[])();

    union U1
    {
        int n;
        string s;
    }
    U1 u1;
    formatValue(a, u1, f);
    assert(a.data == "U1");

    a.clear();

    union U2
    {
        int n;
        string s;
        string toString(){ return s; }
    }
    U2 u2;
    u2.s = "hello";
    formatValue(a, u2, f);
    assert(a.data == "hello");
}

/**
   Static-size arrays are formatted just like arrays.
 */
void formatValue(Writer, T, Char)(Writer w, ref T val, ref FormatSpec!Char f)
if (isStaticArray!T)
{
    formatValue(w, val[], f);
}

/*
   Formatting a $(D creal) is deprecated but still kept around for a while.
 */
void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (is(T : creal))
{
    formatValue(w, val.re, f);
    put(w, '+');
    formatValue(w, val.im, f);
    put(w, 'i');
}

unittest
{
    FormatSpec!char f;
    auto a = appender!string();
    creal val = 1 + 1i;
    formatValue(a, val, f);
}

/*
   Formatting an $(D ireal) is deprecated but still kept around for a while.
 */
void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (is(T : ireal))
{
    formatValue(w, val.im, f);
    put(w, 'i');
}

unittest
{
    FormatSpec!char f;
    auto a = appender!string();
    ireal val = 1i;
    formatValue(a, val, f);
}


/**
   Delegates are formatted by 'Attributes ReturnType delegate(Parameters)'
 */
void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (is(T == delegate))
{
    alias FunctionAttribute FA;
    if (functionAttributes!T & FA.pure_)    formatValue(w, "pure ", f);
    if (functionAttributes!T & FA.nothrow_) formatValue(w, "nothrow ", f);
    if (functionAttributes!T & FA.ref_)     formatValue(w, "ref ", f);
    if (functionAttributes!T & FA.property) formatValue(w, "@property ", f);
    if (functionAttributes!T & FA.trusted)  formatValue(w, "@trusted ", f);
    if (functionAttributes!T & FA.safe)     formatValue(w, "@safe ", f);
    formatValue(w, ReturnType!(T).stringof,f);
    formatValue(w, " delegate",f);
    formatValue(w, ParameterTypeTuple!(T).stringof,f);
}

unittest
{
    FormatSpec!char f;
    auto a = appender!string();
    formatValue(a, {}, f);
}

/*
   Formatting a $(D typedef) is deprecated but still kept around for a while.
 */
void formatValue(Writer, T, Char)(Writer w, T val, ref FormatSpec!Char f)
if (is(T == typedef))
{
    static if (is(T U == typedef)) {
        formatValue(w, cast(U) val, f);
    }
}

unittest
{
    FormatSpec!char f;
    auto a = appender!string();
    ireal val = 1i;
    formatValue(a, val, f);
}

/*
  Formats an object of type 'D' according to 'f' and writes it to
  'w'. The pointer 'arg' is assumed to point to an object of type
  'D'. The untyped signature is for the sake of taking this function's
  address.
 */
private void formatGeneric(Writer, D, Char)(Writer w, const(void)* arg, ref FormatSpec!Char f)
{
    formatValue(w, *cast(D*) arg, f);
}

unittest
{
    auto w = appender!(char[])();
    int[] a = [ 1, 3, 2 ];
    formattedWrite(w, "testing %(%s & %) embedded", a);
    assert(w.data == "testing 1 & 3 & 2 embedded", w.data);
    w.clear();
    formattedWrite(w, "testing %((%s) %)) wyda3", a);
    assert(w.data == "testing (1) (3) (2) wyda3", w.data);

    int[0] empt = [];
    w.clear();
    formattedWrite(w, "(%s)", empt);
    assert(w.data == "([])", w.data);
}

//------------------------------------------------------------------------------
// Fix for issue 1591
private int getNthInt(A...)(uint index, A args)
{
    static if (A.length)
    {
        if (index)
        {
            return getNthInt(index - 1, args[1 .. $]);
        }
        static if (is(typeof(args[0]) : long) || is(typeof(arg) : ulong))
        {
            return to!(int)(args[0]);
        }
        else
        {
            throw new FormatException("int expected");
        }
    }
    else
    {
        throw new FormatException("int expected");
    }
}

/* ======================== Unit Tests ====================================== */

unittest
{
    auto stream = appender!string();
    formattedWrite(stream, "%s", 1.1);
    assert(stream.data == "1.1", stream.data);

    stream = appender!string();
    formattedWrite(stream, "%s", map!"a*a"([2, 3, 5]));
    assert(stream.data == "[4, 9, 25]", stream.data);

    // Test shared data.
    stream = appender!string();
    shared int s = 6;
    formattedWrite(stream, "%s", s);
    assert(stream.data == "6");
}

unittest
{
    auto stream = appender!string();
    formattedWrite(stream, "%u", 42);
    assert(stream.data == "42", stream.data);
}

unittest
{
    // testing raw writes
    auto w = appender!(char[])();
    uint a = 0x02030405;
    formattedWrite(w, "%+r", a);
    assert(w.data.length == 4 && w.data[0] == 2 && w.data[1] == 3
        && w.data[2] == 4 && w.data[3] == 5);
    w.clear();
    formattedWrite(w, "%-r", a);
    assert(w.data.length == 4 && w.data[0] == 5 && w.data[1] == 4
        && w.data[2] == 3 && w.data[3] == 2);
}

unittest
{
    // testing positional parameters
    auto w = appender!(char[])();
    formattedWrite(w,
            "Numbers %2$s and %1$s are reversed and %1$s%2$s repeated",
            42, 0);
    assert(w.data == "Numbers 0 and 42 are reversed and 420 repeated",
            w.data);
    w.clear();
    formattedWrite(w, "asd%s", 23);
    assert(w.data == "asd23", w.data);
    w.clear();
    formattedWrite(w, "%s%s", 23, 45);
    assert(w.data == "2345", w.data);
}

unittest
{
    debug(format) printf("std.format.format.unittest\n");

    auto stream = appender!(char[])();
    //goto here;

    formattedWrite(stream,
            "hello world! %s %s ", true, 57, 1_000_000_000, 'x', " foo");
    assert(stream.data == "hello world! true 57 ",
        stream.data);

    stream.clear();
    formattedWrite(stream, "%g %A %s", 1.67, -1.28, float.nan);
    // std.c.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr);

    /* The host C library is used to format floats.  C99 doesn't
    * specify what the hex digit before the decimal point is for
    * %A.  */

    version (linux)
    {
        assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan",
                stream.data);
    }
    else version (OSX)
    {
        assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan",
                stream.data);
    }
    else
    {
        assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan",
                stream.data);
    }
    stream.clear();

    formattedWrite(stream, "%x %X", 0x1234AF, 0xAFAFAFAF);
    assert(stream.data == "1234af AFAFAFAF");
    stream.clear();

    formattedWrite(stream, "%b %o", 0x1234AF, 0xAFAFAFAF);
    assert(stream.data == "100100011010010101111 25753727657");
    stream.clear();

    formattedWrite(stream, "%d %s", 0x1234AF, 0xAFAFAFAF);
    assert(stream.data == "1193135 2947526575");
    stream.clear();

    // formattedWrite(stream, "%s", 1.2 + 3.4i);
    // assert(stream.data == "1.2+3.4i");
    // stream.clear();

    formattedWrite(stream, "%a %A", 1.32, 6.78f);
    //formattedWrite(stream, "%x %X", 1.32);
    assert(stream.data == "0x1.51eb851eb851fp+0 0X1.B1EB86P+2");
    stream.clear();

    formattedWrite(stream, "%#06.*f",2,12.345);
    assert(stream.data == "012.35");
    stream.clear();

    formattedWrite(stream, "%#0*.*f",6,2,12.345);
    assert(stream.data == "012.35");
    stream.clear();

    const real constreal = 1;
    formattedWrite(stream, "%g",constreal);
    assert(stream.data == "1");
    stream.clear();

    formattedWrite(stream, "%7.4g:", 12.678);
    assert(stream.data == "  12.68:");
    stream.clear();

    formattedWrite(stream, "%7.4g:", 12.678L);
    assert(stream.data == "  12.68:");
    stream.clear();

    formattedWrite(stream, "%04f|%05d|%#05x|%#5x",-4.,-10,1,1);
    assert(stream.data == "-4.000000|-0010|0x001|  0x1",
            stream.data);
    stream.clear();

    int i;
    string s;

    i = -10;
    formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
    assert(stream.data == "-10|-10|-10|-10|-10.0000");
    stream.clear();

    i = -5;
    formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
    assert(stream.data == "-5| -5|-05|-5|-5.0000");
    stream.clear();

    i = 0;
    formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
    assert(stream.data == "0|  0|000|0|0.0000");
    stream.clear();

    i = 5;
    formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
    assert(stream.data == "5|  5|005|5|5.0000");
    stream.clear();

    i = 10;
    formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
    assert(stream.data == "10| 10|010|10|10.0000");
    stream.clear();

    formattedWrite(stream, "%.0d", 0);
    assert(stream.data == "");
    stream.clear();

    formattedWrite(stream, "%.g", .34);
    assert(stream.data == "0.3");
    stream.clear();

    stream.clear(); formattedWrite(stream, "%.0g", .34);
    assert(stream.data == "0.3");

    stream.clear(); formattedWrite(stream, "%.2g", .34);
    assert(stream.data == "0.34");

    stream.clear(); formattedWrite(stream, "%0.0008f", 1e-08);
    assert(stream.data == "0.00000001");

    stream.clear(); formattedWrite(stream, "%0.0008f", 1e-05);
    assert(stream.data == "0.00001000");

    //return;
    //std.c.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr);

    s = "helloworld";
    string r;
    stream.clear(); formattedWrite(stream, "%.2s", s[0..5]);
    assert(stream.data == "he");
    stream.clear(); formattedWrite(stream, "%.20s", s[0..5]);
    assert(stream.data == "hello");
    stream.clear(); formattedWrite(stream, "%8s", s[0..5]);
    assert(stream.data == "   hello");

    byte[] arrbyte = new byte[4];
    arrbyte[0] = 100;
    arrbyte[1] = -99;
    arrbyte[3] = 0;
    stream.clear(); formattedWrite(stream, "%s", arrbyte);
    assert(stream.data == "[100, -99, 0, 0]", stream.data);

    ubyte[] arrubyte = new ubyte[4];
    arrubyte[0] = 100;
    arrubyte[1] = 200;
    arrubyte[3] = 0;
    stream.clear(); formattedWrite(stream, "%s", arrubyte);
    assert(stream.data == "[100, 200, 0, 0]", stream.data);

    short[] arrshort = new short[4];
    arrshort[0] = 100;
    arrshort[1] = -999;
    arrshort[3] = 0;
    stream.clear(); formattedWrite(stream, "%s", arrshort);
    assert(stream.data == "[100, -999, 0, 0]");
    stream.clear(); formattedWrite(stream, "%s",arrshort);
    assert(stream.data == "[100, -999, 0, 0]");

    ushort[] arrushort = new ushort[4];
    arrushort[0] = 100;
    arrushort[1] = 20_000;
    arrushort[3] = 0;
    stream.clear(); formattedWrite(stream, "%s", arrushort);
    assert(stream.data == "[100, 20000, 0, 0]");

    int[] arrint = new int[4];
    arrint[0] = 100;
    arrint[1] = -999;
    arrint[3] = 0;
    stream.clear(); formattedWrite(stream, "%s", arrint);
    assert(stream.data == "[100, -999, 0, 0]");
    stream.clear(); formattedWrite(stream, "%s",arrint);
    assert(stream.data == "[100, -999, 0, 0]");

    long[] arrlong = new long[4];
    arrlong[0] = 100;
    arrlong[1] = -999;
    arrlong[3] = 0;
    stream.clear(); formattedWrite(stream, "%s", arrlong);
    assert(stream.data == "[100, -999, 0, 0]");
    stream.clear(); formattedWrite(stream, "%s",arrlong);
    assert(stream.data == "[100, -999, 0, 0]");

    ulong[] arrulong = new ulong[4];
    arrulong[0] = 100;
    arrulong[1] = 999;
    arrulong[3] = 0;
    stream.clear(); formattedWrite(stream, "%s", arrulong);
    assert(stream.data == "[100, 999, 0, 0]");

    string[] arr2 = new string[4];
    arr2[0] = "hello";
    arr2[1] = "world";
    arr2[3] = "foo";
    stream.clear(); formattedWrite(stream, "%s", arr2);
    assert(stream.data == `["hello", "world", "", "foo"]`, stream.data);

    stream.clear(); formattedWrite(stream, "%.8d", 7);
    assert(stream.data == "00000007");

    stream.clear(); formattedWrite(stream, "%.8x", 10);
    assert(stream.data == "0000000a");

    stream.clear(); formattedWrite(stream, "%-3d", 7);
    assert(stream.data == "7  ");

    stream.clear(); formattedWrite(stream, "%*d", -3, 7);
    assert(stream.data == "7  ");

    stream.clear(); formattedWrite(stream, "%.*d", -3, 7);
    //writeln(stream.data);
    assert(stream.data == "7");

//  assert(false);
//   typedef int myint;
//   myint m = -7;
//   stream.clear(); formattedWrite(stream, "", m);
//   assert(stream.data == "-7");

    stream.clear(); formattedWrite(stream, "%s", "abc"c);
    assert(stream.data == "abc");
    stream.clear(); formattedWrite(stream, "%s", "def"w);
    assert(stream.data == "def", text(stream.data.length));
    stream.clear(); formattedWrite(stream, "%s", "ghi"d);
    assert(stream.data == "ghi");

here:
    void* p = cast(void*)0xDEADBEEF;
    stream.clear(); formattedWrite(stream, "%s", p);
    assert(stream.data == "DEADBEEF", stream.data);

    stream.clear(); formattedWrite(stream, "%#x", 0xabcd);
    assert(stream.data == "0xabcd");
    stream.clear(); formattedWrite(stream, "%#X", 0xABCD);
    assert(stream.data == "0XABCD");

    stream.clear(); formattedWrite(stream, "%#o", octal!12345);
    assert(stream.data == "012345");
    stream.clear(); formattedWrite(stream, "%o", 9);
    assert(stream.data == "11");

    stream.clear(); formattedWrite(stream, "%+d", 123);
    assert(stream.data == "+123");
    stream.clear(); formattedWrite(stream, "%+d", -123);
    assert(stream.data == "-123");
    stream.clear(); formattedWrite(stream, "% d", 123);
    assert(stream.data == " 123");
    stream.clear(); formattedWrite(stream, "% d", -123);
    assert(stream.data == "-123");

    stream.clear(); formattedWrite(stream, "%%");
    assert(stream.data == "%");

    stream.clear(); formattedWrite(stream, "%d", true);
    assert(stream.data == "1");
    stream.clear(); formattedWrite(stream, "%d", false);
    assert(stream.data == "0");

    stream.clear(); formattedWrite(stream, "%d", 'a');
    assert(stream.data == "97", stream.data);
    wchar wc = 'a';
    stream.clear(); formattedWrite(stream, "%d", wc);
    assert(stream.data == "97");
    dchar dc = 'a';
    stream.clear(); formattedWrite(stream, "%d", dc);
    assert(stream.data == "97");

    byte b = byte.max;
    stream.clear(); formattedWrite(stream, "%x", b);
    assert(stream.data == "7f");
    stream.clear(); formattedWrite(stream, "%x", ++b);
    assert(stream.data == "80");
    stream.clear(); formattedWrite(stream, "%x", ++b);
    assert(stream.data == "81");

    short sh = short.max;
    stream.clear(); formattedWrite(stream, "%x", sh);
    assert(stream.data == "7fff");
    stream.clear(); formattedWrite(stream, "%x", ++sh);
    assert(stream.data == "8000");
    stream.clear(); formattedWrite(stream, "%x", ++sh);
    assert(stream.data == "8001");

    i = int.max;
    stream.clear(); formattedWrite(stream, "%x", i);
    assert(stream.data == "7fffffff");
    stream.clear(); formattedWrite(stream, "%x", ++i);
    assert(stream.data == "80000000");
    stream.clear(); formattedWrite(stream, "%x", ++i);
    assert(stream.data == "80000001");

    stream.clear(); formattedWrite(stream, "%x", 10);
    assert(stream.data == "a");
    stream.clear(); formattedWrite(stream, "%X", 10);
    assert(stream.data == "A");
    stream.clear(); formattedWrite(stream, "%x", 15);
    assert(stream.data == "f");
    stream.clear(); formattedWrite(stream, "%X", 15);
    assert(stream.data == "F");

    Object c = null;
    stream.clear(); formattedWrite(stream, "%s", c);
    assert(stream.data == "null");

    enum TestEnum
    {
        Value1, Value2
    }
    stream.clear(); formattedWrite(stream, "%s", TestEnum.Value2);
    assert(stream.data == "Value2", stream.data);
    stream.clear(); formattedWrite(stream, "%s", cast(TestEnum)5);
    assert(stream.data == "cast(TestEnum)5", stream.data);

    //immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]);
    //stream.clear(); formattedWrite(stream, "%s", aa.values);
    //std.c.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr);
    //assert(stream.data == "[[h,e,l,l,o],[b,e,t,t,y]]");
    //stream.clear(); formattedWrite(stream, "%s", aa);
    //assert(stream.data == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]");

    static const dchar[] ds = ['a','b'];
    for (int j = 0; j < ds.length; ++j)
    {
        stream.clear(); formattedWrite(stream, " %d", ds[j]);
        if (j == 0)
            assert(stream.data == " 97");
        else
            assert(stream.data == " 98");
    }

    stream.clear(); formattedWrite(stream, "%.-3d", 7);
    assert(stream.data == "7", ">" ~ stream.data ~ "<");


    // systematic test
    const string[] flags = [ "-", "+", "#", "0", " ", "" ];
    const string[] widths = [ "", "0", "4", "20" ];
    const string[] precs = [ "", ".", ".0", ".4", ".20" ];
    const string formats = "sdoxXeEfFgGaA";
  /+
  foreach (flag1; flags)
      foreach (flag2; flags)
          foreach (flag3; flags)
              foreach (flag4; flags)
                  foreach (flag5; flags)
                      foreach (width; widths)
                          foreach (prec; precs)
                              foreach (format; formats)
                              {
                                  stream.clear();
                                  auto fmt = "%" ~ flag1 ~ flag2  ~ flag3
                                      ~ flag4 ~ flag5 ~ width ~ prec ~ format
                                      ~ '\0';
                                  fmt = fmt[0 .. $ - 1]; // keep it zero-term
                                  char buf[256];
                                  buf[0] = 0;
                                  switch (format)
                                  {
                                  case 's':
                                      formattedWrite(stream, fmt, "wyda");
                                      snprintf(buf.ptr, buf.length, fmt.ptr,
                                          "wyda\0".ptr);
                                      break;
                                  case 'd':
                                      formattedWrite(stream, fmt, 456);
                                      snprintf(buf.ptr, buf.length, fmt.ptr,
                                               456);
                                      break;
                                  case 'o':
                                      formattedWrite(stream, fmt, 345);
                                      snprintf(buf.ptr, buf.length, fmt.ptr,
                                               345);
                                      break;
                                  case 'x':
                                      formattedWrite(stream, fmt, 63546);
                                      snprintf(buf.ptr, buf.length, fmt.ptr,
                                          63546);
                                      break;
                                  case 'X':
                                      formattedWrite(stream, fmt, 12566);
                                      snprintf(buf.ptr, buf.length, fmt.ptr,
                                          12566);
                                      break;
                                  case 'e':
                                      formattedWrite(stream, fmt, 3245.345234);
                                      snprintf(buf.ptr, buf.length, fmt.ptr,
                                          3245.345234);
                                      break;
                                  case 'E':
                                      formattedWrite(stream, fmt, 3245.2345234);
                                      snprintf(buf.ptr, buf.length, fmt.ptr,
                                          3245.2345234);
                                      break;
                                  case 'f':
                                      formattedWrite(stream, fmt, 3245234.645675);
                                      snprintf(buf.ptr, buf.length, fmt.ptr,
                                          3245234.645675);
                                      break;
                                  case 'F':
                                      formattedWrite(stream, fmt, 213412.43);
                                      snprintf(buf.ptr, buf.length, fmt.ptr,
                                          213412.43);
                                      break;
                                  case 'g':
                                      formattedWrite(stream, fmt, 234134.34);
                                      snprintf(buf.ptr, buf.length, fmt.ptr,
                                          234134.34);
                                      break;
                                  case 'G':
                                      formattedWrite(stream, fmt, 23141234.4321);
                                      snprintf(buf.ptr, buf.length, fmt.ptr,
                                               23141234.4321);
                                      break;
                                  case 'a':
                                      formattedWrite(stream, fmt, 21341234.2134123);
                                      snprintf(buf.ptr, buf.length, fmt.ptr,
                                               21341234.2134123);
                                      break;
                                  case 'A':
                                      formattedWrite(stream, fmt, 1092384098.45234);
                                      snprintf(buf.ptr, buf.length, fmt.ptr,
                                               1092384098.45234);
                                      break;
                                  default:
                                      break;
                                  }
                                  auto exp = buf[0 .. strlen(buf.ptr)];
                                  if (stream.data != exp)
                                  {
                                      writeln("Format: \"", fmt, '"');
                                      writeln("Expected: >", exp, "<");
                                      writeln("Actual:   >", stream.data,
                                              "<");
                                      assert(false);
                                  }
                              }+/
}

unittest
{
    immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]);
    if (false) writeln(aa.keys);
    assert(aa[3] == "hello");
    assert(aa[4] == "betty");
    // if (false)
    // {
    //     writeln(aa.values[0]);
    //     writeln(aa.values[1]);
    //     writefln("%s", typeid(typeof(aa.values)));
    //     writefln("%s", aa[3]);
    //     writefln("%s", aa[4]);
    //     writefln("%s", aa.values);
    //     //writefln("%s", aa);
    //     wstring a = "abcd";
    //     writefln(a);
    //     dstring b = "abcd";
    //     writefln(b);
    // }

    auto stream = appender!(char[])();
    alias TypeTuple!(byte, ubyte, short, ushort, int, uint, long, ulong,
            float, double, real) AllNumerics;
    foreach (T; AllNumerics)
    {
        T value = 1;
        stream.clear();
        formattedWrite(stream, "%s", value);
        assert(stream.data == "1");
    }

    //auto r = std.string.format("%s", aa.values);
    stream.clear(); formattedWrite(stream, "%s", aa);
    //assert(stream.data == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]", stream.data);
//    r = std.string.format("%s", aa);
//   assert(r == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]");
}

unittest
{
    string s = "hello!124:34.5";
    string a;
    int b;
    double c;
    formattedRead(s, "%s!%s:%s", &a, &b, &c);
    assert(a == "hello" && b == 124 && c == 34.5);
}

//------------------------------------------------------------------------------
private void skipData(Range, Char)(ref Range input, ref FormatSpec!Char spec)
{
    switch (spec.spec)
    {
        case 'c': input.popFront(); break;
        case 'd':
            if (input.front == '+' || input.front == '-') input.popFront();
            goto case 'u';
        case 'u':
            while (!input.empty && isDigit(input.front)) input.popFront();
            break;
        default:
            assert(false,
                    text("Format specifier not understood: %", spec.spec));
    }
}

private template acceptedSpecs(T)
{
    static if (isIntegral!T) enum acceptedSpecs = "sdu";// + "coxX" (todo)
    else static if (isFloatingPoint!T) enum acceptedSpecs = "seEfgG";
    else enum acceptedSpecs = "";
}

/**
   Reads an integral value and returns it.
 */
T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec)
    if (isInputRange!Range && isIntegral!T)
{
    enforce(std.algorithm.find("cdosuxX", spec.spec).length,
            text("Wrong integral type specifier: `", spec.spec, "'"));
    if (std.algorithm.find("dsu", spec.spec).length)
    {
        return parse!T(input);
    }
    assert(0, "Parsing spec '"~spec.spec~"' not implemented.");
}

/**
   Reads a floating-point value and returns it.
 */
T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec)
    if (isFloatingPoint!T)
{
    if (spec.spec == 'r')
    {
        // raw read
        //enforce(input.length >= T.sizeof);
        enforce(isSomeString!Range || ElementType!(Range).sizeof == 1);
        union X
        {
            ubyte[T.sizeof] raw;
            T typed;
        }
        X x;
        foreach (i; 0 .. T.sizeof)
        {
            static if (isSomeString!Range)
            {
                x.raw[i] = input[0];
                input = input[1 .. $];
            }
            else
            {
                // TODO: recheck this
                x.raw[i] = cast(ubyte) input.front;
                input.popFront();
            }
        }
        return x.typed;
    }
    enforce(std.algorithm.find(acceptedSpecs!T, spec.spec).length,
            text("Format specifier `%", spec.spec,
                    "' not accepted for floating point types"));
    return parse!T(input);
}

version(none)unittest
{
    union A
    {
        char[float.sizeof] untyped;
        float typed;
    };
    A a;
    a.typed = 5.5;
    char[] input = a.untyped[];
    float witness;
    formattedRead(input, "%r", &witness);
    assert(witness == a.typed);
}

unittest
{
    char[] line = "1 2".dup;
    int a, b;
    formattedRead(line, "%s %s", &a, &b);
    assert(a == 1 && b == 2);

    line = "10 2 3".dup;
    formattedRead(line, "%d ", &a);
    assert(a == 10);
    assert(line == "2 3");

    Tuple!(int, float) t;
    line = "1 2.125".dup;
    formattedRead(line, "%d %g", &t);
    assert(t[0] == 1 && t[1] == 2.125);

    line = "1 7643 2.125".dup;
    formattedRead(line, "%s %*u %s", &t);
    assert(t[0] == 1 && t[1] == 2.125);
}

/**
 * Reads a boolean value and returns it.
 */
T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec)
    if (isInputRange!Range && is(Unqual!T == bool))
{
    enforce(std.algorithm.find("cdosuxX", spec.spec).length,
            text("Wrong integral type specifier: `", spec.spec, "'"));
    if (spec.spec == 's')
    {
        return parse!T(input);
    }
    else if (spec.spec == 'd')
    {
        return parse!long(input) != 0;
    }
    assert(0, "Parsing spec '"~spec.spec~"' not implemented.");
}

unittest
{
    string line;

    bool f1;

    line = "true";
    formattedRead(line, "%s", &f1);
    assert(f1);

    line = "TrUE";
    formattedRead(line, "%s", &f1);
    assert(f1);

    line = "false";
    formattedRead(line, "%s", &f1);
    assert(!f1);

    line = "fALsE";
    formattedRead(line, "%s", &f1);
    assert(!f1);


    line = "1";
    formattedRead(line, "%d", &f1);
    assert(f1);

    line = "-1";
    formattedRead(line, "%d", &f1);
    assert(f1);

    line = "0";
    formattedRead(line, "%d", &f1);
    assert(!f1);

    line = "-0";
    formattedRead(line, "%d", &f1);
    assert(!f1);
}

/**
 * Reads one character and returns it.
 */
T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec)
    if (isInputRange!Range && isSomeChar!T)
{
    enforce(std.algorithm.find("cdosuxX", spec.spec).length,
            text("Wrong character type specifier: `", spec.spec, "'"));
    if (std.algorithm.find("sc", spec.spec).length)
    {
        auto result = to!T(input.front);
        input.popFront();
        return result;
    }
    assert(0, "Parsing spec '"~spec.spec~"' not implemented.");
}

unittest
{
    string line;

    char c1, c2;

    line = "abc";
    formattedRead(line, "%s%c", &c1, &c2);
    assert(c1 == 'a' && c2 == 'b');
    assert(line == "c");
}

/**
   Reads an array (except for string types) and returns it.
 */
T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec)
    if (isInputRange!Range && isArray!T && !isSomeString!T)
{
    if (spec.spec == 's')
    {
        return parse!T(input);
    }
    else if (spec.spec == '(')
    {
        return unformatRange!T(input, spec);
    }
    assert(0, "Parsing spec '"~spec.spec~"' not implemented.");
}

unittest
{
    string line;

    line = "[1,2,3]";
    int[] s1;
    formattedRead(line, "%s", &s1);
    assert(s1 == [1,2,3]);
}

unittest
{
    string line;

    line = "[1,2,3]";
    int[] s1;
    formattedRead(line, "[%(%s, %)]", &s1);
    assert(s1 == [1,2,3]);

    line = `["hello", "world"]`;
    string[] s2;
    formattedRead(line, "[%(%s, %)]", &s2);
    assert(s2 == ["hello", "world"]);

    line = "123 456";
    int[] s3;
    formattedRead(line, "%(%s %)", &s3);
    assert(s3 == [123, 456]);

    line = "h,e,l,l,o;w,o,r,l,d;";
    string[] s4;
    formattedRead(line, "%(%(%c,%);%)", &s4);
    assert(s4 == ["hello", "world"]);
}

unittest
{
    string line;

    int[4] sa1;
    line = `[1,2,3,4]`;
    formattedRead(line, "%s", &sa1);
    assert(sa1 == [1,2,3,4]);

    int[4] sa2;
    line = `[1,2,3]`;
    assertThrown(formattedRead(line, "%s", &sa2));

    int[4] sa3;
    line = `[1,2,3,4,5]`;
    assertThrown(formattedRead(line, "%s", &sa3));
}

/**
   Reads a string and returns it.
 */
T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec)
    if (isInputRange!Range && isSomeString!T)
{
    if (spec.spec == 's')
    {
        auto app = appender!T();
        if (spec.trailing.empty)
        {
            for (; !input.empty; input.popFront())
            {
                app.put(input.front);
            }
        }
        else
        {
            for (; !input.empty && input.front != spec.trailing.front;
                 input.popFront())
            {
                app.put(input.front);
            }
        }
        auto result = app.data;
        return result;
    }
    else if (spec.spec == '(')
    {
        return unformatRange!T(input, spec);
    }
    assert(0, "Parsing spec '"~spec.spec~"' not implemented.");
}

unittest
{
    string line;

    string s1, s2;

    line = "hello, world";
    formattedRead(line, "%s", &s1);
    assert(s1 == "hello, world", s1);

    line = "hello, world;yah";
    formattedRead(line, "%s;%s", &s1, &s2);
    assert(s1 == "hello, world", s1);
    assert(s2 == "yah", s2);

    line = `['h','e','l','l','o']`;
    string s3;
    formattedRead(line, "[%(%s, %)]", &s3);
    assert(s3 == "hello");

    line = `"hello"`;
    string s4;
    formattedRead(line, "\"%(%c%)\"", &s4);
    assert(s4 == "hello");
}

/**
 * Reads an associative array and returns it.
 */
T unformatValue(T, Range, Char)(ref Range input, ref FormatSpec!Char spec)
    if (isInputRange!Range && isAssociativeArray!T)
{
    if (spec.spec == 's')
    {
        return parse!T(input);
    }
    else if (spec.spec == '(')
    {
        return unformatRange!T(input, spec);
    }
    assert(0, "Parsing spec '"~spec.spec~"' not implemented.");
}

unittest
{
    string line;

    string[int] aa1;
    line = `[1:"hello", 2:"world"]`;
    formattedRead(line, "%s", &aa1);
    assert(aa1 == [1:"hello", 2:"world"]);

    int[string] aa2;
    line = `{"hello"=1; "world"=2}`;
    formattedRead(line, "{%(%s=%s; %)}", &aa2);
    assert(aa2 == ["hello":1, "world":2]);

    int[string] aa3;
    line = `{hello=1; world=2}`;
    formattedRead(line, "{%(%(%c%)=%s; %)}", &aa3);
    assert(aa3 == ["hello":1, "world":2]);
}

private T unformatRange(T, Range, Char)(ref Range input, ref FormatSpec!Char spec)
{
    T result;
    static if (isStaticArray!T)
    {
        size_t i;
    }

    auto tr = spec.headUpToNextSpec();

    for (;;)
    {
        auto fmt = FormatSpec!Char(spec.nested);
        fmt.readUpToNextSpec(input);

        bool isRangeValue = (fmt.spec == '(');

        static if (isStaticArray!T)
        {
            result[i++] = unformatElement!(typeof(T.init[0]))(input, fmt);
        }
        else static if (isDynamicArray!T)
        {
            result ~= unformatElement!(ElementType!T)(input, fmt);
        }
        else static if (isAssociativeArray!T)
        {
            auto key = unformatElement!(typeof(T.keys[0]))(input, fmt);
            enforce(!input.empty, "Need more input");
            fmt.readUpToNextSpec(input);        // eat key separator

            result[key] = unformatElement!(typeof(T.values[0]))(input, fmt);
        }

        if (isRangeValue)
        {
            fmt.readUpToNextSpec(input);        // always get trailing
            if (input.empty)
                break;
            if (tr.length && std.algorithm.startsWith(input, tr))
                break;
        }
        else
        {
            if (input.empty)
                break;
            if (tr.length && std.algorithm.startsWith(input, tr))
                break;
            fmt.readUpToNextSpec(input);
        }
    }
    return result;
}

// Undocumented
T unformatElement(T, Range, Char)(ref Range input, ref FormatSpec!Char spec)
    if (isInputRange!Range)
{
    static if (isSomeString!T)
    {
        if (spec.spec == 's')
        {
            return parseElement!T(input);
        }
    }
    else static if (isSomeChar!T)
    {
        if (spec.spec == 's')
        {
            return parseElement!T(input);
        }
    }

    return unformatValue!T(input, spec);
}


// Legacy implementation

enum Mangle : char
{
    Tvoid     = 'v',
    Tbool     = 'b',
    Tbyte     = 'g',
    Tubyte    = 'h',
    Tshort    = 's',
    Tushort   = 't',
    Tint      = 'i',
    Tuint     = 'k',
    Tlong     = 'l',
    Tulong    = 'm',
    Tfloat    = 'f',
    Tdouble   = 'd',
    Treal     = 'e',

    Tifloat   = 'o',
    Tidouble  = 'p',
    Tireal    = 'j',
    Tcfloat   = 'q',
    Tcdouble  = 'r',
    Tcreal    = 'c',

    Tchar     = 'a',
    Twchar    = 'u',
    Tdchar    = 'w',

    Tarray    = 'A',
    Tsarray   = 'G',
    Taarray   = 'H',
    Tpointer  = 'P',
    Tfunction = 'F',
    Tident    = 'I',
    Tclass    = 'C',
    Tstruct   = 'S',
    Tenum     = 'E',
    Ttypedef  = 'T',
    Tdelegate = 'D',

    Tconst    = 'x',
    Timmutable = 'y',
}

// return the TypeInfo for a primitive type and null otherwise.  This
// is required since for arrays of ints we only have the mangled char
// to work from. If arrays always subclassed TypeInfo_Array this
// routine could go away.
private TypeInfo primitiveTypeInfo(Mangle m)
{
    // BUG: should fix this in static this() to avoid double checked locking bug
    __gshared TypeInfo[Mangle] dic;
    if (!dic.length) {
        dic = [
            Mangle.Tvoid : typeid(void),
            Mangle.Tbool : typeid(bool),
            Mangle.Tbyte : typeid(byte),
            Mangle.Tubyte : typeid(ubyte),
            Mangle.Tshort : typeid(short),
            Mangle.Tushort : typeid(ushort),
            Mangle.Tint : typeid(int),
            Mangle.Tuint : typeid(uint),
            Mangle.Tlong : typeid(long),
            Mangle.Tulong : typeid(ulong),
            Mangle.Tfloat : typeid(float),
            Mangle.Tdouble : typeid(double),
            Mangle.Treal : typeid(real),
            Mangle.Tifloat : typeid(ifloat),
            Mangle.Tidouble : typeid(idouble),
            Mangle.Tireal : typeid(ireal),
            Mangle.Tcfloat : typeid(cfloat),
            Mangle.Tcdouble : typeid(cdouble),
            Mangle.Tcreal : typeid(creal),
            Mangle.Tchar : typeid(char),
            Mangle.Twchar : typeid(wchar),
            Mangle.Tdchar : typeid(dchar)
            ];
    }
    auto p = m in dic;
    return p ? *p : null;
}

// This stuff has been removed from the docs and is planned for deprecation.
/*
 * Interprets variadic argument list pointed to by argptr whose types
 * are given by arguments[], formats them according to embedded format
 * strings in the variadic argument list, and sends the resulting
 * characters to putc.
 *
 * The variadic arguments are consumed in order.  Each is formatted
 * into a sequence of chars, using the default format specification
 * for its type, and the characters are sequentially passed to putc.
 * If a $(D char[]), $(D wchar[]), or $(D dchar[]) argument is
 * encountered, it is interpreted as a format string. As many
 * arguments as specified in the format string are consumed and
 * formatted according to the format specifications in that string and
 * passed to putc. If there are too few remaining arguments, a
 * $(D FormatException) is thrown. If there are more remaining arguments than
 * needed by the format specification, the default processing of
 * arguments resumes until they are all consumed.
 *
 * Params:
 *        putc =        Output is sent do this delegate, character by character.
 *        arguments = Array of $(D TypeInfo)s, one for each argument to be formatted.
 *        argptr = Points to variadic argument list.
 *
 * Throws:
 *        Mismatched arguments and formats result in a $(D FormatException) being thrown.
 *
 * Format_String:
 *        <a name="format-string">$(I Format strings)</a>
 *        consist of characters interspersed with
 *        $(I format specifications). Characters are simply copied
 *        to the output (such as putc) after any necessary conversion
 *        to the corresponding UTF-8 sequence.
 *
 *        A $(I format specification) starts with a '%' character,
 *        and has the following grammar:

<pre>
$(I FormatSpecification):
    $(B '%%')
    $(B '%') $(I Flags) $(I Width) $(I Precision) $(I FormatChar)

$(I Flags):
    $(I empty)
    $(B '-') $(I Flags)
    $(B '+') $(I Flags)
    $(B '#') $(I Flags)
    $(B '0') $(I Flags)
    $(B ' ') $(I Flags)

$(I Width):
    $(I empty)
    $(I Integer)
    $(B '*')

$(I Precision):
    $(I empty)
    $(B '.')
    $(B '.') $(I Integer)
    $(B '.*')

$(I Integer):
    $(I Digit)
    $(I Digit) $(I Integer)

$(I Digit):
    $(B '0')
    $(B '1')
    $(B '2')
    $(B '3')
    $(B '4')
    $(B '5')
    $(B '6')
    $(B '7')
    $(B '8')
    $(B '9')

$(I FormatChar):
    $(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')
</pre>
    <dl>
    <dt>$(I Flags)
    <dl>
        <dt>$(B '-')
        <dd>
        Left justify the result in the field.
        It overrides any $(B 0) flag.

        <dt>$(B '+')
        <dd>Prefix positive numbers in a signed conversion with a $(B +).
        It overrides any $(I space) flag.

        <dt>$(B '#')
        <dd>Use alternative formatting:
        <dl>
            <dt>For $(B 'o'):
            <dd> Add to precision as necessary so that the first digit
            of the octal formatting is a '0', even if both the argument
            and the $(I Precision) are zero.
            <dt> For $(B 'x') ($(B 'X')):
            <dd> If non-zero, prefix result with $(B 0x) ($(B 0X)).
            <dt> For floating point formatting:
            <dd> Always insert the decimal point.
            <dt> For $(B 'g') ($(B 'G')):
            <dd> Do not elide trailing zeros.
        </dl>

        <dt>$(B '0')
        <dd> For integer and floating point formatting when not nan or
        infinity, use leading zeros
        to pad rather than spaces.
        Ignore if there's a $(I Precision).

        <dt>$(B ' ')
        <dd>Prefix positive numbers in a signed conversion with a space.
    </dl>

    <dt>$(I Width)
    <dd>
    Specifies the minimum field width.
    If the width is a $(B *), the next argument, which must be
    of type $(B int), is taken as the width.
    If the width is negative, it is as if the $(B -) was given
    as a $(I Flags) character.

    <dt>$(I Precision)
    <dd> Gives the precision for numeric conversions.
    If the precision is a $(B *), the next argument, which must be
    of type $(B int), is taken as the precision. If it is negative,
    it is as if there was no $(I Precision).

    <dt>$(I FormatChar)
    <dd>
    <dl>
        <dt>$(B 's')
        <dd>The corresponding argument is formatted in a manner consistent
        with its type:
        <dl>
            <dt>$(B bool)
            <dd>The result is <tt>'true'</tt> or <tt>'false'</tt>.
            <dt>integral types
            <dd>The $(B %d) format is used.
            <dt>floating point types
            <dd>The $(B %g) format is used.
            <dt>string types
            <dd>The result is the string converted to UTF-8.
            A $(I Precision) specifies the maximum number of characters
            to use in the result.
            <dt>classes derived from $(B Object)
            <dd>The result is the string returned from the class instance's
            $(B .toString()) method.
            A $(I Precision) specifies the maximum number of characters
            to use in the result.
            <dt>non-string static and dynamic arrays
            <dd>The result is [s<sub>0</sub>, s<sub>1</sub>, ...]
            where s<sub>k</sub> is the kth element
            formatted with the default format.
        </dl>

        <dt>$(B 'b','d','o','x','X')
        <dd> The corresponding argument must be an integral type
        and is formatted as an integer. If the argument is a signed type
        and the $(I FormatChar) is $(B d) it is converted to
        a signed string of characters, otherwise it is treated as
        unsigned. An argument of type $(B bool) is formatted as '1'
        or '0'. The base used is binary for $(B b), octal for $(B o),
        decimal
        for $(B d), and hexadecimal for $(B x) or $(B X).
        $(B x) formats using lower case letters, $(B X) uppercase.
        If there are fewer resulting digits than the $(I Precision),
        leading zeros are used as necessary.
        If the $(I Precision) is 0 and the number is 0, no digits
        result.

        <dt>$(B 'e','E')
        <dd> A floating point number is formatted as one digit before
        the decimal point, $(I Precision) digits after, the $(I FormatChar),
        &plusmn;, followed by at least a two digit exponent: $(I d.dddddd)e$(I &plusmn;dd).
        If there is no $(I Precision), six
        digits are generated after the decimal point.
        If the $(I Precision) is 0, no decimal point is generated.

        <dt>$(B 'f','F')
        <dd> A floating point number is formatted in decimal notation.
        The $(I Precision) specifies the number of digits generated
        after the decimal point. It defaults to six. At least one digit
        is generated before the decimal point. If the $(I Precision)
        is zero, no decimal point is generated.

        <dt>$(B 'g','G')
        <dd> A floating point number is formatted in either $(B e) or
        $(B f) format for $(B g); $(B E) or $(B F) format for
        $(B G).
        The $(B f) format is used if the exponent for an $(B e) format
        is greater than -5 and less than the $(I Precision).
        The $(I Precision) specifies the number of significant
        digits, and defaults to six.
        Trailing zeros are elided after the decimal point, if the fractional
        part is zero then no decimal point is generated.

        <dt>$(B 'a','A')
        <dd> A floating point number is formatted in hexadecimal
        exponential notation 0x$(I h.hhhhhh)p$(I &plusmn;d).
        There is one hexadecimal digit before the decimal point, and as
        many after as specified by the $(I Precision).
        If the $(I Precision) is zero, no decimal point is generated.
        If there is no $(I Precision), as many hexadecimal digits as
        necessary to exactly represent the mantissa are generated.
        The exponent is written in as few digits as possible,
        but at least one, is in decimal, and represents a power of 2 as in
        $(I h.hhhhhh)*2<sup>$(I &plusmn;d)</sup>.
        The exponent for zero is zero.
        The hexadecimal digits, x and p are in upper case if the
        $(I FormatChar) is upper case.
    </dl>

    Floating point NaN's are formatted as $(B nan) if the
    $(I FormatChar) is lower case, or $(B NAN) if upper.
    Floating point infinities are formatted as $(B inf) or
    $(B infinity) if the
    $(I FormatChar) is lower case, or $(B INF) or $(B INFINITY) if upper.
    </dl>

Example:

-------------------------
import std.c.stdio;
import std.format;

void myPrint(...)
{
    void putc(char c)
    {
        fputc(c, stdout);
    }

    std.format.doFormat(&putc, _arguments, _argptr);
}

...

int x = 27;
// prints 'The answer is 27:6'
myPrint("The answer is %s:", x, 6);
------------------------
 */
void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr)
{
    doFormatPtr(putc, arguments, argptr, null);
}

void doFormatPtr(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr, void* p_args)
{
    TypeInfo ti;
    Mangle m;
    uint flags;
    int field_width;
    int precision;

    enum : uint
    {
        FLdash = 1,
        FLplus = 2,
        FLspace = 4,
        FLhash = 8,
        FLlngdbl = 0x20,
        FL0pad = 0x40,
        FLprecision = 0x80,
    }

    static TypeInfo skipCI(TypeInfo valti)
    {
        for (;;)
        {
            if (valti.classinfo.name.length == 18 &&
                    valti.classinfo.name[9..18] == "Invariant")
                valti =        (cast(TypeInfo_Invariant)valti).next;
            else if (valti.classinfo.name.length == 14 &&
                    valti.classinfo.name[9..14] == "Const")
                valti =        (cast(TypeInfo_Const)valti).next;
            else
                break;
        }
        return valti;
    }

    void formatArg(char fc)
    {
        bool vbit;
        ulong vnumber;
        char vchar;
        dchar vdchar;
        Object vobject;
        real vreal;
        creal vcreal;
        Mangle m2;
        int signed = 0;
        uint base = 10;
        int uc;
        char[ulong.sizeof * 8] tmpbuf; // long enough to print long in binary
        const(char)* prefix = "";
        string s;

        void putstr(const char[] s)
        {
            //printf("putstr: s = %.*s, flags = x%x\n", s.length, s.ptr, flags);
            sizediff_t padding = field_width -
                (strlen(prefix) + toUCSindex(s, s.length));
            sizediff_t prepad = 0;
            sizediff_t postpad = 0;
            if (padding > 0)
            {
                if (flags & FLdash)
                    postpad = padding;
                else
                    prepad = padding;
            }

            if (flags & FL0pad)
            {
                while (*prefix)
                    putc(*prefix++);
                while (prepad--)
                    putc('0');
            }
            else
            {
                while (prepad--)
                    putc(' ');
                while (*prefix)
                    putc(*prefix++);
            }

            foreach (dchar c; s)
                putc(c);

            while (postpad--)
                putc(' ');
        }

        void putreal(real v)
        {
            //printf("putreal %Lg\n", vreal);

            switch (fc)
            {
                case 's':
                    fc = 'g';
                    break;

                case 'f', 'F', 'e', 'E', 'g', 'G', 'a', 'A':
                    break;

                default:
                    //printf("fc = '%c'\n", fc);
                Lerror:
                    throw new FormatException("floating");
            }
            version (DigitalMarsC)
            {
                uint sl;
                char[] fbuf = tmpbuf;
                if (!(flags & FLprecision))
                    precision = 6;
                while (1)
                {
                    sl = fbuf.length;
                    prefix = (*__pfloatfmt)(fc, flags | FLlngdbl,
                            precision, &v, cast(char*)fbuf, &sl, field_width);
                    if (sl != -1)
                        break;
                    sl = fbuf.length * 2;
                    fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl];
                }
                putstr(fbuf[0 .. sl]);
            }
            else
            {
                sizediff_t sl;
                char[] fbuf = tmpbuf;
                char[12] format;
                format[0] = '%';
                int i = 1;
                if (flags & FLdash)
                    format[i++] = '-';
                if (flags & FLplus)
                    format[i++] = '+';
                if (flags & FLspace)
                    format[i++] = ' ';
                if (flags & FLhash)
                    format[i++] = '#';
                if (flags & FL0pad)
                    format[i++] = '0';
                format[i + 0] = '*';
                format[i + 1] = '.';
                format[i + 2] = '*';
                format[i + 3] = 'L';
                format[i + 4] = fc;
                format[i + 5] = 0;
                if (!(flags & FLprecision))
                    precision = -1;
                while (1)
                {
                    sl = fbuf.length;
                    auto n = snprintf(fbuf.ptr, sl, format.ptr, field_width,
                            precision, v);
                    //printf("format = '%s', n = %d\n", cast(char*)format, n);
                    if (n >= 0 && n < sl)
                    {        sl = n;
                        break;
                    }
                    if (n < 0)
                        sl = sl * 2;
                    else
                        sl = n + 1;
                    fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl];
                }
                putstr(fbuf[0 .. sl]);
            }
            return;
        }

        static Mangle getMan(TypeInfo ti)
        {
          auto m = cast(Mangle)ti.classinfo.name[9];
          if (ti.classinfo.name.length == 20 &&
              ti.classinfo.name[9..20] == "StaticArray")
                m = cast(Mangle)'G';
          return m;
        }

        /* p = pointer to the first element in the array
         * len = number of elements in the array
         * valti = type of the elements
         */
        void putArray(void* p, size_t len, TypeInfo valti)
        {
          //printf("\nputArray(len = %u), tsize = %u\n", len, valti.tsize());
          putc('[');
          valti = skipCI(valti);
          size_t tsize = valti.tsize();
          auto argptrSave = p_args;
          auto tiSave = ti;
          auto mSave = m;
          ti = valti;
          //printf("\n%.*s\n", valti.classinfo.name.length, valti.classinfo.name.ptr);
          m = getMan(valti);
          while (len--)
          {
            //doFormat(putc, (&valti)[0 .. 1], p);
            p_args = p;
            formatArg('s');

            p += tsize;
            if (len > 0) putc(',');
          }
          m = mSave;
          ti = tiSave;
          p_args = argptrSave;
          putc(']');
        }

        void putAArray(ubyte[long] vaa, TypeInfo valti, TypeInfo keyti)
        {
            putc('[');
            bool comma=false;
            auto argptrSave = p_args;
            auto tiSave = ti;
            auto mSave = m;
            valti = skipCI(valti);
            keyti = skipCI(keyti);
            foreach(ref fakevalue; vaa)
            {
                if (comma) putc(',');
                comma = true;
                void *pkey = &fakevalue; 
                version (D_LP64)
                    pkey -= 16;
                else
                    pkey -= long.sizeof;

                // the key comes before the value
                auto keysize = keyti.tsize;
                version (D_LP64) 
                    auto keysizet = (keysize + 15) & ~(15); 
                else 
                    auto keysizet = (keysize + size_t.sizeof - 1) & ~(size_t.sizeof - 1); 

                void* pvalue = pkey + keysizet; 

                //doFormat(putc, (&keyti)[0..1], pkey); 
                p_args = pkey;
                ti = keyti;
                m = getMan(keyti);
                formatArg('s');

                putc(':');
                //doFormat(putc, (&valti)[0..1], pvalue);
                p_args = pvalue; 
                ti = valti;
                m = getMan(valti);
                formatArg('s');
            }
            m = mSave;
            ti = tiSave;
            p_args = argptrSave;
            putc(']');
        }

        //printf("formatArg(fc = '%c', m = '%c')\n", fc, m);
        if (! p_args)
        switch (m)
        {
            case Mangle.Tbool:
                vbit = va_arg!(bool)(argptr);
                if (fc != 's')
                {   vnumber = vbit;
                    goto Lnumber;
                }
                putstr(vbit ? "true" : "false");
                return;


            case Mangle.Tchar:
                vchar = va_arg!(char)(argptr);
                if (fc != 's')
                {   vnumber = vchar;
                    goto Lnumber;
                }
            L2:
                putstr((&vchar)[0 .. 1]);
                return;

            case Mangle.Twchar:
                vdchar = va_arg!(wchar)(argptr);
                goto L1;

            case Mangle.Tdchar:
                vdchar = va_arg!(dchar)(argptr);
            L1:
                if (fc != 's')
                {   vnumber = vdchar;
                    goto Lnumber;
                }
                if (vdchar <= 0x7F)
                {   vchar = cast(char)vdchar;
                    goto L2;
                }
                else
                {   if (!isValidDchar(vdchar))
                        throw new UTFException("invalid dchar in format");
                    char[4] vbuf;
                    putstr(toUTF8(vbuf, vdchar));
                }
                return;


            case Mangle.Tbyte:
                signed = 1;
                vnumber = va_arg!(byte)(argptr);
                goto Lnumber;

            case Mangle.Tubyte:
                vnumber = va_arg!(ubyte)(argptr);
                goto Lnumber;

            case Mangle.Tshort:
                signed = 1;
                vnumber = va_arg!(short)(argptr);
                goto Lnumber;

            case Mangle.Tushort:
                vnumber = va_arg!(ushort)(argptr);
                goto Lnumber;

            case Mangle.Tint:
                signed = 1;
                vnumber = va_arg!(int)(argptr);
                goto Lnumber;

            case Mangle.Tuint:
            Luint:
                vnumber = va_arg!(uint)(argptr);
                goto Lnumber;

            case Mangle.Tlong:
                signed = 1;
                vnumber = cast(ulong)va_arg!(long)(argptr);
                goto Lnumber;

            case Mangle.Tulong:
            Lulong:
                vnumber = va_arg!(ulong)(argptr);
                goto Lnumber;

            case Mangle.Tclass:
                vobject = va_arg!(Object)(argptr);
                if (vobject is null)
                    s = "null";
                else
                    s = vobject.toString();
                goto Lputstr;

            case Mangle.Tpointer:
                vnumber = cast(ulong)va_arg!(void*)(argptr);
                if (fc != 'x')  uc = 1;
                flags |= FL0pad;
                if (!(flags & FLprecision))
                {   flags |= FLprecision;
                    precision = (void*).sizeof;
                }
                base = 16;
                goto Lnumber;


            case Mangle.Tfloat:
            case Mangle.Tifloat:
                if (fc == 'x' || fc == 'X')
                    goto Luint;
                vreal = va_arg!(float)(argptr);
                goto Lreal;

            case Mangle.Tdouble:
            case Mangle.Tidouble:
                if (fc == 'x' || fc == 'X')
                    goto Lulong;
                vreal = va_arg!(double)(argptr);
                goto Lreal;

            case Mangle.Treal:
            case Mangle.Tireal:
                vreal = va_arg!(real)(argptr);
                goto Lreal;


            case Mangle.Tcfloat:
                vcreal = va_arg!(cfloat)(argptr);
                goto Lcomplex;

            case Mangle.Tcdouble:
                vcreal = va_arg!(cdouble)(argptr);
                goto Lcomplex;

            case Mangle.Tcreal:
                vcreal = va_arg!(creal)(argptr);
                goto Lcomplex;

            case Mangle.Tsarray:
                /* Static arrays are converted to dynamic arrays when
                   passed as a variadic argument, so this code should
                   never be executed with GDC.  The case of an
                   embedded static array is handled below.

                putArray(argptr, (cast(TypeInfo_StaticArray)ti).len,
                        (cast(TypeInfo_StaticArray)ti).next);
                return;
                 */
                goto Lerror;

            case Mangle.Tarray:
                int mi = 10;
                if (ti.classinfo.name.length == 14 &&
                    ti.classinfo.name[9..14] == "Array")
                { // array of non-primitive types
                  TypeInfo tn = (cast(TypeInfo_Array)ti).next;
                  tn = skipCI(tn);
                  switch (cast(Mangle)tn.classinfo.name[9])
                  {
                    case Mangle.Tchar:  goto LarrayChar;
                    case Mangle.Twchar: goto LarrayWchar;
                    case Mangle.Tdchar: goto LarrayDchar;
                    default:
                        break;
                  }
                  void[] va = va_arg!(void[])(argptr);
                  putArray(va.ptr, va.length, tn);
                  return;
                }
                if (ti.classinfo.name.length == 25 &&
                    ti.classinfo.name[9..25] == "AssociativeArray")
                { // associative array
                  ubyte[long] vaa = va_arg!(ubyte[long])(argptr);
                  putAArray(vaa,
                        (cast(TypeInfo_AssociativeArray)ti).next,
                        (cast(TypeInfo_AssociativeArray)ti).key);
                  return;
                }

                while (1)
                {
                    m2 = cast(Mangle)ti.classinfo.name[mi];
                    switch (m2)
                    {
                        case Mangle.Tchar:
                        LarrayChar:
                            s = va_arg!(string)(argptr);
                            goto Lputstr;

                        case Mangle.Twchar:
                        LarrayWchar:
                            wchar[] sw = va_arg!(wchar[])(argptr);
                            s = toUTF8(sw);
                            goto Lputstr;

                        case Mangle.Tdchar:
                        LarrayDchar:
                            auto sd = va_arg!(dstring)(argptr);
                            s = toUTF8(sd);
                        Lputstr:
                            if (fc != 's')
                                throw new FormatException("string");
                            if (flags & FLprecision && precision < s.length)
                                s = s[0 .. precision];
                            putstr(s);
                            break;

                        case Mangle.Tconst:
                        case Mangle.Timmutable:
                            mi++;
                            continue;

                        default:
                            TypeInfo ti2 = primitiveTypeInfo(m2);
                            if (!ti2)
                              goto Lerror;
                            void[] va = va_arg!(void[])(argptr);
                            putArray(va.ptr, va.length, ti2);
                    }
                    return;
                }
                assert(0);

            case Mangle.Ttypedef:
                ti = (cast(TypeInfo_Typedef)ti).base;
                m = cast(Mangle)ti.classinfo.name[9];
                formatArg(fc);
                return;

            case Mangle.Tenum:
                ti = (cast(TypeInfo_Enum)ti).base;
                m = cast(Mangle)ti.classinfo.name[9];
                formatArg(fc);
                return;

            case Mangle.Tstruct:
            {        TypeInfo_Struct tis = cast(TypeInfo_Struct)ti;
                if (tis.xtoString is null)
                    throw new FormatException("Can't convert " ~ tis.toString()
                            ~ " to string: \"string toString()\" not defined");
                static if (is(va_list : void[]) || is(va_list == struct))
                {
                    version(PPC)
                    {
                        // Structs are pass-by-reference in V4 ABI
                        s = tis.xtoString(va_arg!(void*)(argptr));
                    }
                    else
                    {
                        throw new FormatException("cannot portably format a struct on this target");
                    }
                }
                else
                {
                    version( MinGW64 )
                    {
                        // MinGW64 packs a pointer to struct.
                        s = tis.xtoString(va_arg!(void*)(argptr));
                    }
                    else
                    {
                        s = tis.xtoString(argptr);
                        argptr += (tis.tsize() + 3) & ~3;
                    }
                }
                goto Lputstr;
            }

            default:
                goto Lerror;
        }
        else
        {
            switch (m)
            {
                case Mangle.Tbool:
                    vbit = *cast(bool*)(p_args); p_args += bool.sizeof; // int.sizeof, etc.?
                    if (fc != 's')
                    {   vnumber = vbit;
                        goto Lnumber;
                    }
                    putstr(vbit ? "true" : "false");
                    return;

                case Mangle.Tchar:
                    vchar = *cast(char*)(p_args); p_args += char.sizeof;
                    if (fc != 's')
                    {   vnumber = vchar;
                        goto Lnumber;
                    }
            PL2:
                    putstr((&vchar)[0 .. 1]);
                    return;

                case Mangle.Twchar:
                    vdchar = *cast(wchar*)(p_args); p_args += wchar.sizeof;
                    goto PL1;

                case Mangle.Tdchar:
                    vdchar = *cast(dchar*)(p_args); p_args += dchar.sizeof;
            PL1:
                    if (fc != 's')
                    {   vnumber = vdchar;
                        goto Lnumber;
                    }
                    if (vdchar <= 0x7F)
                    {   vchar = cast(char)vdchar;
                        goto PL2;
                    }
                    else
                    {   if (!isValidDchar(vdchar))
                        throw new UtfException("invalid dchar in format", 0);
                        char[4] vbuf;
                        putstr(toUTF8(vbuf, vdchar));
                    }
                    return;

                case Mangle.Tbyte:
                    signed = 1;
                    vnumber = *cast(byte*)p_args; p_args += byte.sizeof;
                    goto Lnumber;

                case Mangle.Tubyte:
                    vnumber = *cast(ubyte*)p_args; p_args += ubyte.sizeof;
                    goto Lnumber;

                case Mangle.Tshort:
                    signed = 1;
                    vnumber = *cast(short*)p_args; p_args += short.sizeof;
                    goto Lnumber;

                case Mangle.Tushort:
                    vnumber = *cast(ushort*)p_args; p_args += ushort.sizeof;
                    goto Lnumber;

                case Mangle.Tint:
                    signed = 1;
                    vnumber = *cast(int*)p_args; p_args += int.sizeof;
                    goto Lnumber;

                case Mangle.Tuint:
            PLuint:
                    vnumber = *cast(uint*)p_args; p_args += uint.sizeof;
                    goto Lnumber;

                case Mangle.Tlong:
                    signed = 1;
                    vnumber = cast(ulong)*cast(long*)p_args; p_args += long.sizeof;
                    goto Lnumber;

                case Mangle.Tulong:
            PLulong:
                    vnumber = *cast(ulong*)p_args; p_args += ulong.sizeof;
                    goto Lnumber;

                case Mangle.Tclass:
                    vobject = *cast(Object*)p_args; p_args += Object.sizeof;
                    if (vobject is null)
                        s = "null";
                    else
                        s = vobject.toString();
                    goto Lputstr;

                case Mangle.Tpointer:
                    vnumber = cast(size_t)*cast(void**)p_args; p_args += (void*).sizeof;
                    uc = 1;
                    flags |= FL0pad;
                    if (!(flags & FLprecision))
                    {   flags |= FLprecision;
                        precision = (void*).sizeof;
                    }
                    base = 16;
                    goto Lnumber;

                case Mangle.Tfloat:
                case Mangle.Tifloat:
                    if (fc == 'x' || fc == 'X')
                        goto PLuint;
                    vreal = *cast(float*)p_args; p_args += float.sizeof;
                    goto Lreal;

                case Mangle.Tdouble:
                case Mangle.Tidouble:
                    if (fc == 'x' || fc == 'X')
                        goto PLulong;
                    vreal = *cast(double*)p_args; p_args += double.sizeof;
                    goto Lreal;

                case Mangle.Treal:
                case Mangle.Tireal:
                    vreal = *cast(real*)p_args; p_args += real.sizeof;
                    goto Lreal;

                case Mangle.Tcfloat:
                    vcreal = *cast(cfloat*)p_args; p_args += cfloat.sizeof;
                    goto Lcomplex;

                case Mangle.Tcdouble:
                    vcreal = *cast(cdouble*)p_args; p_args += cdouble.sizeof;
                    goto Lcomplex;

                case Mangle.Tcreal:
                    vcreal = *cast(creal*)p_args; p_args += creal.sizeof;
                    goto Lcomplex;

                case Mangle.Tsarray:
                    putArray(p_args, (cast(TypeInfo_StaticArray)ti).len, (cast(TypeInfo_StaticArray)ti).next);
                    p_args += ti.tsize();
                    return;

                case Mangle.Tarray:
                    int mi = 10;
                    if (ti.classinfo.name.length == 14 &&
                            ti.classinfo.name[9..14] == "Array")
                    { // array of non-primitive types
                        TypeInfo tn = (cast(TypeInfo_Array)ti).next;
                        tn = skipCI(tn);
                        switch (cast(Mangle)tn.classinfo.name[9])
                        {
                            case Mangle.Tchar:  goto LarrayChar;
                            case Mangle.Twchar: goto LarrayWchar;
                            case Mangle.Tdchar: goto LarrayDchar;
                            default:
                                                break;
                        }
                        void[] va = *cast(void[]*)p_args; p_args += (void[]).sizeof;
                        putArray(va.ptr, va.length, tn);
                        return;
                    }
                    if (ti.classinfo.name.length == 25 &&
                            ti.classinfo.name[9..25] == "AssociativeArray")
                    { // associative array
                        ubyte[long] vaa = *cast(ubyte[long]*)p_args; p_args += (ubyte[long]).sizeof;
                        putAArray(vaa,
                                (cast(TypeInfo_AssociativeArray)ti).next,
                                (cast(TypeInfo_AssociativeArray)ti).key);
                        return;
                    }

                    while (1)
                    {
                        m2 = cast(Mangle)ti.classinfo.name[mi];
                        switch (m2)
                        {
                            case Mangle.Tchar:
                    PLarrayChar:
                                s = *cast(string*)p_args; p_args += (string).sizeof;
                                goto PLputstr;

                            case Mangle.Twchar:
                    PLarrayWchar:
                                wchar[] sw = *cast(wchar[]*)p_args; p_args += (wchar[]).sizeof;
                                s = toUTF8(sw);
                                goto PLputstr;

                            case Mangle.Tdchar:
                    PLarrayDchar:
                                auto sd = *cast(dchar[]*)p_args; p_args += (dchar[]).sizeof;
                                s = toUTF8(sd);
                    PLputstr:
                                if (fc != 's')
                                    throw new FormatException("string");
                                if (flags & FLprecision && precision < s.length)
                                    s = s[0 .. precision];
                                putstr(s);
                                break;

                            case Mangle.Tconst:
                            case Mangle.Timmutable:
                                mi++;
                                continue;

                            default:
                                TypeInfo ti2 = primitiveTypeInfo(m2);
                                if (!ti2)
                                    goto Lerror;
                                void[] va = *cast(void[]*)p_args; p_args += (void[]).sizeof;
                                putArray(va.ptr, va.length, ti2);
                        }
                        return;
                    }
                    assert(0);

                case Mangle.Ttypedef:
                    ti = (cast(TypeInfo_Typedef)ti).base;
                    m = cast(Mangle)ti.classinfo.name[9];
                    formatArg(fc);
                    return;

                case Mangle.Tenum:
                    ti = (cast(TypeInfo_Enum)ti).base;
                    m = cast(Mangle)ti.classinfo.name[9];
                    formatArg(fc);
                    return;

                case Mangle.Tstruct:
                {   TypeInfo_Struct tis = cast(TypeInfo_Struct)ti;
                    if (tis.xtoString is null)
                        throw new FormatException("Can't convert " ~ tis.toString()
                                ~ " to string: \"string toString()\" not defined");
                    s = tis.xtoString(p_args);
                    p_args += tis.tsize();
                    goto Lputstr;
                }

                default:
                    goto Lerror;
            }
        }

    Lnumber:
        switch (fc)
        {
            case 's':
            case 'd':
                if (signed)
                {   if (cast(long)vnumber < 0)
                    {        prefix = "-";
                        vnumber = -vnumber;
                    }
                    else if (flags & FLplus)
                        prefix = "+";
                    else if (flags & FLspace)
                        prefix = " ";
                }
                break;

            case 'b':
                signed = 0;
                base = 2;
                break;

            case 'o':
                signed = 0;
                base = 8;
                break;

            case 'X':
                uc = 1;
                if (flags & FLhash && vnumber)
                    prefix = "0X";
                signed = 0;
                base = 16;
                break;

            case 'x':
                if (flags & FLhash && vnumber)
                    prefix = "0x";
                signed = 0;
                base = 16;
                break;

            default:
                goto Lerror;
        }

        if (!signed)
        {
            switch (m)
            {
                case Mangle.Tbyte:
                    vnumber &= 0xFF;
                    break;

                case Mangle.Tshort:
                    vnumber &= 0xFFFF;
                    break;

                case Mangle.Tint:
                    vnumber &= 0xFFFFFFFF;
                    break;

                default:
                    break;
            }
        }

        if (flags & FLprecision && fc != 'p')
            flags &= ~FL0pad;

        if (vnumber < base)
        {
            if (vnumber == 0 && precision == 0 && flags & FLprecision &&
                !(fc == 'o' && flags & FLhash))
            {
                putstr(null);
                return;
            }
            if (precision == 0 || !(flags & FLprecision))
            {        vchar = cast(char)('0' + vnumber);
                if (vnumber < 10)
                    vchar = cast(char)('0' + vnumber);
                else
                    vchar = cast(char)((uc ? 'A' - 10 : 'a' - 10) + vnumber);
                goto L2;
            }
        }

        sizediff_t n = tmpbuf.length;
        char c;
        int hexoffset = uc ? ('A' - ('9' + 1)) : ('a' - ('9' + 1));

        while (vnumber)
        {
            c = cast(char)((vnumber % base) + '0');
            if (c > '9')
                c += hexoffset;
            vnumber /= base;
            tmpbuf[--n] = c;
        }
        if (tmpbuf.length - n < precision && precision < tmpbuf.length)
        {
            sizediff_t m = tmpbuf.length - precision;
            tmpbuf[m .. n] = '0';
            n = m;
        }
        else if (flags & FLhash && fc == 'o')
            prefix = "0";
        putstr(tmpbuf[n .. tmpbuf.length]);
        return;

    Lreal:
        putreal(vreal);
        return;

    Lcomplex:
        putreal(vcreal.re);
        putc('+');
        putreal(vcreal.im);
        putc('i');
        return;

    Lerror:
        throw new FormatException("formatArg");
    }

    for (int j = 0; j < arguments.length; )
    {
        ti = arguments[j++];
        //printf("arg[%d]: '%.*s' %d\n", j, ti.classinfo.name.length, ti.classinfo.name.ptr, ti.classinfo.name.length);
        //ti.print();

        flags = 0;
        precision = 0;
        field_width = 0;

        ti = skipCI(ti);
        int mi = 9;
        do
        {
            if (ti.classinfo.name.length <= mi)
                goto Lerror;
            m = cast(Mangle)ti.classinfo.name[mi++];
        } while (m == Mangle.Tconst || m == Mangle.Timmutable);

        if (m == Mangle.Tarray)
        {
            if (ti.classinfo.name.length == 14 &&
                    ti.classinfo.name[9..14] == "Array")
            {
                TypeInfo tn = (cast(TypeInfo_Array)ti).next;
                tn = skipCI(tn);
                switch (cast(Mangle)tn.classinfo.name[9])
                {
                case Mangle.Tchar:
                case Mangle.Twchar:
                case Mangle.Tdchar:
                    ti = tn;
                    mi = 9;
                    break;
                default:
                    break;
                }
            }
          L1:
            Mangle m2 = cast(Mangle)ti.classinfo.name[mi];
            string  fmt;                        // format string
            wstring wfmt;
            dstring dfmt;

            /* For performance reasons, this code takes advantage of the
             * fact that most format strings will be ASCII, and that the
             * format specifiers are always ASCII. This means we only need
             * to deal with UTF in a couple of isolated spots.
             */

            switch (m2)
            {
            case Mangle.Tchar:
                fmt = va_arg!(string)(argptr);
                break;

            case Mangle.Twchar:
                wfmt = va_arg!(wstring)(argptr);
                fmt = toUTF8(wfmt);
                break;

            case Mangle.Tdchar:
                dfmt = va_arg!(dstring)(argptr);
                fmt = toUTF8(dfmt);
                break;

            case Mangle.Tconst:
            case Mangle.Timmutable:
                mi++;
                goto L1;

            default:
                formatArg('s');
                continue;
            }

            for (size_t i = 0; i < fmt.length; )
            {        dchar c = fmt[i++];

                dchar getFmtChar()
                {   // Valid format specifier characters will never be UTF
                    if (i == fmt.length)
                        throw new FormatException("invalid specifier");
                    return fmt[i++];
                }

                int getFmtInt()
                {   int n;

                    while (1)
                    {
                        n = n * 10 + (c - '0');
                        if (n < 0)        // overflow
                            throw new FormatException("int overflow");
                        c = getFmtChar();
                        if (c < '0' || c > '9')
                            break;
                    }
                    return n;
                }

                int getFmtStar()
                {   Mangle m;
                    TypeInfo ti;

                    if (j == arguments.length)
                        throw new FormatException("too few arguments");
                    ti = arguments[j++];
                    m = cast(Mangle)ti.classinfo.name[9];
                    if (m != Mangle.Tint)
                        throw new FormatException("int argument expected");
                    return va_arg!(int)(argptr);
                }

                if (c != '%')
                {
                    if (c > 0x7F)        // if UTF sequence
                    {
                        i--;                // back up and decode UTF sequence
                        c = std.utf.decode(fmt, i);
                    }
                  Lputc:
                    putc(c);
                    continue;
                }

                // Get flags {-+ #}
                flags = 0;
                while (1)
                {
                    c = getFmtChar();
                    switch (c)
                    {
                    case '-':        flags |= FLdash;        continue;
                    case '+':        flags |= FLplus;        continue;
                    case ' ':        flags |= FLspace;        continue;
                    case '#':        flags |= FLhash;        continue;
                    case '0':        flags |= FL0pad;        continue;

                    case '%':        if (flags == 0)
                                          goto Lputc;
                                     break;

                    default:         break;
                    }
                    break;
                }

                // Get field width
                field_width = 0;
                if (c == '*')
                {
                    field_width = getFmtStar();
                    if (field_width < 0)
                    {   flags |= FLdash;
                        field_width = -field_width;
                    }

                    c = getFmtChar();
                }
                else if (c >= '0' && c <= '9')
                    field_width = getFmtInt();

                if (flags & FLplus)
                    flags &= ~FLspace;
                if (flags & FLdash)
                    flags &= ~FL0pad;

                // Get precision
                precision = 0;
                if (c == '.')
                {   flags |= FLprecision;
                    //flags &= ~FL0pad;

                    c = getFmtChar();
                    if (c == '*')
                    {
                        precision = getFmtStar();
                        if (precision < 0)
                        {   precision = 0;
                            flags &= ~FLprecision;
                        }

                        c = getFmtChar();
                    }
                    else if (c >= '0' && c <= '9')
                        precision = getFmtInt();
                }

                if (j == arguments.length)
                    goto Lerror;
                ti = arguments[j++];
                ti = skipCI(ti);
                mi = 9;
                do
                {
                    m = cast(Mangle)ti.classinfo.name[mi++];
                } while (m == Mangle.Tconst || m == Mangle.Timmutable);

                if (c > 0x7F)                // if UTF sequence
                    goto Lerror;        // format specifiers can't be UTF
                formatArg(cast(char)c);
            }
        }
        else
        {
            formatArg('s');
        }
    }
    return;

  Lerror:
    throw new FormatException();
}

/* ======================== Unit Tests ====================================== */

unittest
{
    int i;
    string s;

    debug(format) printf("std.format.format.unittest\n");

    s = std.string.format("hello world! %s %s ", true, 57, 1_000_000_000, 'x', " foo");
    assert(s == "hello world! true 57 1000000000x foo");

    s = std.string.format(1.67, " %A ", -1.28, float.nan);
    /* The host C library is used to format floats.
     * C99 doesn't specify what the hex digit before the decimal point
     * is for %A.
     */
    version (linux)
        assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan");
    else version (OSX)
        assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan", s);
    else
        assert(s == "1.67 -0X1.47AE147AE147BP+0 nan");

    s = std.string.format("%x %X", 0x1234AF, 0xAFAFAFAF);
    assert(s == "1234af AFAFAFAF");

    s = std.string.format("%b %o", 0x1234AF, 0xAFAFAFAF);
    assert(s == "100100011010010101111 25753727657");

    s = std.string.format("%d %s", 0x1234AF, 0xAFAFAFAF);
    assert(s == "1193135 2947526575");

    s = std.string.format("%s", 1.2 + 3.4i);
    assert(s == "1.2+3.4i");

    s = std.string.format("%x %X", 1.32, 6.78f);
    assert(s == "3ff51eb851eb851f 40D8F5C3");

    s = std.string.format("%#06.*f",2,12.345);
    assert(s == "012.35");

    s = std.string.format("%#0*.*f",6,2,12.345);
    assert(s == "012.35");

    s = std.string.format("%7.4g:", 12.678);
    assert(s == "  12.68:");

    s = std.string.format("%7.4g:", 12.678L);
    assert(s == "  12.68:");

    s = std.string.format("%04f|%05d|%#05x|%#5x",-4.,-10,1,1);
    assert(s == "-4.000000|-0010|0x001|  0x1");

    i = -10;
    s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
    assert(s == "-10|-10|-10|-10|-10.0000");

    i = -5;
    s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
    assert(s == "-5| -5|-05|-5|-5.0000");

    i = 0;
    s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
    assert(s == "0|  0|000|0|0.0000");

    i = 5;
    s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
    assert(s == "5|  5|005|5|5.0000");

    i = 10;
    s = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
    assert(s == "10| 10|010|10|10.0000");

    s = std.string.format("%.0d", 0);
    assert(s == "");

    s = std.string.format("%.g", .34);
    assert(s == "0.3");

    s = std.string.format("%.0g", .34);
    assert(s == "0.3");

    s = std.string.format("%.2g", .34);
    assert(s == "0.34");

    s = std.string.format("%0.0008f", 1e-08);
    assert(s == "0.00000001");

    s = std.string.format("%0.0008f", 1e-05);
    assert(s == "0.00001000");

    s = "helloworld";
    string r;
    r = std.string.format("%.2s", s[0..5]);
    assert(r == "he");
    r = std.string.format("%.20s", s[0..5]);
    assert(r == "hello");
    r = std.string.format("%8s", s[0..5]);
    assert(r == "   hello");

    byte[] arrbyte = new byte[4];
    arrbyte[0] = 100;
    arrbyte[1] = -99;
    arrbyte[3] = 0;
    r = std.string.format(arrbyte);
    assert(r == "[100,-99,0,0]");

    ubyte[] arrubyte = new ubyte[4];
    arrubyte[0] = 100;
    arrubyte[1] = 200;
    arrubyte[3] = 0;
    r = std.string.format(arrubyte);
    assert(r == "[100,200,0,0]");

    short[] arrshort = new short[4];
    arrshort[0] = 100;
    arrshort[1] = -999;
    arrshort[3] = 0;
    r = std.string.format(arrshort);
    assert(r == "[100,-999,0,0]");
    r = std.string.format("%s",arrshort);
    assert(r == "[100,-999,0,0]");

    ushort[] arrushort = new ushort[4];
    arrushort[0] = 100;
    arrushort[1] = 20_000;
    arrushort[3] = 0;
    r = std.string.format(arrushort);
    assert(r == "[100,20000,0,0]");

    int[] arrint = new int[4];
    arrint[0] = 100;
    arrint[1] = -999;
    arrint[3] = 0;
    r = std.string.format(arrint);
    assert(r == "[100,-999,0,0]");
    r = std.string.format("%s",arrint);
    assert(r == "[100,-999,0,0]");

    long[] arrlong = new long[4];
    arrlong[0] = 100;
    arrlong[1] = -999;
    arrlong[3] = 0;
    r = std.string.format(arrlong);
    assert(r == "[100,-999,0,0]");
    r = std.string.format("%s",arrlong);
    assert(r == "[100,-999,0,0]");

    ulong[] arrulong = new ulong[4];
    arrulong[0] = 100;
    arrulong[1] = 999;
    arrulong[3] = 0;
    r = std.string.format(arrulong);
    assert(r == "[100,999,0,0]");

    string[] arr2 = new string[4];
    arr2[0] = "hello";
    arr2[1] = "world";
    arr2[3] = "foo";
    r = std.string.format(arr2);
    assert(r == "[hello,world,,foo]");

    r = std.string.format("%.8d", 7);
    assert(r == "00000007");
    r = std.string.format("%.8x", 10);
    assert(r == "0000000a");

    r = std.string.format("%-3d", 7);
    assert(r == "7  ");

    r = std.string.format("%*d", -3, 7);
    assert(r == "7  ");

    r = std.string.format("%.*d", -3, 7);
    assert(r == "7");

    //typedef int myint;
    //myint m = -7;
    //r = std.string.format(m);
    //assert(r == "-7");

    r = std.string.format("abc"c);
    assert(r == "abc");
    r = std.string.format("def"w);
    assert(r == "def");
    r = std.string.format("ghi"d);
    assert(r == "ghi");

    void* p = cast(void*)0xDEADBEEF;
    r = std.string.format(p);
    assert(r == "DEADBEEF");

    r = std.string.format("%#x", 0xabcd);
    assert(r == "0xabcd");
    r = std.string.format("%#X", 0xABCD);
    assert(r == "0XABCD");

    r = std.string.format("%#o", octal!12345);
    assert(r == "012345");
    r = std.string.format("%o", 9);
    assert(r == "11");

    r = std.string.format("%+d", 123);
    assert(r == "+123");
    r = std.string.format("%+d", -123);
    assert(r == "-123");
    r = std.string.format("% d", 123);
    assert(r == " 123");
    r = std.string.format("% d", -123);
    assert(r == "-123");

    r = std.string.format("%%");
    assert(r == "%");

    r = std.string.format("%d", true);
    assert(r == "1");
    r = std.string.format("%d", false);
    assert(r == "0");

    r = std.string.format("%d", 'a');
    assert(r == "97");
    wchar wc = 'a';
    r = std.string.format("%d", wc);
    assert(r == "97");
    dchar dc = 'a';
    r = std.string.format("%d", dc);
    assert(r == "97");

    byte b = byte.max;
    r = std.string.format("%x", b);
    assert(r == "7f");
    r = std.string.format("%x", ++b);
    assert(r == "80");
    r = std.string.format("%x", ++b);
    assert(r == "81");

    short sh = short.max;
    r = std.string.format("%x", sh);
    assert(r == "7fff");
    r = std.string.format("%x", ++sh);
    assert(r == "8000");
    r = std.string.format("%x", ++sh);
    assert(r == "8001");

    i = int.max;
    r = std.string.format("%x", i);
    assert(r == "7fffffff");
    r = std.string.format("%x", ++i);
    assert(r == "80000000");
    r = std.string.format("%x", ++i);
    assert(r == "80000001");

    r = std.string.format("%x", 10);
    assert(r == "a");
    r = std.string.format("%X", 10);
    assert(r == "A");
    r = std.string.format("%x", 15);
    assert(r == "f");
    r = std.string.format("%X", 15);
    assert(r == "F");

    Object c = null;
    r = std.string.format(c);
    assert(r == "null");

    enum TestEnum
    {
            Value1, Value2
    }
    r = std.string.format("%s", TestEnum.Value2);
    assert(r == "1");

    immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]);
    r = std.string.format("%s", aa.values);
    assert(r == "[[h,e,l,l,o],[b,e,t,t,y]]");
    r = std.string.format("%s", aa);
    assert(r == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]");

    static const dchar[] ds = ['a','b'];
    for (int j = 0; j < ds.length; ++j)
    {
        r = std.string.format(" %d", ds[j]);
        if (j == 0)
            assert(r == " 97");
        else
            assert(r == " 98");
    }

    r = std.string.format(">%14d<, ", 15, [1,2,3]);
    assert(r == ">            15<, [1,2,3]");

    assert(std.string.format("%8s", "bar") == "     bar");
    assert(std.string.format("%8s", "b\u00e9ll\u00f4") == "   b\u00e9ll\u00f4");
}

unittest
{
    // bugzilla 3479
    auto stream = appender!(char[])();
    formattedWrite(stream, "%2$.*1$d", 12, 10);
    assert(stream.data == "000000000010", stream.data);
}
