// Written in the D programming language.

/**
This is a submodule of $(MREF std, format).

It provides two functions for writing formatted output: $(LREF
formatValue) and $(LREF formattedWrite). The former writes a single
value. The latter writes several values at once, interspersed with
unformatted text.

The following combinations of format characters and types are
available:

$(BOOKTABLE ,
$(TR $(TH) $(TH s) $(TH c) $(TH d, u, b, o) $(TH x, X) $(TH e, E, f, F, g, G, a, A) $(TH r) $(TH compound))
$(TR $(TD `bool`) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH)))
$(TR $(TD `null`) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)))
$(TR $(TD $(I integer)) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD yes) $(TD yes) $(TD yes) $(TD $(MDASH)))
$(TR $(TD $(I floating point)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes) $(TD $(MDASH)))
$(TR $(TD $(I character)) $(TD yes) $(TD yes) $(TD yes) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH)))
$(TR $(TD $(I string)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes))
$(TR $(TD $(I array)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes))
$(TR $(TD $(I associative array)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes))
$(TR $(TD $(I pointer)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)))
$(TR $(TD $(I SIMD vectors)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes))
$(TR $(TD $(I delegates)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes))
)

Enums can be used with all format characters of the base type.

$(H3 $(LNAME2 aggregates, Structs, Unions, Classes, and Interfaces))

Aggregate types can define various `toString` functions. If this
function takes a $(REF_ALTTEXT FormatSpec, FormatSpec, std, format,
spec) or a $(I format string) as argument, the function decides
which format characters are accepted. If no `toString` is defined and
the aggregate is an $(REF_ALTTEXT input range, isInputRange, std,
range, primitives), it is treated like a range, that is $(B 's'), $(B
'r') and a compound specifier are accepted. In all other cases
aggregate types only accept $(B 's').

`toString` should have one of the following signatures:

---
void toString(Writer, Char)(ref Writer w, const ref FormatSpec!Char fmt)
void toString(Writer)(ref Writer w)
string toString();
---

Where `Writer` is an $(REF_ALTTEXT output range, isOutputRange,
std,range,primitives) which accepts characters $(LPAREN)of type
`Char` in the first version$(RPAREN). The template type does not have
to be called `Writer`.

Sometimes it's not possible to use a template, for example when
`toString` overrides `Object.toString`. In this case, the following
$(LPAREN)slower and less flexible$(RPAREN) functions can be used:

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

When several of the above `toString` versions are available, the
versions with `Writer` take precedence over the versions with a
`sink`. `string toString()` has the lowest priority.

If none of the above mentioned `toString` versions are available, the
aggregates will be formatted by other means, in the following
order:

If an aggregate is an $(REF_ALTTEXT input range, isInputRange, std,
range, primitives), it is formatted like an input range.

If an aggregate is a builtin type (using `alias this`), it is formatted
like the builtin type.

If all else fails, structs are formatted like `Type(field1, field2, ...)`,
classes and interfaces are formatted with their fully qualified name
and unions with their base name.

Copyright: Copyright The D Language Foundation 2000-2013.

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

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

Source: $(PHOBOSSRC std/format/write.d)
 */
module std.format.write;

/**
`bool`s are formatted as `"true"` or `"false"` with `%s` and like the
`byte`s 1 and 0 with all other format characters.
 */
@safe pure unittest
{
    import std.array : appender;
    import std.format.spec : singleSpec;

    auto w1 = appender!string();
    auto spec1 = singleSpec("%s");
    formatValue(w1, true, spec1);

    assert(w1.data == "true");

    auto w2 = appender!string();
    auto spec2 = singleSpec("%#x");
    formatValue(w2, true, spec2);

    assert(w2.data == "0x1");
}

/// The `null` literal is formatted as `"null"`.
@safe pure unittest
{
    import std.array : appender;
    import std.format.spec : singleSpec;

    auto w = appender!string();
    auto spec = singleSpec("%s");
    formatValue(w, null, spec);

    assert(w.data == "null");
}

/**
Integrals are formatted in (signed) every day notation with `%s` and
`%d` and as an (unsigned) image of the underlying bit representation
with `%b` (binary), `%u` (decimal), `%o` (octal), and `%x` (hexadecimal).
 */
@safe pure unittest
{
    import std.array : appender;
    import std.format.spec : singleSpec;

    auto w1 = appender!string();
    auto spec1 = singleSpec("%d");
    formatValue(w1, -1337, spec1);

    assert(w1.data == "-1337");

    auto w2 = appender!string();
    auto spec2 = singleSpec("%x");
    formatValue(w2, -1337, spec2);

    assert(w2.data == "fffffac7");
}

/**
Floating-point values are formatted in natural notation with `%f`, in
scientific notation with `%e`, in short notation with `%g`, and in
hexadecimal scientific notation with `%a`. If a rounding mode is
available, they are rounded according to this rounding mode, otherwise
they are rounded to the nearest value, ties to even.
 */
@safe unittest
{
    import std.array : appender;
    import std.format.spec : singleSpec;

    auto w1 = appender!string();
    auto spec1 = singleSpec("%.3f");
    formatValue(w1, 1337.7779, spec1);

    assert(w1.data == "1337.778");

    auto w2 = appender!string();
    auto spec2 = singleSpec("%.3e");
    formatValue(w2, 1337.7779, spec2);

    assert(w2.data == "1.338e+03");

    auto w3 = appender!string();
    auto spec3 = singleSpec("%.3g");
    formatValue(w3, 1337.7779, spec3);

    assert(w3.data == "1.34e+03");

    auto w4 = appender!string();
    auto spec4 = singleSpec("%.3a");
    formatValue(w4, 1337.7779, spec4);

    assert(w4.data == "0x1.4e7p+10");
}

/**
Individual characters (`char`, `wchar`, or `dchar`) are formatted as
Unicode characters with `%s` and `%c` and as integers (`ubyte`,
`ushort`, `uint`) with all other format characters. With
$(MREF_ALTTEXT compound specifiers, std,format) characters are
treated differently.
 */
@safe pure unittest
{
    import std.array : appender;
    import std.format.spec : singleSpec;

    auto w1 = appender!string();
    auto spec1 = singleSpec("%c");
    formatValue(w1, 'ì', spec1);

    assert(w1.data == "ì");

    auto w2 = appender!string();
    auto spec2 = singleSpec("%#x");
    formatValue(w2, 'ì', spec2);

    assert(w2.data == "0xec");
}

/**
Strings are formatted as a sequence of characters with `%s`.
Non-printable characters are not escaped. With a compound specifier
the string is treated like a range of characters. With $(MREF_ALTTEXT
compound specifiers, std,format) strings are treated differently.
 */
@safe pure unittest
{
    import std.array : appender;
    import std.format.spec : singleSpec;

    auto w1 = appender!string();
    auto spec1 = singleSpec("%s");
    formatValue(w1, "hello", spec1);

    assert(w1.data == "hello");

    auto w2 = appender!string();
    auto spec2 = singleSpec("%(%#x%|/%)");
    formatValue(w2, "hello", spec2);

    assert(w2.data == "0x68/0x65/0x6c/0x6c/0x6f");
}

/// Static arrays are formatted as dynamic arrays.
@safe pure unittest
{
    import std.array : appender;
    import std.format.spec : singleSpec;

    auto w = appender!string();
    auto spec = singleSpec("%s");
    int[2] two = [1, 2];
    formatValue(w, two, spec);

    assert(w.data == "[1, 2]");
}

/**
Dynamic arrays are formatted as input ranges.
 */
@safe pure unittest
{
    import std.array : appender;
    import std.format.spec : singleSpec;

    auto w1 = appender!string();
    auto spec1 = singleSpec("%s");
    auto two = [1, 2];
    formatValue(w1, two, spec1);

    assert(w1.data == "[1, 2]");

    auto w2 = appender!string();
    auto spec2 = singleSpec("%(%g%|, %)");
    auto consts = [3.1415926, 299792458, 6.67430e-11];
    formatValue(w2, consts, spec2);

    assert(w2.data == "3.14159, 2.99792e+08, 6.6743e-11");

    // void[] is treated like ubyte[]
    auto w3 = appender!string();
    auto spec3 = singleSpec("%s");
    void[] val = cast(void[]) cast(ubyte[])[1, 2, 3];
    formatValue(w3, val, spec3);

    assert(w3.data == "[1, 2, 3]");
}

/**
Associative arrays are formatted by using `':'` and `", "` as
separators, enclosed by `'['` and `']'` when used with `%s`. It's
also possible to use a compound specifier for better control.

Please note, that the order of the elements is not defined, therefore
the result of this function might differ.
 */
@safe pure unittest
{
    import std.array : appender;
    import std.format.spec : singleSpec;

    auto aa = [10:17.5, 20:9.99];

    auto w1 = appender!string();
    auto spec1 = singleSpec("%s");
    formatValue(w1, aa, spec1);

    assert(w1.data == "[10:17.5, 20:9.99]" || w1.data == "[20:9.99, 10:17.5]");

    auto w2 = appender!string();
    auto spec2 = singleSpec("%(%x = %.0e%| # %)");
    formatValue(w2, aa, spec2);

    assert(w2.data == "a = 2e+01 # 14 = 1e+01" || w2.data == "14 = 1e+01 # a = 2e+01");
}

/**
`enum`s are formatted as their name when used with `%s` and like
their base value else.
 */
@safe pure unittest
{
    import std.array : appender;
    import std.format.spec : singleSpec;

    enum A { first, second, third }

    auto w1 = appender!string();
    auto spec1 = singleSpec("%s");
    formatValue(w1, A.second, spec1);

    assert(w1.data == "second");

    auto w2 = appender!string();
    auto spec2 = singleSpec("%d");
    formatValue(w2, A.second, spec2);

    assert(w2.data == "1");

    // values of an enum that have no name are formatted with %s using a cast
    A a = A.third;
    a++;

    auto w3 = appender!string();
    auto spec3 = singleSpec("%s");
    formatValue(w3, a, spec3);

    assert(w3.data == "cast(A)3");
}

/**
`structs`, `unions`, `classes` and `interfaces` can be formatted in
several different ways. The following example highlights `struct`
formatting, however, it applies to other aggregates as well.
 */
@safe unittest
{
    import std.array : appender;
    import std.format.spec : FormatSpec, singleSpec;

    // Using a `toString` with a writer
    static struct Point1
    {
        import std.range.primitives : isOutputRange, put;

        int x, y;

        void toString(W)(ref W writer, scope const ref FormatSpec!char f)
        if (isOutputRange!(W, char))
        {
            put(writer, "(");
            formatValue(writer, x, f);
            put(writer, ",");
            formatValue(writer, y, f);
            put(writer, ")");
        }
    }

    auto w1 = appender!string();
    auto spec1 = singleSpec("%s");
    auto p1 = Point1(16, 11);

    formatValue(w1, p1, spec1);
    assert(w1.data == "(16,11)");

    // Using a `toString` with a sink
    static struct Point2
    {
        int x, y;

        void toString(scope void delegate(scope const(char)[]) @safe sink,
                      scope const FormatSpec!char fmt) const
        {
            sink("(");
            sink.formatValue(x, fmt);
            sink(",");
            sink.formatValue(y, fmt);
            sink(")");
        }
    }

    auto w2 = appender!string();
    auto spec2 = singleSpec("%03d");
    auto p2 = Point2(16,11);

    formatValue(w2, p2, spec2);
    assert(w2.data == "(016,011)");

    // Using `string toString()`
    static struct Point3
    {
        int x, y;

        string toString()
        {
            import std.conv : to;

            return "(" ~ to!string(x) ~ "," ~ to!string(y) ~ ")";
        }
    }

    auto w3 = appender!string();
    auto spec3 = singleSpec("%s"); // has to be %s
    auto p3 = Point3(16,11);

    formatValue(w3, p3, spec3);
    assert(w3.data == "(16,11)");

    // without `toString`
    static struct Point4
    {
        int x, y;
    }

    auto w4 = appender!string();
    auto spec4 = singleSpec("%s"); // has to be %s
    auto p4 = Point4(16,11);

    formatValue(w4, p4, spec3);
    assert(w4.data == "Point4(16, 11)");
}

/// Pointers are formatted as hexadecimal integers.
@safe pure unittest
{
    import std.array : appender;
    import std.format.spec : singleSpec;

    auto w1 = appender!string();
    auto spec1 = singleSpec("%s");
    auto p1 = () @trusted { return cast(void*) 0xFFEECCAA; } ();
    formatValue(w1, p1, spec1);

    assert(w1.data == "FFEECCAA");

    // null pointers are printed as `"null"` when used with `%s` and as hexadecimal integer else
    auto w2 = appender!string();
    auto spec2 = singleSpec("%s");
    auto p2 = () @trusted { return cast(void*) 0x00000000; } ();
    formatValue(w2, p2, spec2);

    assert(w2.data == "null");

    auto w3 = appender!string();
    auto spec3 = singleSpec("%x");
    formatValue(w3, p2, spec3);

    assert(w3.data == "0");
}

