Thread overview
[Issue 6946] New: Compile-time flags generator
Dec 13, 2011
yebblies
Aug 15, 2012
Marco Leise
Sep 25, 2012
Denis Shelomovskij
November 14, 2011
http://d.puremagic.com/issues/show_bug.cgi?id=6946

           Summary: Compile-time flags generator
           Product: D
           Version: D2
          Platform: All
        OS/Version: All
            Status: NEW
          Severity: enhancement
          Priority: P2
         Component: Phobos
        AssignedTo: nobody@puremagic.com
        ReportedBy: bearophile_hugs@eml.cc


--- Comment #0 from bearophile_hugs@eml.cc 2011-11-13 20:13:10 PST ---
A basic version of a compile-time enum-based bit flags:


import std.traits: isUnsigned;
import std.string: split;
import std.conv: text;
import std.algorithm: canFind;

bool isReservedWord(in string w) {
    string[] reservedWords = "abstract alias align asm
    assert auto body bool break byte case cast catch cdouble cent cfloat
    char class const continue creal dchar debug default delegate delete
    deprecated do double else enum export extern false final finally
    float for foreach foreach_reverse function goto idouble if ifloat
    immutable import in inout int interface invariant ireal is lazy long
    macro mixin module new nothrow null out override package pragma
    private protected public pure real ref return scope shared short
    static struct super switch synchronized template this throw true try
    typedef typeid typeof ubyte ucent uint ulong union unittest ushort
    version void volatile wchar while with __FILE__ __LINE__ __gshared
    __thread __traits".split();
    return canFind(reservedWords, w);
}

