用于填写自定义 @:enum 摘要的字段名称、值和 switch 语句的宏?

Macro to fill out field names, values, and switch statements of a custom @:enum abstract?

我的情况有点特殊。出于各种原因,主要是与可为空的 stringly typed 遗留系统进行互操作,以及目前我不会讨论的各种其他需求,我已经确定了一个自定义的 @:enum 摘要,如下所示:

@:enum abstract MyEnum(Null<Int>) {

    public var A = 0;
    public var B = 1;
    public var C = 2;
    public var D = 3;
    public var E = 4;
    public var F = 5;
    public var G = 6;

    @:from private static function fromString (value:String):MyEnum {

        return switch (value) {

            case "a": A;
            case "b": B;
            case "c": C;
            case "d": D;
            case "e": E;
            case "f": F;
            case "g": G;
            default: null;

        }

    }

    @:to private static function toString (value:Int):String {

        return switch (value) {

            case A: "a";
            case B: "b";
            case C: "c";
            case D: "d";
            case E: "e";
            case F: "f";
            case G: "g";
            default: null;

        }

    }

}

但是,要键入的内容太多,令人恼火,添加和删除成员时很容易出现手动错误。显然,这遵循了一个超级可预测的模式,并且似乎是用宏构建的好东西,但我不擅长 haxe 宏。

有人可以解释我如何使用宏来构建这个枚举,这样我只需要提供一个字段名称列表吗?

伪代码:

@:enum abstract MyEnum = doTheMacroMagic(["A","B","C","D","E","F","G"]);

逻辑步骤是:

我认为像这样一个简单的实际例子可能最终让 haxe 宏对我来说 "click" 如果我能看到它的实际效果。

Flixel 在 类 中处理非常相似的用例,例如 FlxKeyFlxMacroUtil.buildMap()。这个表达式宏查找它在摘要中找到的所有大写内联变量,并从中生成一个 Map<String, EnumType>,键是字段名,值是字段值(或者如果 inverttrue).

@:enum
abstract FlxKey(Int) from Int to Int
{
    public static var fromStringMap(default, null):Map<String, FlxKey>
        = FlxMacroUtil.buildMap("flixel.input.keyboard.FlxKey");

    public static var toStringMap(default, null):Map<FlxKey, String>
        = FlxMacroUtil.buildMap("flixel.input.keyboard.FlxKey", true);

    var A              = 65;
    var B              = 66;
    // more keys...

    @:from
    public static inline function fromString(s:String)
    {
        s = s.toUpperCase();
        return fromStringMap.exists(s) ? fromStringMap.get(s) : NONE;
    }

    @:to
    public inline function toString():String
    {
        return toStringMap.get(this);
    }
}

我认为这是一个很好的起点。如果要生成整个 abstract,您将需要一个 @:build 宏。


回答后续问题,如何生成字段:这实际上使用构建宏非常简单:

@:enum
@:build(Macro.createVariables(["A", "B", "C", "D", "E"]))
abstract Generated(Int)
{
}

Macro.hx(明智的做法是在自己的文件中避免必须处理 #if macro 条件):

package;

import haxe.macro.Context;
import haxe.macro.Expr;

class Macro
{
    public static macro function createVariables(varNames:Array<String>):Array<Field>
    {
        // get the current fields of the calling type (empty array in this case)
        var fields = Context.getBuildFields();
        for (i in 0...varNames.length)
            // create a custom variable and add it to the fields
            fields.push(createVariable(varNames[i], i));
        return fields;
    }

    private static function createVariable(name:String, value:Int):Field
    {
        return {
            name: name,
            doc: null,
            meta: [],
            access: [Access.APublic, Access.AStatic, Access.AInline],
            kind: FieldType.FVar(macro:Int, macro $v{value}),
            pos: Context.currentPos()
        }
    }
}

您会注意到 Generated. 的字段显示在自动完成中。您还可以在执行 AST 转储时通过查看 Generated_Impl.dump 查看生成的内容。