/// SIMD vectors are formatted as arrays.
@safe unittest
{
    import core.simd; // cannot be selective, because float4 might not be defined
    import std.array : appender;
    import std.format.spec : singleSpec;

    auto w = appender!string();
    auto spec = singleSpec("%s");

    static if (is(float4))
    {
        version (X86) {}
        else
        {
            float4 f4;
            f4.array[0] = 1;
            f4.array[1] = 2;
            f4.array[2] = 3;
            f4.array[3] = 4;

            formatValue(w, f4, spec);
            assert(w.data == "[1, 2, 3, 4]");
        }
    }
}

import std.format.internal.write;

import std.format.spec : FormatSpec;
import std.traits : isSomeString;

/**
Converts its arguments according to a format string and writes
the result to an output range.

The second version of `formattedWrite` takes the format string as a
template argument. In this case, it is checked for consistency at
compile-time.

Params:
    w = an $(REF_ALTTEXT output range, isOutputRange, std, range, primitives),
        where the formatted result is written to
    fmt = a $(MREF_ALTTEXT format string, std,format)
    args = a variadic list of arguments to be formatted
    Writer = the type of the writer `w`
    Char = character type of `fmt`
    Args = a variadic list of types of the arguments

Returns:
    The index of the last argument that was formatted. If no positional
    arguments are used, this is the number of arguments that where formatted.

Throws:
    A $(REF_ALTTEXT FormatException, FormatException, std, format)
    if formatting did not succeed.

Note:
    In theory this function should be `@nogc`. But with the current
    implementation there are some cases where allocations occur.
    See $(REF_ALTTEXT $(D sformat), sformat, std, format) for more details.
 */
uint formattedWrite(Writer, Char, Args...)(auto ref Writer w, const scope Char[] fmt, Args args)
{
    import std.conv : text;
    import std.format : enforceFmt, FormatException;
    import std.traits : isSomeChar;

    auto spec = FormatSpec!Char(fmt);

    // Are we already done with formats? Then just dump each parameter in turn
    uint currentArg = 0;
    while (spec.writeUpToNextSpec(w))
    {
        if (currentArg == Args.length && !spec.indexStart)
        {
            // leftover spec?
            enforceFmt(fmt.length == 0,
                text("Orphan format specifier: %", spec.spec));
            break;
        }

        if (spec.width == spec.DYNAMIC)
        {
            auto width = getNthInt!"integer width"(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, "The index must be greater than zero");
            auto width = getNthInt!"integer width"(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 = getNthInt!"integer precision"(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, "The precision must be greater than zero");
            auto precision = getNthInt!"integer precision"(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;
        }

        if (spec.separators == spec.DYNAMIC)
        {
            auto separators = getNthInt!"separator digit width"(currentArg, args);
            spec.separators = separators;
            ++currentArg;
        }

        if (spec.dynamicSeparatorChar)
        {
            auto separatorChar =
                getNth!("separator character", isSomeChar, dchar)(currentArg, args);
            spec.separatorChar = separatorChar;
            spec.dynamicSeparatorChar = false;
            ++currentArg;
        }

        if (currentArg == Args.length && !spec.indexStart)
        {
            // leftover spec?
            enforceFmt(fmt.length == 0,
                text("Orphan format specifier: %", spec.spec));
            break;
        }

        // Format an argument
        // This switch uses a static foreach to generate a jump table.
        // Currently `spec.indexStart` use the special value '0' to signal
        // we should use the current argument. An enhancement would be to
        // always store the index.
        size_t index = currentArg;
        if (spec.indexStart != 0)
            index = spec.indexStart - 1;
        else
            ++currentArg;
    SWITCH: switch (index)
        {
            foreach (i, Tunused; Args)
            {
            case i:
                formatValue(w, args[i], spec);
                if (currentArg < spec.indexEnd)
                    currentArg = spec.indexEnd;
                // A little know feature of format is to format a range
                // of arguments, e.g. `%1:3$` will format the first 3
                // arguments. Since they have to be consecutive we can
                // just use explicit fallthrough to cover that case.
                if (i + 1 < spec.indexEnd)
                {
                    // You cannot goto case if the next case is the default
                    static if (i + 1 < Args.length)
                        goto case;
                    else
                        goto default;
                }
                else
                    break SWITCH;
            }
        default:
            if (spec.indexEnd == spec.indexEnd.max)
                break;
            else if (spec.indexEnd == spec.indexStart)
                throw new FormatException(
                    text("Positional specifier %", spec.indexStart, '$', spec.spec,
                    " index exceeds ", Args.length));
            else
                throw new FormatException(
                    text("Positional specifier %", spec.indexStart, ":", spec.indexEnd, '$', spec.spec,
                    " index exceeds ", Args.length));
        }
    }
    return currentArg;
}

///
@safe pure unittest
{
    import std.array : appender;

    auto writer1 = appender!string();
    formattedWrite(writer1, "%s is the ultimate %s.", 42, "answer");
    assert(writer1[] == "42 is the ultimate answer.");

    auto writer2 = appender!string();
    formattedWrite(writer2, "Increase: %7.2f %%", 17.4285);
    assert(writer2[] == "Increase:   17.43 %");
}

/// ditto
uint formattedWrite(alias fmt, Writer, Args...)(auto ref Writer w, Args args)
if (isSomeString!(typeof(fmt)))
{
    import std.format : checkFormatException;

    alias e = checkFormatException!(fmt, Args);
    static assert(!e, e);
    return .formattedWrite(w, fmt, args);
}

/// The format string can be checked at compile-time:
@safe pure unittest
{
    import std.array : appender;

    auto writer = appender!string();
    writer.formattedWrite!"%d is the ultimate %s."(42, "answer");
    assert(writer[] == "42 is the ultimate answer.");

    // This line doesn't compile, because 3.14 cannot be formatted with %d:
    // writer.formattedWrite!"%d is the ultimate %s."(3.14, "answer");
}

@safe pure unittest
{
    import std.array : appender;

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

@safe pure unittest
{
    import std.array;

    auto w = appender!string();
    formattedWrite(w, "%s %d", "@safe/pure", 42);
    assert(w.data == "@safe/pure 42");
}

@safe pure unittest
{
    char[20] buf;
    auto w = buf[];
    formattedWrite(w, "%s %d", "@safe/pure", 42);
    assert(buf[0 .. $ - w.length] == "@safe/pure 42");
}

@safe pure unittest
{
    import std.algorithm.iteration : map;
    import std.array : appender;

    auto 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");
}

@safe pure unittest
{
    // testing positional parameters
    import std.array : appender;
    import std.exception : collectExceptionMsg;
    import std.format : FormatException;

    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);
    assert(collectExceptionMsg!FormatException(formattedWrite(w, "%1$s, %3$s", 1, 2))
        == "Positional specifier %3$s index exceeds 2");

    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);
}

// https://issues.dlang.org/show_bug.cgi?id=3479
@safe unittest
{
    import std.array : appender;

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

// https://issues.dlang.org/show_bug.cgi?id=6893
@safe unittest
{
    import std.array : appender;

    enum E : ulong { A, B, C }
    auto stream = appender!(char[])();
    formattedWrite(stream, "%s", E.C);
    assert(stream.data == "C");
}

@safe pure unittest
{
    import std.array : appender;

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

@safe pure unittest
{
    // testing raw writes
    import std.array : appender;

    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);
}

@safe unittest
{
    import std.array : appender;
    import std.conv : text, octal;

    auto stream = appender!(char[])();

    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);
    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, "%a %A", 1.32, 6.78f);
    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.0, -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 == "0");
    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");

    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);
    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");

    @trusted void* deadBeef() { return cast(void*) 0xDEADBEEF; }
    stream.clear();
    formattedWrite(stream, "%s", deadBeef());
    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");

    @trusted void ObjectTest()
    {
        Object c = null;
        stream.clear();
        formattedWrite(stream, "%s", c);
        assert(stream.data == "null");
    }
    ObjectTest();

    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);
    //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 ~ "<");
}

@safe unittest
{
    import std.array : appender;
    import std.meta : AliasSeq;

    immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]);
    assert(aa[3] == "hello");
    assert(aa[4] == "betty");

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

    stream.clear();
    formattedWrite(stream, "%s", aa);
}

