reflective enums

Kevin Bealer kevinbealer at gmail.com
Thu Feb 15 21:28:42 PST 2007


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



More information about the Digitalmars-d mailing list