// Written in the D programming language.

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

It provides two functions for reading formatted input: $(LREF
unformatValue) and $(LREF formattedRead). The former reads a single
value. The latter reads several values at once and matches the
characters found between format specifiers.

Parameters are ignored, except for the ones consisting of a single
$(B '*'). See $(LREF formattedRead) for more information.

A space outside of a format specifier has a special meaning: it
matches any sequence of whitespace characters, not just a single
space.

The following combinations of format characters and types are
available:

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

Below are highlighted examples on how these combinations are used
with $(LREF unformatValue), however, they apply for $(LREF
formattedRead) also

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/read.d)
 */
module std.format.read;

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

    auto str = "false";
    auto spec = singleSpec("%s");
    assert(str.unformatValue!bool(spec) == false);

    str = "1";
    spec = singleSpec("%d");
    assert(str.unformatValue!bool(spec) == true);
}

/// Null values
@safe pure unittest
{
    import std.format.spec : singleSpec;

    auto str = "null";
    auto spec = singleSpec("%s");
    assert(str.unformatValue!(typeof(null))(spec) == null);
}

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

    // signed decimal values
    auto str = "123";
    auto spec = singleSpec("%s");
    assert(str.unformatValue!int(spec) == 123);

    // hexadecimal values
    str = "ABC";
    spec = singleSpec("%X");
    assert(str.unformatValue!int(spec) == 2748);

    // octal values
    str = "11610";
    spec = singleSpec("%o");
    assert(str.unformatValue!int(spec) == 5000);

    // raw read, depends on endianess
    str = "\x75\x01";
    spec = singleSpec("%r");
    auto result = str.unformatValue!short(spec);
    assert(result == 373 /* little endian */ || result == 29953 /* big endian */ );
}

/// Floating point numbers
@safe pure unittest
{
    import std.format.spec : singleSpec;
    import std.math.operations : isClose;

    // natural notation
    auto str = "123.456";
    auto spec = singleSpec("%s");
    assert(str.unformatValue!double(spec).isClose(123.456));

    // scientific notation
    str = "1e17";
    spec = singleSpec("%e");
    assert(str.unformatValue!double(spec).isClose(1e17));

    // raw read, depends on endianess
    str = "\x40\x00\x00\xBF";
    spec = singleSpec("%r");
    auto result = str.unformatValue!float(spec);
    assert(isClose(result, -0.5) /* little endian */ || isClose(result, 2.0) /* big endian */ );
}

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

    // only the first character is read
    auto str = "abc";
    auto spec = singleSpec("%s");
    assert(str.unformatValue!char(spec) == 'a');

    // using a numerical format character treats the read number as unicode code point
    str = "65";
    spec = singleSpec("%d");
    assert(str.unformatValue!char(spec) == 'A');

    str = "41";
    spec = singleSpec("%x");
    assert(str.unformatValue!char(spec) == 'A');

    str = "10003";
    spec = singleSpec("%d");
    assert(str.unformatValue!dchar(spec) == '✓');
}

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

    // string value
    string str = "aaa";
    auto spec = singleSpec("%s");
    assert(str.unformatValue!(dchar[])(spec) == "aaa"d);

    // fixed size array with characters
    str = "aaa";
    spec = singleSpec("%s");
    dchar[3] ret = ['a', 'a', 'a'];
    assert(str.unformatValue!(dchar[3])(spec) == ret);

    // dynamic array
    str = "[1, 2, 3, 4]";
    spec = singleSpec("%s");
    assert(str.unformatValue!(int[])(spec) == [1, 2, 3, 4]);

    // fixed size array with integers
    str = "[1, 2, 3, 4]";
    spec = singleSpec("%s");
    int[4] ret2 = [1, 2, 3, 4];
    assert(str.unformatValue!(int[4])(spec) == ret2);

    // compound specifiers can be used for more control
    str = "1,2,3";
    spec = singleSpec("%(%s,%)");
    assert(str.unformatValue!(int[])(spec) == [1, 2, 3]);

    str = "cool";
    spec = singleSpec("%(%c%)");
    assert(str.unformatValue!(char[])(spec) == ['c', 'o', 'o', 'l']);
}