// https://github.com/dlang/phobos/issues/10699
@safe pure unittest
{
    import std.array : appender;
    auto w = appender!(char[])();

    formattedWrite(w, "%1:$d", 1, 2, 3);
    assert(w.data == "123");
}

/**
Formats a value of any type according to a format specifier and
writes the result to an output range.

More details about how types are formatted, and how the format
specifier influences the outcome, can be found in the definition of a
$(MREF_ALTTEXT format string, std,format).

Params:
    w = an $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) where
        the formatted value is written to
    val = the value to write
    f = a $(REF_ALTTEXT FormatSpec, FormatSpec, std, format, spec) defining the
        format specifier
    Writer = the type of the output range `w`
    T = the type of value `val`
    Char = the character type used for `f`

Throws:
    A $(LREF FormatException) if formatting did not succeed.

Note:
    In theory this function should be `@nogc`. But with the current
    implementation there are some cases where allocations occur.
    See $(REF_ALTTEXT $(D sformat), sformat, std, format) for more details.

See_Also:
    $(LREF formattedWrite) which formats several values at once.
 */
void formatValue(Writer, T, Char)(auto ref Writer w, auto ref T val, scope const ref FormatSpec!Char f)
{
    import std.format : enforceFmt;

    enforceFmt(f.width != f.DYNAMIC && f.precision != f.DYNAMIC
               && f.separators != f.DYNAMIC && !f.dynamicSeparatorChar,
               "Dynamic argument not allowed for `formatValue`");

    formatValueImpl(w, val, f);
}

///
@safe pure unittest
{
    import std.array : appender;
    import std.format.spec : singleSpec;

    auto writer = appender!string();
    auto spec = singleSpec("%08b");
    writer.formatValue(42, spec);
    assert(writer.data == "00101010");

    spec = singleSpec("%2s");
    writer.formatValue('=', spec);
    assert(writer.data == "00101010 =");

    spec = singleSpec("%+14.6e");
    writer.formatValue(42.0, spec);
    assert(writer.data == "00101010 = +4.200000e+01");
}

// https://issues.dlang.org/show_bug.cgi?id=15386
@safe pure unittest
{
    import std.array : appender;
    import std.format.spec : FormatSpec;
    import std.format : FormatException;
    import std.exception : assertThrown;

    auto w = appender!(char[])();
    auto dor = appender!(char[])();
    auto fs = FormatSpec!char("%.*s");
    fs.writeUpToNextSpec(dor);
    assertThrown!FormatException(formatValue(w, 0, fs));

    fs = FormatSpec!char("%*s");
    fs.writeUpToNextSpec(dor);
    assertThrown!FormatException(formatValue(w, 0, fs));

    fs = FormatSpec!char("%,*s");
    fs.writeUpToNextSpec(dor);
    assertThrown!FormatException(formatValue(w, 0, fs));

    fs = FormatSpec!char("%,?s");
    fs.writeUpToNextSpec(dor);
    assertThrown!FormatException(formatValue(w, 0, fs));

    assertThrown!FormatException(formattedWrite(w, "%(%0*d%)", new int[1]));
}

// https://issues.dlang.org/show_bug.cgi?id=22609
@safe pure unittest
{
    static enum State: ubyte { INACTIVE }
    static struct S {
        State state = State.INACTIVE;
        int generation = 1;
        alias state this;
        // DMDBUG: https://issues.dlang.org/show_bug.cgi?id=16657
        auto opEquals(S other) const { return state == other.state && generation == other.generation; }
        auto opEquals(State other) const { return state == other; }
    }

    import std.array : appender;
    import std.format.spec : singleSpec;

    auto writer = appender!string();
    const spec = singleSpec("%s");
    S a;
    writer.formatValue(a, spec);
    assert(writer.data == "0");
}

// https://issues.dlang.org/show_bug.cgi?id=23400
@safe pure unittest
{
    import std.range : nullSink;
    import std.format.spec : singleSpec;

    static struct S
    {
        // non-const opEquals method
        bool opEquals(S rhs) { return false; }
    }

    enum E { a = S() }

    E e;
    auto writer = nullSink;
    const spec = singleSpec("%s");
    writer.formatValue(e, spec);
}
