I've recently stumbled upon this little macro by Justo Delgado. It takes an "enum abstract" and sets the values of it's constant-fields incrementally. This brings up an interesting point.
See, in C, you can leave out the values and have them assigned automatically (0, 1, 2, ...), set the values manually, or mix the two, having the values continue "upwards" from the last specified value. So, for example, if I have this,
enum class Op { Add = 0x90, Sub, Mul = 0xA0, Div, Mod, };
Op::Div will be equal to 0xA1, which is convenient, and allows grouping enum constants together (in this case storing operator priority in the upper 4 bits).
Haxe doesn't have that "out of box", unfortunately - either you make an actual enum and have the values assigned automatically, or make an @:enum abstract and assign the values manually.
But, of course, that can be fixed with a macro.
Attention: Haxe has @:enum abstract
since Haxe 3.10 and enum abstract
since Haxe 4 (docs), which render this post mostly-obsolete.
The logic in the macro is pretty simple - loop over the fields; assign incremental values to fields without values; adjust the counter for values for fields with integer values:
import haxe.macro.Context; import haxe.macro.Expr; class IntEnum { public static macro function build():Array<Field> { switch (Context.getLocalClass().get().kind) { case KAbstractImpl(_.get() => { type: TAbstract(_.get() => { name: "Int" }, _) }): default: Context.error( "This macro should only be applied to abstracts with base type Int", Context.currentPos()); } var fields:Array<Field> = Context.getBuildFields(); var nextIndex:Int = 0; for (field in fields) { switch (field.kind) { case FVar(t, { expr: EConst(CInt(i)) }): { // `var some = 1;` nextIndex = Std.parseInt(i) + 1; }; case FVar(t, null): { // `var some;` var expr = EConst(CInt(Std.string(nextIndex++))); field.kind = FVar(t, { expr: expr, pos: field.pos }); }; default: } } return fields; } }
Usage is pretty straightforward:
@:enum @:build(IntEnum.build()) abstract Op(Int) { var Add = 0x90; var Sub; var Mul = 0xA0; var Div; var Mod; }
And, just as with C/C++, Op.Div will be equal to 0xA1.
Bonus: if you want to be able to do getName() on your integer enum instances, here's a version of the macro that also generates the according method.
Have fun!
Hello,
i have a small suggestion,
it can be useful to have another parameter to the macro that specifies, how the next value advances. for example *=2 or +=1. (for nextIndex).