宏条件编译


关于条件编译详细
#if flash
trace("flash");
#else
trace("other");
#end

// 指定 flash player 版本
#if flash11_4
trace("flash player version >= 11.4"); // 如果目标为flash,且指定的编译版本大于等于 FP11.4 
#end

宏条件自定义


这里我们假设需要定义 hello, 并将hello的值设为 world。

  • 1. 可以用 Haxe 的宏代码 haxe.macro.Compiler.define(flag,?value); 来实现,但只能被 haxe.macro.Compiler.getDefine(flag) 识别 ,所以不推荐这种方法.
  • 2. 命令行编译或者.hxml文件使用 -D hello ,如果需要赋值为world则是 -D hello=world
  • 3. flashdevelop 可以在 项目属性 -> 编译器选项-> 常规 -> Directives 的 String[] Array 中添加 hello ,如果需要赋值为world则是 hello=world,
  • 4. openfl 项目可以在 .xml 文件中添加 <haxedef name="hello" />,如果需要赋值为world则为 <haxedef name="hello" value="world" />,
    > 注意: openfl 项目中设置 flashdevelop 的项目属性会被忽略,只能通过 .xml 文件来配置
#if hello
trace(haxe.macro.Compiler.getDefine("hello")); // 如果没有赋值,则默认为 1
#end

宏函数

  • 注意:通常应该把宏函数和其它函数分开放在不同文件,否则代码中的很多地方要加上#if macro 这样的条件编译才能通过编译.
  • 注意:给宏函数传参数时,参数只可以是常量,不能是变量.
  • 最简单的一个, macro 字面量,字面量可以是任何简单的数据类型,如 Array,Dynamic,Int...
    // 示例:    trace( tut_const() );    =>     相当于 trace("simple");
    macro public static function tut_const() {
        return macro "simple";
    }
  • 使用变量, macro $v{变量}, 注意: 变量类型不能为 Expr ,只能是简单的数据类型
    // 示例:    trace( tut_variable() );    =>     trace("so easy!");
    macro public static function tut_variable() {
        var easy:String = "easy!";
        return macro "so " + $v{ easy };
    }
    // 示例:    trace( tut_array([1,2,3,4,5]) );    =>     trace([1,2,3,4,5,10]);
    macro public static function tut_array(arr:Array<Int>) {
        arr.push(10);
        return macro $v{arr};
    }
    
    
     //注意 ${ 变量 } ,不能是复杂结构变量,下边语句 因为 编译器不知道如何将 Map 类型转换成 Expr,除非你自已定义一个转换,详细看 abstruct 关键字
     macro public static function tut_map() {
        var map:Map<String,String> = new Map<String,String>();
        map.set('desc', 'some msg');
        try{
            return macro $v{map}; // 错误: Unsupported value {desc => "some msg"}
        }catch (err:Dynamic) {
            return macro $v{err};
        }
    }
  • Expr类型变量, macro $Expr变量, 加前缀 $ 就行了,但只能出现在 macro 的语句后边
    // 示例:    trace( tut_param("abc") );    =>     trace("123abc");
    macro public static function tut_param(param:Expr) {
    
        var str:String = "123"; 
            
        return macro $v{str} + $param; // $param 只能出现在 macro 语句后边,否则报错
    }
    
    // 这有个复杂的示例:
    // trace( repeat(10,5) )     =>    [10,10,10,10,10]
    macro public static function repeat(e : Expr, eN : Expr) {
        return macro [for( x in 0...$eN ) $e];
    }
  • 处理 文件,除了从文件中解析数据, 实际上你还可以动态生成 hx 文件
    > 这是 需要加载的示例文件
    <?xml version="1.0" encoding="utf-8" ?>
    <root>
    <data lang="zh">
        <user name="小明" phone="123" addr="请开门,我们查一下水表" />
        <user name="小红" phone="456" addr="不存在" />
    </data>
    <data lang="en">
        <user name="ming" phone="123" addr="open the door,We need to check the water meter!" />
        <user name="hong" phone="456" addr="..." />
    </data>
    </root>

    >由于宏运行在编译前,因此可以针对任何平台使用 IO操作,但有时候需要用 #if macro 来隔离一下其它代码
    // example trace(tut_file("test.xml")) => [{ name : 小明, addr : 请开门,我们查一下水表, phone : 123 },{ name : 小红, addr : 不存在, phone : 456 }]
    macro public static function tut_file(name:String) {
        var content = sys.io.File.getContent( Context.resolvePath(name) );
        var xml = Xml.parse(content);
        var fast = new haxe.xml.Fast(xml.firstElement());
            
        var ret = new Array<Dynamic>();
        
        for (data in fast.nodes.data) {
            if (data.att.lang == 'zh') {
                for (user in data.nodes.user) {
                    var obj:Dynamic<String> = { };    
                        for (k in user.x.attributes()) {                        
                            Reflect.setField(obj, k, user.att.resolve(k));
                        }
                    ret.push(obj);
                }
            }    
        }
        return macro $v{ret};
    }
  • 使用 Context.parseInlineString 用于解析字符串代码, 多数ui库,都使用这个方法来预处理 xml 配置文件
    //example: trace(tut_parse());  => {width => 800, lang => zh-CH, note => 测试, sprite => [object Sprite]}
    macro public static function tut_parse() {
        var str = "测试";    // Tip:在单引号字符串中,可以使用 ${变量} 来引用一些变量, 
        var code:String = '
            function() {
                var map = new haxe.ds.StringMap<Dynamic>();
                map.set("note","${str}"); 
                map.set("width", Lib.current.stage.stageWidth);
                map.set("lang", flash.system.Capabilities.language);
                map.set("sprite", new flash.display.Sprite());
                return map;
             }()';
        return  haxe.macro.Context.parseInlineString(code,haxe.macro.Context.currentPos());
    }

    Reification Escaping

new in haxe 3.0

When you use macro reification, you still want to inject some values into the written expressions, this can be done in several ways :

  • Using ${value}, will replace the expression at this place by the corresponding value. The value needs to be an actual haxe.macro.Expr
        var v = macro "Hello";
        var e = macro ${v}.toLowerCase();
        // is the same as :
        var e = macro "Hello".toLowerCase();
  • In some case where the value can only be an identifier and not an expression, $ident' is replaced by the value of the identifier :

With var:

    var myVar = "i";
    var e = macro var $myVar = 0;
    // is the same as :
    var e = macro var i = 0;

With field :

    var myField = "f";
    var e = macro o.$myField;
    // is the same as :
    var e = macro o.f;

With object fields :

    var myField = "f";
    var e = macro { $myField : 0 };
    // is the same as :
    var e = macro { f : 0 };

  • Using $i{ident}, will create an identifier which value is ident
        var varName = "myVar";
        var e = macro $i{varName}++;
        // is the same as :
        var e = macro myVar++;
  • Using $v{value} will tranform the value into the corresponding expression, in a similar way to Context.makeExpr :
        var myStr = "some string";
        var e = macro $v{myStr};
        // is the same as :
        var e = macro "some string";

    And for a more complex case :
        var o = { x : 5 * 20 };
        var e = macro $v{o};
        // is the same as :
        var e = macro { x : 100 };
  • Using $a{exprs}, will substitute the expression Array in a {..} block, a [ ... ] constant array or a call parameters :
        var args = [macro "sub", macro 3];
        var e = macro "Hello".toLowerCase($a{args});
        // is the same as :
        var e = macro "Hello".toLowerCase("sub",3);

一些语言特性,如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 --times parameter. It will give you time measurements for the different parts of the compiling process. Additionally you can use -D macrotimes that 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.
version #20057, modified 2014-04-12 07:06:10 by r32