/// Associative arrays
@safe pure unittest
{
    import std.format.spec : singleSpec;

    // as single value
    auto str = `["one": 1, "two": 2]`;
    auto spec = singleSpec("%s");
    assert(str.unformatValue!(int[string])(spec) == ["one": 1, "two": 2]);

    // with compound specifier for more control
    str = "1/1, 2/4, 3/9";
    spec = singleSpec("%(%d/%d%|, %)");
    assert(str.unformatValue!(int[int])(spec) == [1: 1, 2: 4, 3: 9]);
}

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

/**
Reads an input range according to a format string and stores the read
values into its arguments.

Format specifiers with format character $(B 'd'), $(B 'u') and $(B
'c') can take a $(B '*') parameter for skipping values.

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

Params:
    r = an $(REF_ALTTEXT input range, isInputRange, std, range, primitives),
        where the formatted input is read from
    fmt = a $(MREF_ALTTEXT format string, std,format)
    args = a variadic list of arguments where the read values are stored
    Range = the type of the input range `r`
    Char = the character type used for `fmt`
    Args = a variadic list of types of the arguments

Returns:
    The number of variables filled. If the input range `r` ends early,
    this number will be less than the number of variables provided.

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

Note:
    For backward compatibility the arguments `args` can be given as pointers
    to that variable, but it is not recommended to do so, because this
    option might be removed in the future.
 */
uint formattedRead(Range, Char, Args...)(auto ref Range r, const(Char)[] fmt, auto ref Args args)
{
    import std.format : enforceFmt;
    import std.range.primitives : empty;
    import std.traits : isPointer;
    import std.typecons : isTuple;

    auto spec = FormatSpec!Char(fmt);
    static if (!Args.length)
    {
        spec.readUpToNextSpec(r);
        enforceFmt(spec.trailing.empty, "Trailing characters in formattedRead format string");
        return 0;
    }
    else
    {
        enum hasPointer = isPointer!(typeof(args[0]));

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

        static if (hasPointer)
            alias A = typeof(*args[0]);
        else
            alias A = typeof(args[0]);

        static if (isTuple!A)
        {
            foreach (i, T; A.Types)
            {
                static if (hasPointer)
                    (*args[0])[i] = unformatValue!(T)(r, spec);
                else
                    args[0][i] = unformatValue!(T)(r, spec);
                skipUnstoredFields();
            }
        }
        else
        {
            static if (hasPointer)
                *args[0] = unformatValue!(A)(r, spec);
            else
                args[0] = unformatValue!(A)(r, spec);
        }
        return 1 + formattedRead(r, spec.trailing, args[1 .. $]);
    }
}

/// ditto
uint formattedRead(alias fmt, Range, Args...)(auto ref Range r, auto ref Args args)
if (isSomeString!(typeof(fmt)))
{
    import std.format : checkFormatException;
    import std.meta : staticMap;
    import std.typecons : Tuple;


    // formattedRead supports std.typecons.Tuple
    // however, checkFormatException does not
    // this means that all std.typecons.Tuple's types in Args must be unwrapped
    // and passed to checkFormatException
    template Flatten(T)
    {
        static if (is(T : Tuple!Args, Args...))
            alias Flatten = Args;
        else
            alias Flatten = T;
    }

    alias e = checkFormatException!(fmt, staticMap!(Flatten, Args));
    static assert(!e, e);
    return .formattedRead(r, fmt, args);
}

///
@safe pure unittest
{
    string object;
    char cmp;
    int value;

    assert(formattedRead("angle < 36", "%s %c %d", object, cmp, value) == 3);
    assert(object == "angle");
    assert(cmp == '<');
    assert(value == 36);

    // reading may end early:
    assert(formattedRead("length >", "%s %c %d", object, cmp, value) == 2);
    assert(object == "length");
    assert(cmp == '>');
    // value is not changed:
    assert(value == 36);
}

/// The format string can be checked at compile-time:
@safe pure unittest
{
    string a;
    int b;
    double c;

    assert("hello!124:34.5".formattedRead!"%s!%s:%s"(a, b, c) == 3);
    assert(a == "hello");
    assert(b == 124);
    assert(c == 34.5);
}

/// Skipping values
@safe pure unittest
{
    string item;
    double amount;

    assert("orange: (12%) 15.25".formattedRead("%s: (%*d%%) %f", item, amount) == 2);
    assert(item == "orange");
    assert(amount == 15.25);

    // can also be used with tuples
    import std.typecons : Tuple;

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

// https://issues.dlang.org/show_bug.cgi?id=23600
@safe pure unittest
{
    import std.typecons : Tuple, tuple;

    string h, w;
    Tuple!(int, float) t;

    assert("hello 1 2.34 world".formattedRead!"%s %d %f %s"(h, t, w) == 3);
    assert(h == "hello");
    assert(t == tuple(1, 2.34f));
    assert(w == "world");
}

@safe unittest
{
    import std.math.operations : isClose;
    import std.math.traits : isNaN;
    import std.range.primitives : empty;

    string s = " 1.2 3.4 ";
    double x, y, z;
    assert(formattedRead(s, " %s %s %s ", x, y, z) == 2);
    assert(s.empty);
    assert(isClose(x, 1.2));
    assert(isClose(y, 3.4));
    assert(isNaN(z));
}

// for backwards compatibility
@safe pure 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);

    // mix pointers and auto-ref
    s = "world!200:42.25";
    formattedRead(s, "%s!%s:%s", a, &b, &c);
    assert(a == "world" && b == 200 && c == 42.25);

    s = "world1!201:42.5";
    formattedRead(s, "%s!%s:%s", &a, &b, c);
    assert(a == "world1" && b == 201 && c == 42.5);

    s = "world2!202:42.75";
    formattedRead(s, "%s!%s:%s", a, b, &c);
    assert(a == "world2" && b == 202 && c == 42.75);
}

// for backwards compatibility
@safe pure unittest
{
    import std.math.operations : isClose;
    import std.math.traits : isNaN;
    import std.range.primitives : empty;

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

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

@safe pure 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);
}

@safe pure unittest
{
    union B
    {
        char[int.sizeof] untyped;
        int typed;
    }

    B b;
    b.typed = 5;
    char[] input = b.untyped[];
    int witness;
    formattedRead(input, "%r", &witness);
    assert(witness == b.typed);
}

@safe pure 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);
}

@safe pure unittest
{
    import std.typecons : Tuple;

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

@safe pure unittest
{
    string line;

    char c1, c2;

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

@safe pure unittest
{
    string line;

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

@safe pure 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"]);
}

@safe pure unittest
{
    import std.exception : assertThrown;

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

@safe pure unittest
{
    import std.exception : assertThrown;
    import std.format : FormatException;

    string input;

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

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

@safe pure 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");
}

@safe pure 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]);
}

// test rvalue using
@safe pure unittest
{
    string[int] aa1;
    formattedRead!("%s")(`[1:"hello", 2:"world"]`, aa1);
    assert(aa1 == [1:"hello", 2:"world"]);

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

/**
Reads a value from the given _input range and converts it according to a
format specifier.

Params:
    input = the $(REF_ALTTEXT input range, isInputRange, std, range, primitives),
            to read from
    spec = a $(MREF_ALTTEXT format string, std,format)
    T = type to return
    Range = the type of the input range `input`
    Char = the character type used for `spec`

Returns:
    A value from `input` of type `T`.

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

See_Also:
    $(REF parse, std, conv) and $(REF to, std, conv)
 */
T unformatValue(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec)
{
    return unformatValueImpl!T(input, spec);
}

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

    string s = "42";
    auto spec = singleSpec("%s");
    assert(unformatValue!int(s, spec) == 42);
}

// https://issues.dlang.org/show_bug.cgi?id=7241
@safe pure unittest
{
    string input = "a";
    auto spec = FormatSpec!char("%s");
    spec.readUpToNextSpec(input);
    auto result = unformatValue!(dchar[1])(input, spec);
    assert(result[0] == 'a');
}

// https://issues.dlang.org/show_bug.cgi?id=20393
@safe pure unittest
{
    import std.exception : assertThrown;
    string str = "foo 12a-buzz";
    string a, c;
    int b;
    assertThrown(formattedRead(str, "%s %d-%s", &a, &b, &c));
}

// https://issues.dlang.org/show_bug.cgi?id=18051
@safe pure unittest
{
    import std.format : format;

    enum Op { lt, gt, eq }

    auto s = format!"%s"(Op.lt);
    Op op;
    assert(formattedRead!"%s"(s, op) == 1);
    assert(op == Op.lt);
}
