www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - reflective enums

reply Kevin Bealer <kevinbealer gmail.com> writes:
People have occasionally asked here about being able to do things like 
foreach() over the value of the enums.  The following code is probably 
obsolete now that 1.006 is out, but I'm posting it anyway.

I think once I update to the next version of DMD I should be able to cut 
all this down to about 20 lines, but here it is for 1.005.

The Enum template allows you to declare a struct that contains a 
definition of constants (like an enumeration) but also allows you to get 
the strings for the constants and foreach() over the data.

Note: the lines of code in main() represent the entirety of testing
on this code, so it probably has bugs and issues.  Also, checking for 
invalid input to the templates was not important to me in this case.

(I'm writing a toy project where I need (as so often happens) both an 
enum and a corresponding "enumToString" function.  Reading all those 
links from the other day about LISP and LISP macros seems to have given 
me a temporary allergy to 'repeating myself'.  So I figured I would see 
if I can solve the enum + string problem -- this is the result.)

On the subject of 'bloat', if I comment out the template definitinos and 
the contents of main() below, the 'strip'ed version of the binary trims 
by about 1.5 k, which doesn't seem too bad.

  234268 // all code shown below
  232796 // no code, but imports
  174368 // no code or imports

(I don't really care about the 232k -- the C++ binaries I build at work 
are something like 130 MB in debug mode; we have a lot of libraries.)

Kevin

// -*- c++ -*-

import std.stdio;
import std.metastrings;
import std.string;
import std.file;

template Find(char[] A, char[] B) {
     static if (A.length < B.length) {
         const int Find = -1;
     } else static if (A[0..B.length] == B) {
         const int Find = 0;
     } else static if (-1 == Find!(A[1..$], B)) {
         const int Find = -1;
     } else {
         const int Find = 1 + Find!(A[1..$], B);
     }
};

template SplitFirst(char[] A, char[] B) {
     const int Location = Find!(A, B);

     static if (Location == -1) {
         const char[] First = A;
         const char[] Rest = "";
     } else {
         const char[] First = A[0..Location];
         const char[] Rest = A[Location+B.length..$];
     }
};

template ChompSpaces(char[] A) {
     static if (A.length) {
         static if (A[0] == ' ') {
             alias ChompSpaces!(A[1..$]) ChompSpaces;
         } else static if (A[$-1] == ' ') {
             alias ChompSpaces!(A[0..$-1]) ChompSpaces;
         } else {
             alias A ChompSpaces;
         }
     } else {
         const char[] ChompSpaces = "";
     }
};

template SplitChomp(char[] A, char[] B) {
     alias ChompSpaces!(SplitFirst!(A, B).First) First;
     alias ChompSpaces!(SplitFirst!(A, B).Rest) Rest;
}

template EnumName(char[] A) {
     alias SplitChomp!(SplitChomp!(A, ",").First, "=").First EnumName;
}

template EnumAssign(char[] A) {
     alias SplitChomp!(SplitChomp!(A, ",").First, "=").Rest EnumAssign;
}

template EnumValue(char[] A, int next) {
     static if (EnumAssign!(A) == "") {
         const int EnumValue = next;
     } else {
         const int EnumValue =
             mixin(ParseInteger!(EnumAssign!(A)).value);
     }
}

template BuildOneEnum(char[] A, int i) {
     const char[] BuildOneEnum =
         "const int "~EnumName!(A)~" = "~
         ToString!(EnumValue!(A, i))~";\n";
}

template BuildOneCase(char[] A, int i) {
     const char[] BuildOneCase =
         "case "~ToString!(EnumValue!(A, i))~
         ": return \""~EnumName!(A)~"\";\n";
}

template BuildOneApply(char[] A, int i) {
     const char[] BuildOneApply =
         "i="~ToString!(EnumValue!(A, i))~";"~
         " rv=dg(i); if (rv) return rv;";
}

template BuildEnums(char[] A, int i) {
     static if (SplitChomp!(A, ",").Rest.length == 0) {
         const char[] BuildEnums = BuildOneEnum!(A, EnumValue!(A, i));
     } else {
         const char[] BuildEnums =
             BuildOneEnum!(SplitChomp!(A, ",").First, EnumValue!(A, i)) ~
             BuildEnums!(SplitChomp!(A, ",").Rest, EnumValue!(A, i)+1);
     }
}

