【问题标题】:What is the best way to get Haxe function parameter types using a macro?使用宏获取 Haxe 函数参数类型的最佳方法是什么?
【发布时间】:2016-05-04 18:51:54
【问题描述】:

我想使用宏获取 Haxe 函数的参数类型并将它们转换为简写字符串形式,有点像 JNI/Java 方法签名,但没有返回类型。

这里的动机是提供对函数参数类型的访问,而不必在运行时慢慢搜索运行时类型信息。例如,假设您想构建一个图形小部件来调用一个带参数的函数。您将需要每个函数参数的类型来创建正确的微调框、文本框和选择框小部件,以调整将传递给函数的值。

那么问题来了,如何用宏保存 Haxe 函数参数类型?

【问题讨论】:

    标签: macros haxe


    【解决方案1】:

    这是一个适用于一些基本类型以及基于这些类型的任何抽象的宏。它将函数参数类型映射到字符串。例如,函数类型 String->Float->Int->String->Void 映射到 sfisFloat->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};
        }
    }
    

    另一个问题是如何使这项工作适用于 所有 类型,而不仅仅是您明确处理的类型。为此,您可以使用每个函数参数的类型名称/类名称/路径填充一个字符串数组,并返回它而不是单个字符串。这是一个尝试,请注意它不适用于函数参数(可能还有其他东西):

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

    【讨论】:

      【解决方案2】:

      更新的方法..

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

      这样使用

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

      您可能面临的一个问题是如何收集参数的默认值,请考虑以下示例。

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

      现在让我们通过稍微修改代码来考虑默认参数值:

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

      所以我们现在可以这样使用它

      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'}]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-03-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-09-19
        相关资源
        最近更新 更多