haxe的 条件编译以及宏
宏 是 Haxe 最主要也是最强大的特性宏可以在编译时通过计算初使化一些值,比如 UI 的配置等等.宏可以扫描资源文件夹,用于自动嵌入文件或者 IDE 智能提示.
条件编译
条件编译 (Conditional Compilation). 通过使用 #if #else #elseif #end 来 检测编译器标志
编译标志(define), 通过使用 -D key=value 或只是 -D key 来设置, 未设置 value 默认值将为字符串 1. 所有 -D 定义标记的值类型都为字符串
#if flash
trace("flash");
#else
trace("other");
#end// 还可以检测 flash player 版本
#if flash11_4
trace("flash player version >= 11.4"); // 如果目标为flash,且指定的编译版本大于等于 FP11.4
#end// 使用运算符检测
#if(haxe_ver > 3.1)
trace("haxe version > 3.1");
#end// 支持逻辑运算符:&& 和 || ,需要有小括号
#if (neko && debug)
// 只有在当平台为neko并且为debug模式
#end#if (flash || php)
// flash 或 php 平台
#end#if js
#error 目前不支持 js 平台
#end
在 #if 和 #elseif 之后的条件允许以下表达式:
-
重要: 任意编译标志(haxe-defines)将替换成名字相同的 条件标识符, 如果带 减(-)号,将会 同时 生成另一个 带 下划线(_)的标识符
请注意:
-D some-flag将会产生some-flag和some_flag二个条件标识符. 但是#if或#elseif却只能识别带下划线的那一个. 减号那个需要用引号括起来, 而用引号括起来的标识符却又不能使用一些运算符.宏方法中 Compiler.getDefine 或 Context.definedValue 能正确识别这二个.是否Bug? haxe --help-defines 下的 haxe-ver 只能通过 haxe_ver 才能检测得到. 一些其它标志未测试.
结论: 即使定义标志时为 减(-)号, 但检测编译标志时, 请使用 下划线(_) 替换掉 减(-)号.
-
重要: 使用 haxelib 时 会生成同库名一样的 条件标识符, 减(-)号问题和上边一样
-
String, Int, Float 常量值可以直接使用, 0值 以及 空字符串 用于表示 false
-
逻辑运算符
&&(与), ||(或), !(非) -
条件运算符
==, !=, >, >=, <= -
圆括号
(), 用于组合多个表达式 -
一些个人收集清单
flash|neko|cpp|js|php|java : 这种平台相关无需多解释,但是从上边示例可以发现, 还可以指定版本
同样可以把条件标识符放置在 @:require 之后
@:require(Compiler Flag [,"custom error message" ])
如果没有满足 @:require 之后的条件标识符, 类名可以访问, 但是 类的所有字段(包括static类型)都不可访问.
@:require(haxe_ver>3.1)
@:require(nodejs, "require haxelib nodejs")
class Foo{public var value:String;public function new(val:String){value = val;}
}
其它:
-
编译标记除了用于条件编译,还可以用于传值:
例如 宏函数 只支持常量不支持使用变量传值,这时使用 条件编译标记 来赋值,然后在代码使用
haxe.macro.Compiler.getDefine(flag)来获得对于 Compiler.define,只能通过 Context 类下的方法获得
-
这里假设需定义一个叫 hello 的标记, 并且将 hello 赋值为 world.
命令行编译或者 .hxml文件 使用
-D hello,如果需要赋值为world则是-D hello=worldflashdevelop 可以在 项目属性 -> 编译器选项 -> 常规 -> Directives 的
String[] Array中添加一行hello,如果需要赋值为world则是hello=worldopenfl 项目可以在 .xml 配置文件中添加
,如果需要赋值为world则为,注意: openfl 项目中设置 flashdevelop 的项目属性会被忽略,只能通过 .xml 文件来配置
// 测试条件编译标记 #if hello trace("hello"); #end
宏
Tips: haxe 标准库中有很多 工具类能更好地构建, 如: macro.TypeTools, macro.ExprTools, 以及其它以 Tools 结尾的类, 通常是使用 using 关键字添加这些工具类.
宏方法
内容只适用于 haxe 3. 宏相当于一个在编译时运行的 neko 平台.例如: 在编译时输出 --macro Sys.println('Hello macro!')
注意: 通常应该把宏函数和其它函数分开放在不同文件,否则代码中的很多地方要加上#if macro 这样的条件编译才能通过编译.
注意: 给宏函数传参数时,参数应该是常量, 如果传变量只能获得 变量名 不能获得 变量值(因为宏编译时赋值还没发生).
-
最简单,
macro 常量// 示例: 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 类型转换成 Exprmacro 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 的语句后边你应该注意到了,即使宏函数参数声明为 Expr 类型,在调用这个函数时传的值却是 字符串类型.
如果参数为 Expr 时,编译器会自动转换这些直接常量为 Expr,然后在宏函数内部 这个变量将会是 Expr 类型.
// trace( tut_param('456') ); => trace('123456'); macro public static function tut_param(param:Expr) {var str:String = "123"; return macro $v{str} + $param; }// 这有个复杂的示例: // 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]; }// Tips: 在宏函数体中获得 Expr 的值 // getValue("hello"); then val = 'hello'; macro public static function getValue(ep:Expr){var val = haxe.macro.ExprTools.getValue(ep)); //或者 using haxe.macro.ExprTools;return macro null; } -
处理文件.
这只是一个从文件中解析数据的示例.
或者你可以通过
template以及haxe.io.File来动态将文件数据写成一个classlang="zh"> name="小明" phone="123" addr="详细地址描述" /> lang="en">name="小红" phone="456" addr="不存在" /> name="ming" phone="123" addr="expanded address details." /> name="hong" phone="456" addr="..." /> // 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 配置文件这个方法有一定的局限性,通常使用类似的匿名函数,不可以包含 class 这些.
//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(); 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()); }
小抄
class Main{static public function main(){trace( Um.tut_const() );}
}class Um{macro public static function tut_const() {return macro "simple";}
}
如上示例: 当调用 宏方法 Um.tut_const 时, Um.tut_const 的返回值将替换掉 Um.tut_const(), 也就是说 trace( Um.tut_const() ) 将替换成 trace("simple");
class Main {static function main() {var i = 1;var j = 2;var a = [1, 2];Um.callMethed(a);Um.assign(i);Um.noChange(j);trace(a); // 输出: [1, 2, 77]trace(i); // 输出: 88trace(j); // 输出: 2 }}class Um{macro static public function callMethed(a) {return macro $i { haxe.macro.ExprTools.toString(a) }.push(77);}macro static public function assign(i) {return macro $i { haxe.macro.ExprTools.toString(i) } = 88;}macro static public function noChange(i) {macro $i { haxe.macro.ExprTools.toString(i) } = 99;return macro null;}
}
上边是一个将变量传递给 宏方法的示例(haxe 3.2 推荐使用 Context.getLocalTVars 来获得本地变量,一,而不是通过宏传递)
注意: 宏方法 assign 和 noChange 只有返回值不一样, 充分说明了 宏返回值 将替换所 宏调用. 这一概念很重要.
-
haxe.macro.ExprTools 类中的 toString 和 getValue 都是常用方法
-
如何从宏方法返回一个 bytes
// 源文件 h3d/hxd/res/Embed.hx // 先字符串序列化 bytes ,然后 反序列化就行了 public static macro function getResource( file : String ) {var path = Context.resolvePath(file);var m = Context.getLocalClass().get().module;Context.registerModuleDependency(m, path); // 参考 Haxe 命令行, haxe -waitvar str = haxe.Serializer.run(sys.io.File.getBytes(path));return macro hxd.res.Any.fromBytes($v{file},haxe.Unserializer.run($v{str})); } -
macro.MacroType
// 在 CastleDB 的 一个示例中, Data.hx 如下: package dat;private typedef Init = haxe.macro.MacroType < [cdb.Module.build("test.cdb")] > ; -
macro 关键字后可以接任意 haxe 代码. AST
// 轻松获得一个类型. 对于宏构建(@:build) 非常有帮助. var loaderType = macro : hxd.res.Loader;var method = macro {return flash.Lib.current.stage; }
Reification Escaping
The Haxe Compiler allows reification of expressions, types and classes to simplify working with macros.
The syntax for reification is macro expr, where expr is any valid Haxe expression.
宏方法小节使用的就是这类语法, 不详写了,参考 相关小节
-
所有下列 $引用 需要必须在 macro 语句后边
${}或$e{}:Expr -> Expr$a{}:Expr -> Array用于把数量未确定的参数传递给 作参数的函数, 示例见 Promhx 库的 Promise.when$b{}:Array-> Expr $i{}:String -> Expr注: 这里的 String 变量名字符串.function main(){var abc = 100;trace( getIdent() ); // 宏替后将为 trace(abc); } macro static function getIdent(i:Expr){return macro $i{"abc"}; }$p{}:Array同上. String 指的是变量名.-> Expr $v{}:Dynamic -> Expr这个应该是使用频率最多的标记. -
haxe 3.1
字段名 {$name : 1}
函数名 function $name(){}
try/catchtry{e()}catch($name:Dynamic){}class Main {macro static function generateClass(funcName:String) {var c = macro class MyClass {public function new() { }public function $funcName() { //函数名trace({ $funcName : "was called" }); //字段名}}haxe.macro.Context.defineType(c); // 动态定义的类需要通过定义,外边才可以引用.return macro new MyClass();}public static function main() {var c = generateClass("myFunc");c.myFunc();} }
Compiler
haxe.macro.Compiler 的一些方法, 这个类的方法运行在编译时, 当编译参数 --macro 后调用方法时,默认为这个包下的方法.
例如: 'haxe --macro define('SomeFlag','Hello')', 记得字符串用 单引号 而不是 双引号.
haxe 命令行编译参数
// 就像 haxe 编译的参数 -cp, 将指定的路径添加类路径
addClassPath(path:String):Void;//
addGlobalMetadata(pathFilter:String, meta:String, recursive:Bool = true, toTypes:Bool = true, toFields:Bool = false):Void//
addMetadata(meta:String, className:String, field:String = null, isStatic:Bool = null):Void// Adds a native library depending on the platform (eg : -swf-lib for Flash)
addNativeLib(name:String):Void// 允许访问指定的包, 例如: 默认情况下当目标平台为 neko 时, 不允许访问 flash 包, 参见 openfl 编译选项
allowPackage(v:String):Void// 像 haxe 编译参数 -D
define(flag:String, value:String = null):Void// Exclude a given class or a complete package from being generated
// 手动排除给定的包或类名在编译时, 由于编译参数 -dce 的关系, 事实上你不用调用这个方法
exclude(pack:String, rec:Bool = true):Void// Exclude classes listed in an extern file (one per line) from being generated.
// 读取指定文件, 排除 外部类清单 每行一个
excludeFile(fileName:String):Void// 得到 -D 定义的值.
getDefine(key:Dynamic):Dynamic// 未知. 估计是用于 代码 编辑器
getDisplayPos():Null<{pos:Int, file:String}>// 得到编译时输出的绝对全文件名, 例如: haxe -main Main -swf main.swf, 那么这个方法返回 D:\path\main.swf
getOutput():String/**
这个方法形为像在代码中的 import pack.*;
@param rec 归递包含所有子项
@param ignore 要忽略的类名数组, 包含路径在内的全名.
@param classPaths pack 的所在路径, TODO: 即使指定了这个路径同样需要以 -cp 的方式添加路径,所以这个参数好像有 Bug, 因为如果指定了 -cp 也就不需要这个参数了.
*/
include(pack:String, rec:Bool = true, ignore:Array = null, classPaths:Array = null)// JS 平台. 嵌入 JS 文件到 输出端, 目前 haxe 只提供 jQuery 和 swfObject 二个 JS 文件
includeFile(fileName:Dynamic):Dynamic;/**
防止 包 或类 或字段 被 -dce 编译参数删除, 参见 命令行编译参数 的 @:keep 元标记 这个标记经常被以 @:keep 的形式添加到 类或静态字段上@param path 包,模块或 子类型
@param paths 包,模块或 子类型 数组, 就是 path 参数的数组形式,
@param recursive 如果为 true, 将归递包含 sub-packages 针对 paths
*/
keep(path:String = null, paths:Array = null, recursive:Bool = true):Void// 加载 flash 补丁文件, 因该只能用于 flash 端的 http://r32.github.io//haxe/2014/05/10/tips-haxe-flash.html#patch-files
patchTypes(file:String):Void// 移除指定字段
removeField(className:String, field:String, isStatic:Bool = null):Void// 自定义 js 输出, bing 上自行搜索教程.
setCustomJSGenerator(callb:JSGenApi -> Void):Void// 设置字段类型
setFieldType(className:String, field:String, type:String, isStatic:Bool = null):Void// 设置输出文件名, 绝对路径全名包括磁盘. 例如编译到 hello.swf, 那么 输出文件名为 X:\path\to\hello.swf
setOutput(fileOrDir:String):Void
Context
haxe.macro.Context 上下文,就是调用这个类方法的环境, 也就是说如果在 Test.hx 代码的 第 6 行调用了一个自定义的宏方法例如: Um.some(), 那么这第 6 行就是 宏的上下文环境. Um.some 方法体内的 currentPos() 也只是返回第 6 行, getLocalModule 也只是返回 第 6 行处所在的 类模块.
package;class Test{public static function(){trace("Context");Um.some();}
}class Um{macro static function some(){trace(haxe.Context.currentPos()); //输出在编译端: #pos(Test.hx:6: characters 2-7)trace(haxe.Context.getLocalModule()); //输出在编译端: Testreturn macro null;}
}
这个类的方法经常用于 宏方法, 宏构建中, 下列方法未注释的请参数 API 文档描述.
// 添加资源文件, haxe.Resource 类能获得这个方法定义的资源文件, 通过 -resource file@name 在编译命令行定义
addResource(name:String, data:Bytes):Void// 返回上下文的位置,
function currentPos():Position// 定义新模块, 也就是像在一个 hx 文件中写几个类或定义. 通常来说 defineType 就够用了.
defineModule(modulePath:String, types:Array , imports:Array = null, usings:Array = null):Void// 定义一个类型 ,示例见: heaps 游戏引擎 hxd.res.FileTree.hx
defineType(t:TypeDefinition):Void// 检测是否存在一个 -D 定义
function defined(s:String):Bool// 检测 -D 定义的值, 如果仅定义标记,那么默认为 字符串 1,
definedValue(key:String):String// 抛出编译错误, 并中断当前宏调用
error(msg:String, pos:Position):Dynamic// 抛出编译错误, 并中断当前编译
fatalError(msg:String, pos:Position):Dynamic// Follows a type. See haxe.macro.TypeTools.follow for details.
follow(t:Type, once:Bool = null):Type// 用于 宏构建 @:build 或 @:autoBuild 所在上下文
getBuildFields():Array<Field>// 得到编译时类全部路径,包括调用的 haxelib 所在路径, -cp 包含的路径, 甚至包含调用了 haxe 标准库下的 std及各目标平台的 _std 目录路径
getClassPath():Array<String>// Returns the constructor arguments that are used to construct the current @:genericBuild class, if available.
// Returns null if the current macro is not a build-macro which was called from constructing a @:genericBuild instance.
getConstructorArguments():Null<Array<Expr>>// haxe 3.2+ 得到所有 -D 的定义
getDefines():Map<String, String>//
getExpectedType():Null<Type>// 得到上下文所在 类名
getLocalClass():Null<Ref<ClassType>>// 得到上下文所在 方法名
getLocalMethod():Null<String>// 得到上下文所在 模块名(hx源码文件名,但不包含文件扩展名)
getLocalModule():String//
getLocalTVars():Map<String, TVar>//
getLocalType():Null<Type>//
getLocalUsing():Array<Ref<ClassType>>//
getLocalVars():Map<String, Type>// 得到指定模块名(hx源码文件名,但不包含文件扩展名)所定义的类型数组
getModule(name:String):Array<Type>// 得到上下文所在位置信息,
// Tips: 通过这个方法可以得到一个 上下文所在的文件路径, 在自定义库中加载资源很方便.
getPosInfos(p:Position):{min:Int, max:Int, file:String}// 取得所有 -resource 定义的资源
getResources():Map<String, Bytes>// 得到已经存在的指定类型, 如: getType("Int")
getType(name:String):Type// Returns a syntax-level expression corresponding to typed expression t.
// This process may lose some information
getTypedExpr(t:TypedExpr):Expr/**
Builds an expression from v, 自动解析 v 的值 构建相应的 ExprThis method generates AST nodes depending on the macro-runtime value of v. As such, only basic types and enums are supported and the behavior for other types is undefinedThe provided Position pos is used for all generated inner AST nodes.
*/
makeExpr(v:Dynamic, pos:Position):Expr// Builds a Position from inf.
makePosition(inf:{min:Int, max:Int, file:String}):Position//
onAfterGenerate(callback:Void -> Void):Void//
onGenerate(callback:Array -> Void):Void//
onMacroContextReused(callb:Void -> Bool):Void//
onTypeNotFound(callback:String -> TypeDefinition):Void// 解析字符串代码, 实际上经常用 parseInlineString 代替这个方法
parse(expr:String, pos:Position):Expr// 解析字符串代码
parseInlineString(expr:String, pos:Position):Expr// 注册模块依赖, 以缓存编译, 如果你没有使用 --wait --connect 缓存编译, 将没有任何效果.
// 在 heaps 的 hxd.res 库中经常能见到这个方法示例.
registerModuleDependency(modulePath:String, externFile:String):Void// Add a macro call to perform in case the module is reused by the compilation cache.
registerModuleReuseCall(modulePath:String, macroCall:String):Void// 从 classPaths 是否存在指定文件名, 并返回 绝对路径全名. 参考 getClassPath 将会返回哪些类路径
// 对于 windows 平台会将 /path/to/file 这种 unix 斜线路径转成 windows 系统能识别的反斜线.
resolvePath(file:String):String// 返回 MD5 值, 但我总觉得这个方法返回的 MD5 值不对.
signature(v:Dynamic):String// 根据指定类型创建复杂类型 , 参看 makeExpr
// toComplexType( getType("Int") ) 等于 `macro :Int` 等于 `TPath({name:"StdTypes", pack:[], params:[], sub:"Int" })`
// 那么简单类型就是 `macro Int` 返回的值 `src/Main.hx:34: { expr => EConst(CIdent(Int)), pos =>...}`
toComplexType(t:Type):Null<ComplexType>// Types expression e and returns the corresponding TypedExpr.
typeExpr(e:Expr):TypedExpr// Types expression e and returns its type.
typeof(e:Expr):Type// Returns true if t1 and t2 unify, false otherwise
unify(t1:Type, t2:Type):Bool// Displays a compilation warning msg at the given Position pos
warning(msg:String, pos:Position):Void
宏构建
通过宏的方式动态构建 class 或 enum. 注意: @:build 或@:autoBuild 调用的方法没有添加 macro
需要理解 AST,以前了解 haxe.macro 包中的所有类. 新参考 参考
build宏函数 与 普通的宏函数不一样的地方:
-
返回的类型不是
Expr,而是Array. (haxe/macro/Expr.hx文件中定义了Field) -
build 宏函数内部的 macro.Context 没有 getLocalMethid 和 getLocalVars.
-
build 宏函数内部的 macro.Context 有 getBuildFields()
-
不是直接调用,而是将元标记
@:build或@:autoBuild放在一个class或enum定义中.
build 一个示例, 其实 haxelib heaps 库中 hxd 目录下有很多很好的宏构建示例
在宏构建方法中, 使用 macro 关键字能省很多麻烦的事情, 比如宏动态创建字段时: 注意 kind 字段
// 创建一个 MIN 的静态常量值.
public static function buildMIN(){var fields = Context.getBuildFields();var pos = Context.currentPos();fields.push({name : "MIN",access : [APublic,AStatic,AInline], //下行的第一个参数也可以用 FVar(TPath({name:"Int" ,pack:[]})//kind : FVar(TPath({name:"StdTypes", pack:[], params:[], sub:"Int" }), {expr: EConst(CInt("200")), pos: pos }), //kind : FVar(Context.toComplexType(Context.getType("Int")), Context.makeExpr(200,pos)) // Context类有一些方法kind: FVar(macro :Int, macro 200), // 使用 macro 让一切变得简单pos : pos});return fields;
}
宏高级特性
参考
-
可变参数
如果你希望使用可变参数,可以使用
Array作为最后一个参数macro static function foo(e : Array<Expr>):Expr{ // ... } -
更可读
大部分宏方法使用 Expr 类型参数并且返回的也是 Expr 类型,为了让代码更为可读, 你可以使用
ExprOf来替代Expr如果你查看源码的话会发现其实typedef ExprOf= Expr -
成员宏方法
-
宏 + using
-
Macro-in-macro
-
End-of-compilation generation
-
使用宏进行编译器配置
编译时使用类似于
--macro : call the given macro before typing anything else例:添加编译标记: --macro include('my.package')
openfl 示例:
-
基准测试 / 优化 (Benchmarking / Optimization)
其它
* `Context.unify(t1,t2)` 检测二个类型是否能(统一?),是检测二个类型是否能隐式转换到其中的一种???* `expr.follow(t,notRecursion=false)` ,在调用 unify() 之后调用这个方法,提升到 unify ??? **同上**```haxe
using haxe.macro.TypeTools;
//....
var t = Context.typeof(macro null); // TMono()
var ts = Context.typeof(macro "foo"); //TInst(String,[])
Context.unify(t, ts);
trace(t); // TMono()
trace(t.follow()); //TInst(String,[])
```
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