template BuildEnumCases(char[] A, int i) {
     static if (SplitChomp!(A, ",").Rest.length == 0) {
         const char[] BuildEnumCases =
             BuildOneCase!(A, EnumValue!(A, i));
     } else {
         const char[] BuildEnumCases =
             BuildOneCase!(SplitChomp!(A, ",").First,
                           EnumValue!(A, i)) ~
             BuildEnumCases!(SplitChomp!(A, ",").Rest,
                             EnumValue!(A, i)+1);
     }
}

template BuildEnumSwitch(char[] var, char[] A, int i) {
     const char[] BuildEnumSwitch =
         "switch("~var~") {"~
         BuildEnumCases!(A, i) ~
         "default: "~
         "    throw new Exception(\"enumeration out of range\");"
         "}";
}

template BuildEnumApply(char[] A, int i) {
     static if (SplitChomp!(A, ",").Rest.length == 0) {
         const char[] BuildEnumApply =
             BuildOneApply!(A, EnumValue!(A, i));
     } else {
         const char[] BuildEnumApply =
             BuildOneApply!(SplitChomp!(A, ",").First,
                            EnumValue!(A, i))~
             BuildEnumApply!(SplitChomp!(A, ",").Rest,
                             EnumValue!(A, i)+1);
     }
}

struct Enum(char[] A) {
     mixin(BuildEnums!(A, 1));

     static char[] getString(int x)
     {
         mixin(BuildEnumSwitch!("x", A, 1));
     }

     int opApply(int delegate(inout int) dg)
     {
         int i, rv;
         mixin(BuildEnumApply!(A, 1));
         return 0;
     }
}

int main(char[][] args)
{
     alias Enum!("start, middle, end=10, ps, pps") PState;

     int p = PState.middle;

     writefln("p is %s, with name %s\n", p, PState.getString(p));

     PState P;
     foreach(v; P) {
         writefln("Enum %s has name=%s", v, PState.getString(v));
     }

     return 0;
}
Feb 15 2007
next sibling parent reply Derek Parnell <derek nomail.afraid.org> writes:
On Fri, 16 Feb 2007 00:28:42 -0500, Kevin Bealer wrote:

 
 The Enum template ...
Now we're talking ... I wonder what 1.006 can do <G> -- Derek (skype: derek.j.parnell) Melbourne, Australia "Justice for David Hicks!" 16/02/2007 4:34:51 PM
Feb 15 2007
parent "Andrei Alexandrescu (See Website For Email)" <SeeWebsiteForEmail erdani.org> writes:
Derek Parnell wrote:
 On Fri, 16 Feb 2007 00:28:42 -0500, Kevin Bealer wrote:
 
  
 The Enum template ...
Now we're talking ... I wonder what 1.006 can do <G>
Probably something along the lines: mixin ReflectiveEnum!(int, "a", "b", "c"); That generates int-based enums (enumerated values of arbitrary type will be possible, but hey, one thing at a time), along with parsing and printing code. So it's not about what features are directly useful, but instead the power is in the proverbial level of indirection. I can imagine lots of people could care less about esoteric compile-time stuff, but they'd be glad to use a better enum. Andrei
Feb 15 2007
prev sibling next sibling parent reply renoX <renosky free.fr> writes:
Kevin Bealer Wrote:
 People have occasionally asked here about being able to do things like 
 foreach() over the value of the enums.  The following code is probably 
 obsolete now that 1.006 is out, but I'm posting it anyway.
