简体   繁体   English

使用宏获取 Haxe function 参数类型的最佳方法是什么?

[英]What is the best way to get Haxe function parameter types using a macro?

I want to get the parameter types of a Haxe function using a macro and convert them to a shorthand string form, a bit like JNI/Java method signatures, but without a return type.我想使用宏获取 Haxe function 的参数类型并将它们转换为简写字符串形式,有点像 JNI/Java 方法签名,但没有返回类型。

The motivation here is to provide access to the function parameter types, without having to slowly search through run-time type information at runtime.这里的动机是提供对 function 参数类型的访问,而不必在运行时慢慢搜索运行时类型信息。 For example, say you want to construct a graphical widget for calling a function that takes parameters.例如,假设您要构造一个图形小部件来调用带参数的 function。 You will need the type of each function parameter to create the correct spinbox, textbox, and select box widgets needed for tweaking the values that will be passed to the function.您将需要每个 function 参数的类型来创建正确的旋转框、文本框和 select 框小部件,以调整将传递给 function 的值。

So the question is, how can you save Haxe function parameter types with a macro?那么问题来了,如何用宏保存Haxe function参数类型呢?

Here is a macro that works for a few basic types, and any abstracts based on those types. 这是一个适用于一些基本类型以及基于这些类型的任何抽象的宏。 It maps the function parameter types to strings. 它将函数参数类型映射为字符串。 For example, function type String->Float->Int->String->Void maps to sfis , Float->Float->Int to ff etc: 例如,函数类型String->Float->Int->String->Void sfis String->Float->Int->String->Void映射到sfissfis Float->Float->Int sfis Float->Float->Int映射到ff等:

package;

import haxe.macro.Expr;
import haxe.macro.Context;
import haxe.macro.Type;
import haxe.macro.ExprTools;

// Map some Haxe types to string ids
@:enum abstract TypeMapping(String) from (String) {
    var BOOL = "b";
    var FLOAT = "f";
    var INT = "i";
    var STRING = "s";
}

class Util
{
    public macro static function getParameterTypes(f:Expr):ExprOf<String> {
        var type:Type = Context.typeof(f);
        if (!Reflect.hasField(type, 'args')) {
            throw "Parameter has no field 'args'";
        }
        var t = type.getParameters()[0];

        var args:Array<Dynamic> = Reflect.field(type, 'args')[0];

        var signature:String = "";
        for (i in 0...args.length) {            
            switch(args[i].t) {
                case TAbstract(t, p):
                    var underlyingTypeName = Std.string(t.get().type.getParameters()[0]);
                    switch(underlyingTypeName) {
                        case "Bool":
                            signature += TypeMapping.BOOL;
                        case "Float":
                            signature += TypeMapping.FLOAT;
                        case "Int":
                            signature += TypeMapping.INT;
                        case "String":
                            signature += TypeMapping.STRING;
                        default:
                            throw "Unhandled abstract function parameter type: " + underlyingTypeName;
                    }
                case CString:
                    signature += TypeMapping.STRING;
                default:
                    throw "Unhandled function parameter type: " + args[i];
            }
        }
        return macro $v{signature};
    }
}

A further problem is how to make this work for all types, rather than just ones you handle explicitly. 另一个问题是如何使它适用于所有类型,而不仅仅是您明确处理的类型。 To do that, you might populate an array of Strings with the type name/class name/path of each function parameter instead, and return that instead of a single String. 为此,您可以使用每个函数参数的类型名称/类名称/路径填充字符串数组,然后返回它而不是单个字符串。 Here's an attempt at that, note it doesn't work with function parameters (and probably other stuff) yet: 这是一种尝试,请注意,它尚不适用于函数参数(可能还有其他功能):

public macro static function getFullParameterTypes(f:Expr):ExprOf<Array<String>> {
    var type:Type = Context.typeof(f);
    if (!Reflect.hasField(type, 'args')) {
        throw "Parameter has no field 'args'";
    }
    var args:Array<Dynamic> = Reflect.field(type, 'args')[0];

    var pos = haxe.macro.Context.currentPos();
    var signature:Array<Expr> = [];

    for (i in 0...args.length) {
        var argType:Type = args[i].t;
        var s;
        switch(argType) {
            case TFun(t, r):
                s = EConst(CString("Function"));
                throw "Not working with function parameters yet";
            case _:
                s = EConst(CString(argType.getParameters()[0].toString()));
        }
        signature.push({expr: s, pos: pos});
    }
    return macro $a{signature};
}

A more up to date approach..一个更新的方法..

macro function deflate(fun:haxe.macro.Expr) {
    var type = haxe.macro.Context.typeof(fun);
    final paramNames = extractFunction(type);

    return macro $v{paramNames};
}
// Extract function parameter names
function extractFunction(type):Array<Dynamic> {
    return switch type {
        case TFun(args, ret): {
                var paramNames:Array<Dynamic> = [];
                for (p in args) {
                    final pName = p.name;
                    paramNames.push(pName);
                }

                return paramNames;
            }
        case _: {throw "unable to extract function information";};
    }
}

Use it like this像这样使用

using Macros;
function func(name:String, greeting:String){};
final args = fun.deflate(); 
trace(args) // output: [name, greeting]

A problem you may face is how to collect the default value of a parameter, consider the example below.您可能面临的一个问题是如何收集参数的默认值,请考虑下面的示例。

function func(name:String = "Josh", greeting:String = "Hello"){ return '$greeting $name'};
final args = fun.deflate(); 
trace(args) // output: [name, greeting]

Now let's account for default parameter values by slightly modifying the code:现在让我们通过稍微修改代码来考虑默认参数值:

// Extract function parameter names
function extractFunction(type):Array<Dynamic> {
    return switch type {
        case TFun(args, ret): {
                var paramNames:Array<Dynamic> = [];
                for (p in args) {
                    final pName = p.name;
                    final v = {name: pName, value: null}; // <= anticipate a value
                    paramNames.push(v);
                }

                return paramNames;
            }
        case _: {throw "unable to extract function information";};
    }
}


macro function deflate(fun:haxe.macro.Expr) {
    var type = haxe.macro.Context.typeof(fun);
    final paramNames:Array<Dynamic> = extractFunction(type);
    // extract default param values 
    switch  fun.expr {
        case EFunction(f, m):{
            for(a in m.args){
                for(p in paramNames){
                    if(p.name == a.name){
                        if(a.value != null){
                            switch (a.value.expr){
                                case EConst(c):{
                                    switch(c){
                                        case CString(v, _):{
                                            p.value = v;
                                        }
                                        case CFloat(f): {
                                            p.value = Std.parseFloat(f);
                                        }
                                        case CInt(i):{
                                            p.value = Std.parseInt(i);
                                        }
                                        case _: throw "unsupported constant value for default parameter";
                                    }
                                }
                                case _:
                            }
                        }
                    }
                }
            }
        }
        case _:
    }
    return macro $v{paramNames};
}

So we can now use it like this所以我们现在可以像这样使用它

function func(name:String = "Josh", greeting:String = "Hello"){ return '$greeting $name'};
final args = Macros.deflate(func); 
trace(args) // output: [{name: 'name', value:'Josh', {name:'greeting', value:'Hello'}]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM