Skip to content

Latest commit

 

History

History
235 lines (183 loc) · 7.69 KB

9.5.类型构建.md

File metadata and controls

235 lines (183 loc) · 7.69 KB

9.5.类型构建

类型构建宏和表达式宏在很多方面是不同的:

  • 它们不返回表达式,而是一个数组或者类字段。它们的返回类型必须显式设置为 Array<haxe.macro.Expr.Field> 。
  • 它们的上下文环境(第9.1节)没有局部方法也没有局部变量。
  • 它们的额上下文环境有构建字段,可以从 haxe.macro.Context.getBuildFields() 获得。
  • 它们不会被直接调用,但是是一个类或者枚举声明上的 @:build 或者 @:autoBuild 元数据(第6.9节)的一个参数。

如下的例子演示了类型构建。注意,它被分成两个文件是有原因的:如果一个模块包含一个宏函数,它也必须类型化到 macro 上下文。 这对类型构建宏来说经常是一个问题,因为要构建的类型只能在它的不完整阶段被加载,在构建宏运行之前。我们建议总是定义类型构建宏到它们自己的模块。

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

class TypeBuildingMacro {
  macro static public function build(fieldName:String):Array<Field> {
    var fields = Context.getBuildFields();
    var newField = {
      name: fieldName,
      doc: null,
      meta: [],
      access: [AStatic, APublic],
      kind: FVar(macro : String, macro "my default"),
      pos: Context.currentPos()
    };
    fields.push(newField);
    return fields;
  }
}
@:build(TypeBuildingMacro.build("myFunc"))
class Main {
  static public function main() {
    trace(Main.myFunc); // my default
  }
}

TypeBuildingMacro 的构建方法执行三步:

  1. 它使用 Context.getBuildFields() 获得构建字段。
  2. 它使用 funcNace 宏参数作为字段名声明一个新的 haxe.macro.expr.Field 字段。这个字段是一个 String变量默认值为 “my default”(来自于 kind 字段),而且是 public static 的(来自于 access 字段)。
  3. 它添加新的字段到构建的字段数组,并返回它。

这个宏是Main 类上 @:build 元数据的参数。一旦这个类型被需要的,编译器就做以下事:

  1. 它解析模块文件,包含类字段。
  2. 它设置类型,包含它和其它类型通过继承和接口产生的关系。
  3. 它根据 @:build 元数据 执行类型构建宏。
  4. 它使用被类型构建宏返回的字段继续如常类型化类。

这允许在一个类型构建宏中随意添加和修改类字段。在我们的例子中,宏被调用,使用了一个 myFunc 参数,使 Main.myFunc 成为一个有效的字段访问。

如果一个类型构建宏不应修改任何内容,宏可以返回null 。这指示编译器没有刻意的改变,更好的是返回 Context.getBuildFields() 。

9.5.1.枚举构建

构建枚举类似于使用一个简单的映射构建类:

  • 没有参数的枚举构造函数是变量字段 FVar。
  • 带有参数的枚举构造函数是方法字段 FFun 。
import haxe.macro.Context;
import haxe.macro.Expr;

class EnumBuildingMacro {
  macro static public function build():Array<Field> {
    var noArgs = makeEnumField("A", FVar(null, null));
    var eFunc = macro function(value:Int) { };
    var fInt = switch (eFunc.expr) {
      case EFunction(_,f): f;
      case _: throw "false";
    }
    var intArg = makeEnumField("B", FFun(fInt));
    return [noArgs, intArg];
  }

  static function makeEnumField(name, kind) {
    return {
      name: name,
      doc: null,
      meta: [],
      access: [],
      kind: kind,
      pos: Context.currentPos()
    }
  }
}
@:build(EnumBuildingMacro.build())
enum E { }

class Main {
  static public function main() {
    switch(E.A) {
      case A:
      case B(v):
    }
  }
}

