Issue 6946 - Compile-time flags generator
Summary: Compile-time flags generator
Status: NEW
Alias: None
Product: D
Classification: Unclassified
Component: phobos (show other issues)
Version: D2
Hardware: All All
: P4 enhancement
Assignee: No Owner
URL:
Keywords: bootcamp
Depends on:
Blocks:
 
Reported: 2011-11-13 20:13 UTC by bearophile_hugs
Modified: 2024-12-01 16:14 UTC (History)
6 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description bearophile_hugs 2011-11-13 20:13:10 UTC
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.
Comment 1 bearophile_hugs 2011-11-14 15:21:15 UTC
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;
}
Comment 2 Alex Rønne Petersen 2011-11-14 15:42:16 UTC
Please use 'none' instead of 'None' (and so on) to follow Phobos naming conventions.
Comment 3 yebblies 2011-12-12 22:49:43 UTC
You probably need to turn this into a pull request if you want it reviewed for inclusion in phobos.
Comment 4 Marco Leise 2012-08-15 06:22:24 UTC
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. :)
Comment 5 Denis Shelomovskii 2012-09-25 12:47:56 UTC
Related NG thread:
http://forum.dlang.org/thread/k3soo8$2ast$1@digitalmars.com
Comment 6 bearophile_hugs 2012-09-26 07:28:19 UTC
See also Issue 8727
Comment 7 Nick Treleaven 2014-04-02 09:14:47 UTC
A note about syntax:

> mixin(bitFlags!size_t("MyFlags", "A B C D"))

Wrapping enum code in a mixin template could allow:

mixin BitFlags!size_t("A B C D")) MyFlags;

Or if D mixin syntax was changed similar to alias assignment syntax:

mixin MyFlags = BitFlags!size_t("A B C D"));
Comment 8 bearophile_hugs 2014-04-02 10:19:23 UTC
(In reply to comment #7)
> A note about syntax:
> 
> > mixin(bitFlags!size_t("MyFlags", "A B C D"))
> 
> Wrapping enum code in a mixin template could allow:
> 
> mixin BitFlags!size_t("A B C D")) MyFlags;
> 
> Or if D mixin syntax was changed similar to alias assignment syntax:
> 
> mixin MyFlags = BitFlags!size_t("A B C D"));

Is the name "MyFlags" not useful (as shown in the syntax you quote)?
Comment 9 Nick Treleaven 2014-04-03 07:43:21 UTC
(In reply to comment #8)
> Is the name "MyFlags" not useful (as shown in the syntax you quote)?

It's fine, I just thought it might be good to take advantage of a mixin identifier (I often forget about that feature). It seems a bit cleaner to me than a string argument, although having mixin assignment syntax would make this clearer. Nice idea BTW.

The cleanness is because I like to have identifiers highlighted in the same way rather than have some as strings, so I would prefer to read something like:

mixin BitFlags!q{A, B, C, D} MyFlags;
Comment 10 bearophile_hugs 2014-04-03 09:07:50 UTC
(In reply to comment #9)

> although having mixin assignment syntax would make this
> clearer. Nice idea BTW.

The point of giving it a name in some way (with a string) is because later you can use some trait to ask for its name. I am not suer how important this is.

The idea comes from Python namedtuples:

>>> from collections import namedtuple
>>> Three = namedtuple("Two", "a b")
>>> Three(1, 2)
Two(a=1, b=2)
Comment 11 bearophile_hugs 2014-04-03 09:49:38 UTC
(In reply to comment #10)

> The cleanness is because I like to have identifiers highlighted in the same way
> rather than have some as strings, so I would prefer to read something like:
> 
> mixin BitFlags!q{A, B, C, D} MyFlags;

Is your version using a function like isReservedWord() I have shown in the first posts here?
Comment 12 Nick Treleaven 2014-04-11 11:25:06 UTC
(In reply to bearophile_hugs from comment #11)
> (In reply to comment #10)
> > mixin BitFlags!q{A, B, C, D} MyFlags;

A better reason for this syntax is that is gives a name for ddoc to use for documentation. Unfortunately the members can't be documented though.

> Is your version using a function like isReservedWord() I have shown in the
> first posts here?

I haven't actually implemented it, it could do.
Comment 13 dlangBugzillaToGithub 2024-12-01 16:14:38 UTC
THIS ISSUE HAS BEEN MOVED TO GITHUB

https://github.com/dlang/phobos/issues/9916

DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB