宏
一些语言特性,如C#,可以让用户自定义语法。这对于执行一些伪代码,动态修改语言的语法,隐藏开发代码可读性读是很有用的。
Haxe宏,可以在不改变haxe本身的语法情况下,在编译时加上功能加大的自定义语法。
宏函数
一个函数可以用 @:macro Metadata:定义为宏函数,如下:
import haxe.macro.Expr; class MyMacro { @:macro public static function getDate() { var date = Date.now().toString(); var pos = haxe.macro.Context.currentPos(); return { expr : EConst(CString(date)), pos : pos }; } }
每一个宏函数必须返回一个对应的代码块,它将取代现在的宏调用的表达式。请注意,不像内联函数,你可以在宏内部的生成实际代码。
例如,上例中调用MyMacro.getDate()将生成包含编译日期和时间的字符串常量。
表达式本身是一个用Expr包定义的枚举类型。
Context
在宏被调用的地方,可以在宏内部定义“haxe.macro.Context”,以便查询一些关于代码的上下文信息。
查看 Context API 文档可以得到更多详细信息。
参数
宏可以采取一个或几个表达式参数,可以以许多不同的方式调用。下面例子里展示了一个宏被重复调用了N次:
import haxe.macro.Expr; import haxe.macro.Context; class Repeat { @:macro public static function times( n : Expr, expr : Expr ) : Expr { // check that our first expression is a constant Int switch( n.expr ) { case EConst(c): switch( c ) { case CInt(n): // create a { } block that execute n times 'expr' var block = new Array(); for( i in 0...Std.parseInt(n) ) block.push(expr); return { expr : EBlock(block), pos : Context.currentPos() } default: } default: } Context.error("Should be an integer",n.pos); return null; } }
这里有一个例子来调用它:
class Test { static function main() { var x = 0; Repeat.times(5,x++); trace(x); // 将显示 5 } }
如果一个宏有两个"Expr"参数,你可以调用并且只能调用两个表达式。如果你想定义一个可变数目的参数,你必须定义一个单参数Array<Expr>:
@:macro static function foo( e : Array<Expr> ) : Expr { // .... }
Constant Parameters
Haxe 2.08中新特性
当你想让一个宏接受几个常量参数,你可以直接用常量类型代替Expr :
@:macro static function doSomething( v : Int, name : String ) : Expr { // .... }
请注意,在这种情况下,只有立即数(immediate constant)的值可以传递给宏。
Constant values are defined as :
null- int, string, float and bool 常量值
- 匿名对象的常量值
- 数组常量值
更强大的宏
Haxe 2.08中新特性
由于大部分宏将接收“Expr”参数和返回“Expr”,那么在编译器自动完成时,这些参数的类型都将显示为“Dynamic”。
为了改善这种情况,可以使用“ExprRequire”类型,将指定类型编译绑定:
@:macro static function isqrt( a : ExprRequire<Int> ) : ExprRequire<Float> { .... }
宏成员方法
Haxe 2.08中新特性
在宏这个子系统中,你可以用@:macro定义一个宏成员方法,这些方法将是静态的,并带有第一个参数用来传入"this"这个对象:
class A { public function new() { } @:macro public function getThis( ethis : Expr ) { return ethis; } } // ... var a = new A(); trace( a.getThis() );
宏 + 命名空间
Haxe 2.08中新特性
可以用"using" mixin来把宏定义放在一起。如果您希望您的宏只适用于一个给定的类型,你需要使用 ExprRequire 类型指定,您将只能接受给定类型的的表达式:
import haxe.macro.Expr; // ... @:macro static function encodeBase64( e : ExprRequire<String> ) : Expr { // .... }
/ /注意:/ /:截至目前的SVN,当宏被"using" mixin调用时,ExprRequire约束只支持第一个参数。
宏类
你也可以这样来定义一个宏类:
@:macro class MyMacro { .... }
这将把这个类里面所以的静态方法转为宏函数。请注意,这将会使代码提示功能丧失,因为他们将宏,而不是正常功能。
宏函数和宏类不会编译成你的代码的一部分:他们的代码只在宏模拟器上编译。因此,你可以用宏来完成算法或加密信息,这将不会被透露出去。
如果运行在宏模拟器中,您还可以用#if macro 条件编译 来包含/排除一些 代码依赖。例如:
class MyMacro { #if macro // ... utilities functions ... #end @:macro public static function foo() // .... }
运行环境
Macros are executed directly into the Haxe compiler by using a neko platform emulator. Most of the neko package API are supported inside macros and should behave the same as they do when running inside the NekoVM.
This means that you have full file I/O access inside your macro, which allow you to read/write external files and output source code depending on some other files content.
Macro-in-macro
It is possible to call a macro while evaluating another macro. However, this second macro evaluation will be delayed until it is actually executed. As a result, a macro called inside another macro will always be typed as returning Dynamic, since we don't know at compile time what expression it will produce.
End-of-compilation generation
In order to create per-class data that take into account the fields and/or inheritance, you can use haxe.macro.Context.onGenerate to register a callback that will run once your project is entirely compiled, prior to being generated for the target platform.
At this point, you can perform post-compilation checks and display errors if some requirements are not matched, and you can also add some class/enum/field metadata that will get compiled in your final source/binary. Use the add and remove methods of Metadata API.
Building types
You can also generate and manipulate types declarations content with macros, see Building Types with Macros
Compiler configuration
Macros can be used during the compilation process in order to generate custom code, but they can also be used to perform some pre-compilation tasks, see macros compiler configuration
Benchmarking / Optimization
Since macro needs to run for every compilation, it is sometimes necessary to take performances into account. Here's a few tips for improving macro performances :
- use a cache file : if you're doing complex processing based on external files, create a cache file and check if your external files have been modified before doing the processing again.
- disable processing in completion : since many IDE rely on the Haxe compiler for handling the autocompletion, sometimes macro execution will make autocompletion slower. You can disable some parts of the macro processing when running in completion mode, this can be checked with
haxe.macro.Context.defined('display') - benchmarking : in order to measure time spent in your macros, you can use
haxe --timesparameter. It will give you time measurements for the different parts of the compiling process. Additionally you can use-D macrotimesthat will give you time details on time spent in each macro method you're calling. Time measurements will also popup in completion mode, so you can check in your IDE what impact macros might have on your completion speed.