因为枚举 E 被使用一个 :build 元数据注解,调用的宏构建两个构造函数 A 和 B 到它之中。前者被添加使用的类型是 FVar(null, null),意味着它是一个构造函数,没有参数。后者,我们使用具体化来获得 haxe.macro.Expr.Function 的一个实例,参数为一个单独的 Int 。

main 方法通过匹配它,证明了我们生成的枚举的结构。我们可以发现,生成的类型跟下面这个是等价的:

enum E {
  A;
  B(value:Int);
}

9.5.2.@:autoBuild

如果一个类有 :autoBuild 元数据,编译器生成 :build 元数据 到所有扩展类。如果一个接口有 :autoBuild 元数据,编译器生成 :build 元数据到所有实现类和所有扩展接口。注意 :autoBuild 并不是暗示 :build 到 类/接口 本身。

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

class AutoBuildingMacro {
  macro static public
  function fromInterface():Array<Field> {
    trace("fromInterface: " + Context.getLocalType());
    return null;
  }

  macro static public
  function fromBaseClass():Array<Field> {
    trace("fromBaseClass: " + Context.getLocalType());
    return null;
  }
}
@:autoBuild(AutoBuildingMacro.fromInterface())
interface I { }

interface I2 extends I { }

@:autoBuild(AutoBuildingMacro.fromBaseClass())
class Base { }

class Main extends Base implements I2 {
  static public function main() { }
}

这在编译过程中输出:

AutoBuildingMacro.hx:6:
  fromInterface: TInst(I2,[])
AutoBuildingMacro.hx:6:
  fromInterface: TInst(Main,[])
AutoBuildingMacro.hx:11:
  fromBaseClass: TInst(Main,[])

重要的是记住 这些宏执行的顺序是未定义的,在构建顺序(第9.6.3节)中详述。

9.5.3.@:genericBuild

从Haxe 3.1.0 后: 一般构建宏每个类型化运行,已经非常强大。在一些情况下,每个类型化运行一个构建宏相反用法,即,当它实际上出现在代码中。除此之外这允许在宏中访问具体的类型参数。

@:genericBuild 就像 @:build 一样使用,通过添加它到一个类型,并使用一个宏调用作为参数:

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

class GenericBuildMacro1 {
  static public function build() {
    switch (Context.getLocalType()) {
      case TInst(_, [t1]):
        trace(t1);
      case t:
        Context.error("Class expected", Context.currentPos());
    }
    return null;
  }
}

@:genericBuild(GenericBuildMacro1.build())
class MyType<T> { }

class Main {
  static function main() {
    var x:MyType<Int>;
    var x:MyType<String>;
  }
}

当运行这个例子,编译器输出 TAbstract(Int,[]) 和 TInst(String,[]),表明它确实意识到 MyType的具体类型参数。宏的逻辑可以使用这个信息来生成一个定制类型(使用 haxe.macro.Context.defineType)或者引用一个存在的类型。方便起见,我们在这里返回null,要求编译器推断这个类型。

在Haxe 3.1 中,一个 @:genericBuild 宏的返回类型必须是一个 haxe.macro.Type 。Haxe 3.2 允许(也更愿意)返回一个 haxe.macro.ComplexType ,是一个类型语法上的表示。在很多情况下这变得很容易使用,因为类型可以通过它们的路径简单的引用。

Const 类型参数 如果类型参数名称是 Const,Haxe 允许传递常量表达式(第5.2节)作为一个类型参数。这可以在 @:genericBuild 宏的上下文中被利用来从语法直接到宏传递信息:

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

class GenericBuildMacro2 {
  static public function build() {
    switch (Context.getLocalType()) {
      case TInst(_,[TInst(_.get() => { kind: KExpr(macro $v{(s:String)}) },_)]):
        trace(s);
      case t:
        Context.error("Class expected", Context.currentPos());
    }
    return null;
  }
}
@:genericBuild(GenericBuildMacro2.build())
class MyType<Const> { }

class Main {
  static function main() {
    var x:MyType<"myfile.txt">;
  }
}

这里宏的逻辑可以加载一个文件并使用它的内容来生成一个定制类型。