string bitFlags(BaseType)(string enumName, string enumItems)
if (isUnsigned!BaseType) {
    assert (enumItems.split().length < (BaseType.sizeof * 8),
            text("BaseType (", BaseType.stringof,
                 ") doesn't have enough bits to represent all the
enumItems."));
    string result = text("enum ", enumName, " : ", BaseType.stringof, " {\n");
    foreach (i, flag; enumItems.split()) {
        assert(!isReservedWord(flag), text("enum '", flag, "' is a D
keyword."));
        result ~= text("  ", flag, " = (1 << ", i, "),\n");
    }
    return result ~ "}";
}

// example usage:
mixin(bitFlags!size_t("MyEnum", "A B C D"));
//mixin(bitFlags!ulong("MyEnum", "A B C D")); // writeln bug

void main() {
    import std.stdio;
    writeln(MyEnum.C);
}


This code lacks many handy features, including safety features when you combine flags with MyEnum.A|MyEnum.B, but for a basic usage, if you have many flags, it seems better than not using it.

Note: in this simple implementation the first enum (here MyENum.A) equals to 2^0 = 1, so there is no enum for zero. Change this if you want.

I'd probably like a function like isReservedWord() too in Phobos. Here the
reservedWords array is not static because I need to call isReservedWord() at
compile time too.

Even if flags are sometimes more complex than the ones this function generates, I think something like this (but improved) is useful in Phobos for the common basic situations.

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
November 14, 2011
http://d.puremagic.com/issues/show_bug.cgi?id=6946



--- Comment #1 from bearophile_hugs@eml.cc 2011-11-14 15:21:15 PST ---
For the C# [Flags] attribute see: http://msdn.microsoft.com/en-us/library/cc138362.aspx http://msdn.microsoft.com/en-us/library/system.flagsattribute.aspx


In D enums EnumBaseType defaults to int:
http://www.d-programming-language.org/enum.html
but for powers of two bit flags I think using an unsigned type is saner, so for
bitFlags I have used uint as default type.

Follows a bit improved version, it always contains a "None" member too, represented with 0:


import std.traits: isUnsigned;
import std.string: split;
import std.conv: text;
import std.algorithm: canFind;

bool isReservedWord(in string w) {
    string[] reservedWords = "abstract alias align asm
    assert auto body bool break byte case cast catch cdouble cent cfloat
    char class const continue creal dchar debug default delegate delete
    deprecated do double else enum export extern false final finally
    float for foreach foreach_reverse function goto idouble if ifloat
    immutable import in inout int interface invariant ireal is lazy long
    macro mixin module new nothrow null out override package pragma
    private protected public pure real ref return scope shared short
    static struct super switch synchronized template this throw true try
    typedef typeid typeof ubyte ucent uint ulong union unittest ushort
    version void volatile wchar while with __FILE__ __LINE__ __gshared
    __thread __traits".split();
    return canFind(reservedWords, w);
}

string bitFlags(BaseType=uint)(string enumName, string enumItems)
if (isUnsigned!BaseType) {
    assert ((enumItems.split().length + 1) < (BaseType.sizeof * 8),
            text("BaseType (", BaseType.stringof,
                 ") doesn't have enough bits to represent all the enumItems
plus None."));
    string result = text("enum ", enumName, " : ", BaseType.stringof, " {\n");
    result ~= text("    None = 0,\n");
    foreach (i, flag; enumItems.split()) {
        assert(!isReservedWord(flag), text("bitFlags: enum member '", flag, "'
is a D keyword."));
        result ~= text("    ", flag, " = (1 << ", i, "),\n");
    }
    return result ~ "}";
}

// example usages ----------------
mixin(bitFlags!size_t("MyFlags", "A B C D"));
//mixin(bitFlags!ulong("MyFlags", "A B C D")); // writeln bug 6892

mixin(bitFlags("Days", "Sunday Monday Tuesday Wednesday Thursday Friday
Saturday"));

void main() {
    import std.stdio;
    writeln(MyFlags.C);
    import std.traits, std.algorithm;
    assert(equal(map!text([EnumMembers!MyFlags]),
                 ["None", "A", "B", "C", "D"]));
    Days meetingDays = Days.Tuesday | Days.Thursday;
}

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
November 14, 2011
http://d.puremagic.com/issues/show_bug.cgi?id=6946


Alex Rønne Petersen <xtzgzorex@gmail.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |xtzgzorex@gmail.com


--- Comment #2 from Alex Rønne Petersen <xtzgzorex@gmail.com> 2011-11-14 15:42:16 PST ---
Please use 'none' instead of 'None' (and so on) to follow Phobos naming
conventions.

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
December 13, 2011
http://d.puremagic.com/issues/show_bug.cgi?id=6946


yebblies <yebblies@gmail.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |yebblies@gmail.com


--- Comment #3 from yebblies <yebblies@gmail.com> 2011-12-13 17:49:43 EST ---
You probably need to turn this into a pull request if you want it reviewed for inclusion in phobos.

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
August 15, 2012
http://d.puremagic.com/issues/show_bug.cgi?id=6946


Marco Leise <Marco.Leise@gmx.de> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |Marco.Leise@gmx.de


--- Comment #4 from Marco Leise <Marco.Leise@gmx.de> 2012-08-15 06:22:24 PDT ---
I think Delphi has the most convenient implementation of sets.

  type
     TWorkDay = (Monday, Tuesday, Wednesday, Thursday, Friday) ;
     TDaySet = set of TWorkDay;
  var
     days : TDaySet;
  begin
     days := [Monday, Friday];

     days := days + [Tuesday, Thursday] - [Friday];

     if Wednesday IN days then ShowMessage('I love Wednesday!') ;

It also uses the 2^n binary notation internally. Any proposed solution should
add safety as well as the basic set operators. As long as it is still easier to
write "set &= ~flag" to remove a flag from a set, it failed its mission.
It would be even better if a set type would be allowed in bindings to C code or
painlessly converted to it's integer representation with "set.val" or similar.
:)

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
September 25, 2012
http://d.puremagic.com/issues/show_bug.cgi?id=6946


Denis Shelomovskij <verylonglogin.reg@gmail.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |verylonglogin.reg@gmail.com


--- Comment #5 from Denis Shelomovskij <verylonglogin.reg@gmail.com> 2012-09-25 23:47:56 MSD ---
Related NG thread: http://forum.dlang.org/thread/k3soo8$2ast$1@digitalmars.com

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
September 26, 2012
http://d.puremagic.com/issues/show_bug.cgi?id=6946



--- Comment #6 from bearophile_hugs@eml.cc 2012-09-26 07:28:19 PDT ---
See also Issue 8727

-- 
Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email
------- You are receiving this mail because: -------