Very nice, thanks! I was trying to do the same thing (without much success, I'm not used to functional template programming) , so you saved me quite some time.. I have a few remarks: - why do Enum start the values at 1 instead of 0? IMHO it should follow the way 'enum' works otherwise developers may be confused. - why the name getString instead of the usual toString name? - I'm not sure if it's useful to 'optimise' compilation time, but there were a few call to SplitChomp that can be replaced by SplitFirst. - toFullString which returns "<enum name>(<enum value>)" can be useful too maybe, I wanted to do a concatenation reusing toString to avoid duplicating the code, but I didn't manage to, so I added a parameter instead. The code below has the modifications. I wonder if the reflective enums could be integrated into a library, I think that it would be useful. The only downside of this version is that the developer must use int instead of a separate type.. This is probably fixable. renoX import std.stdio; import std.metastrings; template Find(char[] A, char[] B) { static if (A.length < B.length) { const int Find = -1; } else static if (A[0..B.length] == B) { const int Find = 0; } else static if (-1 == Find!(A[1..$], B)) { const int Find = -1; } else { const int Find = 1 + Find!(A[1..$], B); } } template SplitFirst(char[] A, char[] B) { const int Location = Find!(A, B); static if (Location == -1) { const char[] First = A; const char[] Rest = ""; } else { const char[] First = A[0..Location]; const char[] Rest = A[Location+B.length..$]; } } template ChompSpaces(char[] A) { static if (A.length) { static if (A[0] == ' ') { alias ChompSpaces!(A[1..$]) ChompSpaces; } else static if (A[$-1] == ' ') { alias ChompSpaces!(A[0..$-1]) ChompSpaces; } else { alias A ChompSpaces; } } else { const char[] ChompSpaces = ""; } } template SplitChomp(char[] A, char[] B) { alias ChompSpaces!(SplitFirst!(A, B).First) First; alias ChompSpaces!(SplitFirst!(A, B).Rest) Rest; } template EnumName(char[] A) { alias SplitChomp!(SplitFirst!(A, ",").First, "=").First EnumName; } template EnumAssign(char[] A) { alias SplitChomp!(SplitFirst!(A, ",").First, "=").Rest EnumAssign; } template EnumValue(char[] A, int i) { static if (EnumAssign!(A) == "") { const int EnumValue = i; } else { const int EnumValue = mixin(ParseInteger!(EnumAssign!(A)).value); } } template BuildOneEnum(char[] A, int i) { const char[] BuildOneEnum = "const int "~EnumName!(A)~" = "~ ToString!(EnumValue!(A, i))~";\n"; } template BuildEnums(char[] A, int i) { static if (SplitChomp!(A, ",").Rest.length == 0) { const char[] BuildEnums = BuildOneEnum!(A, EnumValue!(A, i)); } else { const char[] BuildEnums = BuildOneEnum!(SplitChomp!(A, ",").First, EnumValue!(A, i)) ~ BuildEnums!(SplitChomp!(A, ",").Rest, EnumValue!(A, i)+1); } } template BuildOneCase(char[] A, int i, bool full) { static if (!full) { const char[] BuildOneCase = "case "~ToString!(EnumValue!(A, i))~ ": return \""~EnumName!(A)~"\";\n"; } else { const char[] BuildOneCase = "case "~ToString!(EnumValue!(A, i))~ ": return \""~EnumName!(A)~"("~ToString!(EnumValue!(A, i))~")\";\n"; } } template BuildEnumCases(char[] A, int i, bool full) { static if (SplitChomp!(A, ",").Rest.length == 0) { const char[] BuildEnumCases = BuildOneCase!(A, EnumValue!(A, i), full); } else { const char[] BuildEnumCases = BuildOneCase!(SplitChomp!(A, ",").First, EnumValue!(A, i), full) ~ BuildEnumCases!(SplitChomp!(A, ",").Rest, EnumValue!(A, i)+1,full); } } template BuildEnumSwitch(char[] A, int i, bool full) { const char[] BuildEnumSwitch = "switch(x) {"~ BuildEnumCases!(A, i, full) ~ "default: "~ " throw new Exception(\"enumeration out of range\");" "}"; } template BuildOneApply(char[] A, int i) { const char[] BuildOneApply = "i="~ToString!(EnumValue!(A, i))~";"~ " rv=dg(i); if (rv) return rv;"; } template BuildEnumApply(char[] A, int i) { static if (SplitChomp!(A, ",").Rest.length == 0) { const char[] BuildEnumApply = BuildOneApply!(A, EnumValue!(A, i)); } else { const char[] BuildEnumApply = BuildOneApply!(SplitChomp!(A, ",").First, EnumValue!(A, i))~ BuildEnumApply!(SplitChomp!(A, ",").Rest, EnumValue!(A, i)+1); } } struct Enum(char[] A) { mixin(BuildEnums!(A, 0)); static char[] toString(int x) { mixin(BuildEnumSwitch!(A, 0, false)); } static char[] toFullString(int x) { mixin(BuildEnumSwitch!(A, 0, true)); } int opApply(int delegate(inout int) dg) { int i, rv; mixin(BuildEnumApply!(A, 0)); return 0; } } int main(char[][] args) { alias Enum!("start, middle, end=10, ps, pps,") PState; int s = PState.start; int m = PState.middle; writefln("s is %s, with name %s %s\n", s, PState.toString(s), PState.toFullString(s)); writefln("m is %s, with name %s %s\n", m, PState.toString(m), PState.toFullString(m)); PState P; foreach(v; P) { writefln("Enum %s has name=%s and %s", v, PState.toString(v), PState.toFullString(v)); } return 0; }
Feb 16 2007
parent reply Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from renoX (renosky free.fr)'s article
 Kevin Bealer Wrote:
...
 - why do Enum start the values at 1 instead of 0?

 IMHO it should follow the way 'enum' works otherwise developers may be
confused.
You're right -- this should match the default enum{} behavior. I actually did it this way as a kind of shorthand for myself. When I write an enum {} I usually add a starting value with a name like "e_None" or something. You could think of it as comparable to the floating point NaN. If I don't set an enumerated type, I want it to have an out-of-bound value.
 - why the name getString instead of the usual toString name?
That could be changed too; I think of toString() as an object method, and when adding a static method, I figured I should use a different name to avoid conflicting with the method. I didn't really check whether there is a real conflict on this. There is kind of a strangeness with the way I defined this in that you creating instances of the Enum!(...) struct is not useful. The type is only really interesting for its static properties.
 - I'm not sure if it's useful to 'optimise' compilation time, but there were a
few call to SplitChomp that can be replaced by SplitFirst.
 - toFullString which returns "<enum name>(<enum value>)" can be useful too
maybe, I wanted to do a concatenation reusing toString to avoid duplicating the code, but I didn't manage to, so I added a parameter instead.
 The code below has the modifications.
 I wonder if the reflective enums could be integrated into a library, I think
that it would be useful.
 The only downside of this version is that the developer must use int instead of
a separate type.. This is probably fixable. These modifications make sense. I'm thinking I can write a much smaller and more straightforward version with 1.006.
 renoX
 import std.stdio;
 import std.metastrings;
 template Find(char[] A, char[] B) {
     static if (A.length < B.length)
 	{
         const int Find = -1;
     } else static if (A[0..B.length] == B) {
         const int Find = 0;
     } else static if (-1 == Find!(A[1..$], B)) {
         const int Find = -1;
     } else {
         const int Find = 1 + Find!(A[1..$], B);
     }
 }
 template SplitFirst(char[] A, char[] B) {
     const int Location = Find!(A, B);
     static if (Location == -1) {
         const char[] First = A;
         const char[] Rest = "";
     } else {
         const char[] First = A[0..Location];
         const char[] Rest = A[Location+B.length..$];
     }
 }
 template ChompSpaces(char[] A) {
     static if (A.length) {
         static if (A[0] == ' ') {
             alias ChompSpaces!(A[1..$]) ChompSpaces;
         } else static if (A[$-1] == ' ') {
             alias ChompSpaces!(A[0..$-1]) ChompSpaces;
         } else {
             alias A ChompSpaces;
         }
     } else {
         const char[] ChompSpaces = "";
     }
 }
 template SplitChomp(char[] A, char[] B) {
     alias ChompSpaces!(SplitFirst!(A, B).First) First;
     alias ChompSpaces!(SplitFirst!(A, B).Rest) Rest;
 }
 template EnumName(char[] A) {
     alias SplitChomp!(SplitFirst!(A, ",").First, "=").First EnumName;
 }
 template EnumAssign(char[] A) {
     alias SplitChomp!(SplitFirst!(A, ",").First, "=").Rest EnumAssign;
 }
 template EnumValue(char[] A, int i) {
     static if (EnumAssign!(A) == "") {
         const int EnumValue = i;
     } else {
         const int EnumValue =
             mixin(ParseInteger!(EnumAssign!(A)).value);
     }
 }
 template BuildOneEnum(char[] A, int i) {
     const char[] BuildOneEnum =
         "const int "~EnumName!(A)~" = "~
         ToString!(EnumValue!(A, i))~";\n";
 }
 template BuildEnums(char[] A, int i) {
     static if (SplitChomp!(A, ",").Rest.length == 0) {
         const char[] BuildEnums = BuildOneEnum!(A, EnumValue!(A, i));
     } else {
         const char[] BuildEnums =
             BuildOneEnum!(SplitChomp!(A, ",").First, EnumValue!(A, i)) ~
             BuildEnums!(SplitChomp!(A, ",").Rest, EnumValue!(A, i)+1);
     }
 }
 template BuildOneCase(char[] A, int i, bool full) {
 	static if (!full) {
 	    const char[] BuildOneCase =
     	    "case "~ToString!(EnumValue!(A, i))~
         	": return \""~EnumName!(A)~"\";\n";
 	} else {
 	    const char[] BuildOneCase =
     	    "case "~ToString!(EnumValue!(A, i))~
         	": return \""~EnumName!(A)~"("~ToString!(EnumValue!(A, i))~")\";\n";
 	}
 }
 template BuildEnumCases(char[] A, int i, bool full) {
     static if (SplitChomp!(A, ",").Rest.length == 0) {
         const char[] BuildEnumCases =
             BuildOneCase!(A, EnumValue!(A, i), full);
     } else {
         const char[] BuildEnumCases =
             BuildOneCase!(SplitChomp!(A, ",").First,
                           EnumValue!(A, i), full) ~
             BuildEnumCases!(SplitChomp!(A, ",").Rest,
                             EnumValue!(A, i)+1,full);
     }
 }
 template BuildEnumSwitch(char[] A, int i, bool full) {
     const char[] BuildEnumSwitch =
         "switch(x) {"~
         BuildEnumCases!(A, i, full) ~
         "default: "~
         "    throw new Exception(\"enumeration out of range\");"
         "}";
 }
 template BuildOneApply(char[] A, int i) {
     const char[] BuildOneApply =
         "i="~ToString!(EnumValue!(A, i))~";"~
         " rv=dg(i); if (rv) return rv;";
 }
 template BuildEnumApply(char[] A, int i) {
     static if (SplitChomp!(A, ",").Rest.length == 0) {
         const char[] BuildEnumApply =
             BuildOneApply!(A, EnumValue!(A, i));
     } else {
         const char[] BuildEnumApply =
             BuildOneApply!(SplitChomp!(A, ",").First,
                            EnumValue!(A, i))~
             BuildEnumApply!(SplitChomp!(A, ",").Rest,
                             EnumValue!(A, i)+1);
     }
 }
 struct Enum(char[] A)
 {
     mixin(BuildEnums!(A, 0));
     static char[] toString(int x)
     {
         mixin(BuildEnumSwitch!(A, 0, false));
     }
     static char[] toFullString(int x)
     {
         mixin(BuildEnumSwitch!(A, 0, true));
     }
     int opApply(int delegate(inout int) dg)
     {
         int i, rv;
         mixin(BuildEnumApply!(A, 0));
         return 0;
     }
 }
 int main(char[][] args)
 {
     alias Enum!("start, middle, end=10, ps, pps,") PState;
     int s = PState.start;
 	int m = PState.middle;
     writefln("s is %s, with name %s %s\n", s, PState.toString(s),
PState.toFullString(s));
     writefln("m is %s, with name %s %s\n", m, PState.toString(m),
PState.toFullString(m));
     PState P;
     foreach(v; P) {
         writefln("Enum %s has name=%s and %s", v, PState.toString(v),
PState.toFullString(v));
     }
     return 0;
 }
Feb 16 2007
parent reply renoX <renosky free.fr> writes:
Kevin Bealer a écrit :
 == Quote from renoX (renosky free.fr)'s article
 - why the name getString instead of the usual toString name?
That could be changed too; I think of toString() as an object method, and when adding a static method, I figured I should use a different name to avoid conflicting with the method. I didn't really check whether there is a real conflict on this.
From my testing, it doesn't trigger a conflict, but I've only tested it inside one file.
 There is kind of a strangeness with the way I defined this in that you
 creating instances of the Enum!(...) struct is not useful.  The type is
 only really interesting for its static properties.
About this I was wondering if it wouldn't be less strange to do a template like this: // expected usage: DefEnum!("enum ListEnumFoo {A,B=1};") template DefEnum(char[] def_enum) { mixin(def_enum); static toString(EnumType!(def_enum) x) { // function body similar to the inital get_String() } } Advantages: -The reflective enum type is really an enum type, so it acts like one. -No weird struct. -When(If) I can convince Walther that writef("foo %s",x) means really writef("foo "~x.toString()); we could write: writef("enum x value is %d and name is %s\n",x,x); and have the correct result, because toString(ListEnumFoo x) would hide toString(int x) {x being an enum variable from type ListEnumFoo). Disadvantage: No easy way to iterate among the enum values: we cannot do foreach(v; ListEnumFoo) because now ListEnumFoo is an enum not a struct.. And we cannot pass an enum type as a parameter, other it would be easy to define function 'keys' and 'values' (like for associative arrays), so we'd need a dummy parameter, i.e you wouldn't be able to write foreach (v; ListEnumFoo) {} and neither foreach (v; ListEnumFoo.values()) {} but foreach (v; ListEnumFoo.A.values()) {} could perhaps work. What do you think about this other way to do it? Regards, renoX
Feb 16 2007
next sibling parent reply janderson <askme me.com> writes:
renoX wrote:
 Kevin Bealer a écrit :
 == Quote from renoX (renosky free.fr)'s article
 - why the name getString instead of the usual toString name?
That could be changed too; I think of toString() as an object method, and when adding a static method, I figured I should use a different name to avoid conflicting with the method. I didn't really check whether there is a real conflict on this.
From my testing, it doesn't trigger a conflict, but I've only tested it inside one file.
 There is kind of a strangeness with the way I defined this in that you
 creating instances of the Enum!(...) struct is not useful.  The type is
 only really interesting for its static properties.
About this I was wondering if it wouldn't be less strange to do a template like this: // expected usage: DefEnum!("enum ListEnumFoo {A,B=1};")
Advantage: you would be able to reuse the code as a sub-set of something else like serialization or another language that uses the same enum syntax. Disadvantage: Code is harder to understand. Code runs slower. -Joel -Joel
Feb 16 2007
parent reply janderson <askme me.com> writes:
janderson wrote:
 renoX wrote:
 Kevin Bealer a écrit :
 == Quote from renoX (renosky free.fr)'s article
 - why the name getString instead of the usual toString name?
That could be changed too; I think of toString() as an object method, and when adding a static method, I figured I should use a different name to avoid conflicting with the method. I didn't really check whether there is a real conflict on this.
From my testing, it doesn't trigger a conflict, but I've only tested it inside one file.
 There is kind of a strangeness with the way I defined this in that you
 creating instances of the Enum!(...) struct is not useful.  The type is
 only really interesting for its static properties.
About this I was wondering if it wouldn't be less strange to do a template like this: // expected usage: DefEnum!("enum ListEnumFoo {A,B=1};")
Advantage: you would be able to reuse the code as a sub-set of something else like serialization or another language that uses the same enum syntax. Disadvantage: Code is harder to understand. Code runs slower.
Correction: The interpreter code is harder to understand.
 
 -Joel
 
 -Joel
Feb 16 2007
parent renoX <renosky free.fr> writes:
janderson a écrit :
 janderson wrote:
 renoX wrote:
 Kevin Bealer a écrit :
 == Quote from renoX (renosky free.fr)'s article
 - why the name getString instead of the usual toString name?
That could be changed too; I think of toString() as an object method, and when adding a static method, I figured I should use a different name to avoid conflicting with the method. I didn't really check whether there is a real conflict on this.
From my testing, it doesn't trigger a conflict, but I've only tested it inside one file.
 There is kind of a strangeness with the way I defined this in that you
 creating instances of the Enum!(...) struct is not useful.  The type is
 only really interesting for its static properties.
About this I was wondering if it wouldn't be less strange to do a template like this: // expected usage: DefEnum!("enum ListEnumFoo {A,B=1};")
Advantage: you would be able to reuse the code as a sub-set of something else like serialization or another language that uses the same enum syntax. Disadvantage: Code is harder to understand. Code runs slower.
Correction: The interpreter code is harder to understand.
Thanks for the correction, your initial remark was puzzling me. That said why do you think the interpreter code is harder to understand? In the template, the part printing the enum value is exactly the same as before with just two added (very simple) templates one to get the enum typename, another to get the enum body. For some reason I've lost the code, I'll post it on Monday, but 90% of the code is still kevin bealer's code, so it doesn't look very different than before. Regards, renoX
 
 -Joel

 -Joel
Feb 16 2007
prev sibling parent Kevin Bealer <kevinbealer gmail.com> writes:
renoX wrote:
 Kevin Bealer a écrit :
 == Quote from renoX (renosky free.fr)'s article
 - why the name getString instead of the usual toString name?
That could be changed too; I think of toString() as an object method, and when adding a static method, I figured I should use a different name to avoid conflicting with the method. I didn't really check whether there is a real conflict on this.
From my testing, it doesn't trigger a conflict, but I've only tested it inside one file.
 There is kind of a strangeness with the way I defined this in that you
 creating instances of the Enum!(...) struct is not useful.  The type is
 only really interesting for its static properties.
About this I was wondering if it wouldn't be less strange to do a template like this: // expected usage: DefEnum!("enum ListEnumFoo {A,B=1};") template DefEnum(char[] def_enum) { mixin(def_enum); static toString(EnumType!(def_enum) x) { // function body similar to the inital get_String() } } Advantages: -The reflective enum type is really an enum type, so it acts like one. -No weird struct. -When(If) I can convince Walther that writef("foo %s",x) means really writef("foo "~x.toString()); we could write: writef("enum x value is %d and name is %s\n",x,x); and have the correct result, because toString(ListEnumFoo x) would hide toString(int x) {x being an enum variable from type ListEnumFoo). Disadvantage: No easy way to iterate among the enum values: we cannot do foreach(v; ListEnumFoo) because now ListEnumFoo is an enum not a struct.. And we cannot pass an enum type as a parameter, other it would be easy to define function 'keys' and 'values' (like for associative arrays), so we'd need a dummy parameter, i.e you wouldn't be able to write foreach (v; ListEnumFoo) {} and neither foreach (v; ListEnumFoo.values()) {} but foreach (v; ListEnumFoo.A.values()) {} could perhaps work. What do you think about this other way to do it? Regards, renoX
I don't like the extra syntax -- in your example, the word 'enum' appears three times. I also like the ability to foreach() over an enum. But this has made me think of another idea that adds some of the "enum"-ness back to the struct. I'll post it if I can make it work. Kevin
Feb 16 2007
prev sibling next sibling parent reply janderson <askme me.com> writes:
Kevin Bealer wrote:
 int main(char[][] args)
 {
     alias Enum!("start, middle, end=10, ps, pps") PState;
 
     int p = PState.middle;
 
     writefln("p is %s, with name %s\n", p, PState.getString(p));
 
     PState P;
     foreach(v; P) {
         writefln("Enum %s has name=%s", v, PState.getString(v));
     }
 
     return 0;
 }
Nice one man! -Joel
Feb 16 2007
parent Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from janderson (askme me.com)'s article
 Kevin Bealer wrote:
 int main(char[][] args)
 {
     alias Enum!("start, middle, end=10, ps, pps") PState;

     int p = PState.middle;

     writefln("p is %s, with name %s\n", p, PState.getString(p));

     PState P;
     foreach(v; P) {
         writefln("Enum %s has name=%s", v, PState.getString(v));
     }

     return 0;
 }
Nice one man! -Joel
Thanks! I should say as a formality, I release all this into the public domain etc. Kevin
Feb 16 2007
prev sibling parent reply renoX <renosky free.fr> writes:
Kevin Bealer Wrote:

 renoX wrote:
 Kevin Bealer a écrit :
 == Quote from renoX (renosky free.fr)'s article
 - why the name getString instead of the usual toString name?
That could be changed too; I think of toString() as an object method, and when adding a static method, I figured I should use a different name to avoid conflicting with the method. I didn't really check whether there is a real conflict on this.
From my testing, it doesn't trigger a conflict, but I've only tested it inside one file.
 There is kind of a strangeness with the way I defined this in that you
 creating instances of the Enum!(...) struct is not useful.  The type is
 only really interesting for its static properties.
About this I was wondering if it wouldn't be less strange to do a template like this: // expected usage: DefEnum!("enum ListEnumFoo {A,B=1};") template DefEnum(char[] def_enum) { mixin(def_enum); static toString(EnumType!(def_enum) x) { // function body similar to the inital get_String() } } Advantages: -The reflective enum type is really an enum type, so it acts like one. -No weird struct. -When(If) I can convince Walther that writef("foo %s",x) means really writef("foo "~x.toString()); we could write: writef("enum x value is %d and name is %s\n",x,x); and have the correct result, because toString(ListEnumFoo x) would hide toString(int x) {x being an enum variable from type ListEnumFoo). Disadvantage: No easy way to iterate among the enum values: we cannot do foreach(v; ListEnumFoo) because now ListEnumFoo is an enum not a struct.. And we cannot pass an enum type as a parameter, other it would be easy to define function 'keys' and 'values' (like for associative arrays), so we'd need a dummy parameter, i.e you wouldn't be able to write foreach (v; ListEnumFoo) {} and neither foreach (v; ListEnumFoo.values()) {} but foreach (v; ListEnumFoo.A.values()) {} could perhaps work. What do you think about this other way to do it? Regards, renoX
I don't like the extra syntax -- in your example, the word 'enum' appears three times.
This is easy to fix..
 I also like the ability to foreach() over an enum. 
 But this has made me think of another idea that adds some of the 
 "enum"-ness back to the struct.  I'll post it if I can make it work. 
 Kevin
How about we discuss our usecase first, then we could work on the API for declaration, use, print and foreach. Given our initial work as a basis, the implementation then should be easy either using templates or compile-time functions (I've seen your implementation and I must admit that I'm a bit disappointed that it doesn't look that much better than the one which used templates). I'll open a new thread so that the discussion about the design is seen by everyone, hopefully some will contribute. renoX
Feb 19 2007
parent Kevin Bealer <kevinbealer gmail.com> writes:
renoX wrote:
 Kevin Bealer Wrote:
 
 renoX wrote:
 Kevin Bealer a écrit :
 == Quote from renoX (renosky free.fr)'s article
 - why the name getString instead of the usual toString name?
That could be changed too; I think of toString() as an object method, and when adding a static method, I figured I should use a different name to avoid conflicting with the method. I didn't really check whether there is a real conflict on this.
From my testing, it doesn't trigger a conflict, but I've only tested it inside one file.
 There is kind of a strangeness with the way I defined this in that you
 creating instances of the Enum!(...) struct is not useful.  The type is
 only really interesting for its static properties.
About this I was wondering if it wouldn't be less strange to do a template like this: // expected usage: DefEnum!("enum ListEnumFoo {A,B=1};") template DefEnum(char[] def_enum) { mixin(def_enum); static toString(EnumType!(def_enum) x) { // function body similar to the inital get_String() } } Advantages: -The reflective enum type is really an enum type, so it acts like one. -No weird struct. -When(If) I can convince Walther that writef("foo %s",x) means really writef("foo "~x.toString()); we could write: writef("enum x value is %d and name is %s\n",x,x); and have the correct result, because toString(ListEnumFoo x) would hide toString(int x) {x being an enum variable from type ListEnumFoo). Disadvantage: No easy way to iterate among the enum values: we cannot do foreach(v; ListEnumFoo) because now ListEnumFoo is an enum not a struct.. And we cannot pass an enum type as a parameter, other it would be easy to define function 'keys' and 'values' (like for associative arrays), so we'd need a dummy parameter, i.e you wouldn't be able to write foreach (v; ListEnumFoo) {} and neither foreach (v; ListEnumFoo.values()) {} but foreach (v; ListEnumFoo.A.values()) {} could perhaps work. What do you think about this other way to do it? Regards, renoX
I don't like the extra syntax -- in your example, the word 'enum' appears three times.
This is easy to fix..
 I also like the ability to foreach() over an enum. 
 But this has made me think of another idea that adds some of the 
 "enum"-ness back to the struct.  I'll post it if I can make it work. 
 Kevin
How about we discuss our usecase first, then we could work on the API for declaration, use, print and foreach. Given our initial work as a basis, the implementation then should be easy either using templates or compile-time functions (I've seen your implementation and I must admit that I'm a bit disappointed that it doesn't look that much better than the one which used templates). I'll open a new thread so that the discussion about the design is seen by everyone, hopefully some will contribute. renoX
Okay. A lot of the awkwardness in the code for the second one is due to limitations in the current CTFE. Things like char[][], functions like split(), format(), replace(), and so on from std.strings, would have made the code much simpler. I think in all these cases it is a matter of time. It's quite amazing that the CTFE stuff was added to DMD as quickly as it was but it has a few limits yet and its amazing how much of the language and library even a simple case like this uses. Kevin
Feb 19 2007