/**
 * A library bitfields utility
 *
 * Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved
 * Authors:   Dennis Korpel
 * License:   $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
 * Source:    $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/common/bitfields.d, common/bitfields.d)
 * Documentation: https://dlang.org/phobos/dmd_common_bitfields.html
 * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/common/bitfields.d
 */
module dmd.common.bitfields;

/**
 * Generate code for bit fields inside a struct/class body
 * Params:
 *   S = type of a struct with only boolean fields, which should become bit fields
 *   T = type of bit fields variable, must have enough bits to store all booleans
 * Returns: D code with a bit fields variable and getter / setter functions
 */
extern (D) string generateBitFields(S, T)()
if (__traits(isUnsigned, T))
{
    string result = "extern (C++) pure nothrow @nogc @safe final {";
    enum structName = __traits(identifier, S);

    string initialValue = "";
    foreach (size_t i, mem; __traits(allMembers, S))
    {
        static assert(is(typeof(__traits(getMember, S, mem)) == bool));
        static assert(i < T.sizeof * 8, "too many fields for bit field storage of type `"~T.stringof~"`");
        enum mask = "(1 << "~i.stringof~")";
        result ~= "
        /// set or get the corresponding "~structName~" member
        bool "~mem~"() const scope { return !!(bitFields & "~mask~"); }
        /// ditto
        bool "~mem~"(bool v)
        {
            v ? (bitFields |= "~mask~") : (bitFields &= ~"~mask~");
            return v;
        }";

        initialValue = (__traits(getMember, S.init, mem) ? "1" : "0") ~ initialValue;
    }
    return result ~ "}\n private "~T.stringof~" bitFields = 0b" ~ initialValue ~ ";\n";
}

///
unittest
{
    static struct B
    {
        bool x;
        bool y;
        bool z = 1;
    }

    static struct S
    {
        mixin(generateBitFields!(B, ubyte));
    }

    S s;
    assert(!s.x);
    s.x = true;
    assert(s.x);
    s.x = false;
    assert(!s.x);

    s.y = true;
    assert(s.y);
    assert(!s.x);
    assert(s.z);
}
