Macros.hx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. package hrt.impl;
  2. import haxe.macro.Context;
  3. import haxe.macro.Expr;
  4. import haxe.macro.Type;
  5. class Macros {
  6. public static function enumOrNullByName<T>(e:Enum<T>, constr:String, ?params:Array<Dynamic>):T {
  7. var value = try {
  8. haxe.EnumTools.createByName(e, constr, params);
  9. } catch (_) {
  10. null;
  11. };
  12. if (value == null) {
  13. var defaultConstructors = std.Type.allEnums(e);
  14. if (defaultConstructors.length > 0) value = defaultConstructors[0];
  15. }
  16. return value;
  17. }
  18. // Get the field in the specified field path or null if any element of the path is not null
  19. static function getOrDefault(path: Array<String>, ?startIndex: Int, ?defaultValue: Expr) : Expr {
  20. function recursive(path: Array<String>, index : Int, defaultValue: Expr) : Expr {
  21. if (index == path.length - 1) {
  22. return macro $p{path};
  23. }
  24. else {
  25. var subpath = path.slice(0, index+1);
  26. return macro $p{subpath} != null ? ${recursive(path, index+1, defaultValue)} : $defaultValue;
  27. }
  28. }
  29. return recursive(path, startIndex != null ? startIndex : 0, defaultValue != null ? defaultValue : macro null);
  30. }
  31. static function forEachFieldInType(t: Type, path: Array<String>, pos, func: (t: Type, path : Array<String>, pos: Position) -> Void) : Void {
  32. switch(t) {
  33. case TType(a, _):
  34. forEachFieldInType(a.get().type, path, pos, func);
  35. case TAnonymous(a):
  36. for (f in a.get().fields) {
  37. path.push(f.name);
  38. forEachFieldInType(f.type, path, pos, func);
  39. path.pop();
  40. }
  41. default:
  42. func(t, path, pos);
  43. }
  44. }
  45. static function getTypeExpression(t : Type, path : Array<String>, pos:Position) : Expr {
  46. switch(t) {
  47. case TType(a, _):
  48. return getTypeExpression(a.get().type, path, pos);
  49. case TAnonymous(a):
  50. return createAnonDecl(a, path, pos);
  51. case TEnum(_,_):
  52. var objFields : Array<ObjectField> = [];
  53. return macro {
  54. var name = haxe.EnumTools.EnumValueTools.getName($p{path});
  55. var params = haxe.EnumTools.EnumValueTools.getParameters($p{path});
  56. if (params.length == 0) {
  57. (name:Dynamic);
  58. } else {
  59. ({
  60. "name" : name,
  61. "params" : params
  62. }:Dynamic);
  63. }
  64. };
  65. default:
  66. return macro $p{path};
  67. }
  68. }
  69. static function createAnonDecl(anonType: Ref<AnonType>, path: Array<String>, pos:Position) {
  70. var objFields : Array<ObjectField> = [];
  71. for (f in anonType.get().fields) {
  72. path.push(f.name);
  73. var e = getTypeExpression(f.type, path, pos);
  74. path.pop();
  75. objFields.push({field : f.name, expr : e});
  76. }
  77. return {expr: EObjectDecl(objFields), pos : pos};
  78. }
  79. static function containsEnum(type: Type, pos: Position) {
  80. var contains = false;
  81. forEachFieldInType(type, [], pos, function(t:Type, _, _) {
  82. switch(t) {
  83. case TEnum(_,_):
  84. contains = true;
  85. default:
  86. }
  87. });
  88. return contains;
  89. }
  90. public static macro function serializeValue(val : Expr) {
  91. var type = Context.typeof(val);
  92. if (type == null) throw "assert";
  93. if (containsEnum(type, val.pos)) {
  94. var name = "";
  95. switch(val.expr) {
  96. case EField(_, n):
  97. name = n;
  98. default:
  99. throw "assert";
  100. }
  101. return getTypeExpression(type, ["this", name], val.pos);
  102. }
  103. else {
  104. return val;
  105. }
  106. }
  107. public static macro function fixupEnumUnserialise(original : Expr, val : Expr) {
  108. var exprs = new Array<Expr>();
  109. var type = Context.typeof(original);
  110. var pos = original.pos;
  111. var name = "";
  112. switch(val.expr) {
  113. case EField(_, n):
  114. name = n;
  115. default:
  116. throw "assert";
  117. }
  118. forEachFieldInType(type, ["obj", name], pos, function(t: Type, path: Array<String>, pos: Position) : Void
  119. {
  120. switch(t) {
  121. case TEnum(enumRef,_): {
  122. var name = path.copy(); name.push("name");
  123. var params = path.copy(); params.push("parameters");
  124. var parentPath = path.copy(); parentPath.pop();
  125. var expr = macro @:pos(pos) {
  126. var objNullCheck = ${getOrDefault(parentPath)};
  127. var isString = Std.isOfType($p{path}, String);
  128. if (objNullCheck != null)
  129. $p{path} = hrt.impl.Macros.enumOrNullByName($i{enumRef.get().name}, isString ? $p{path} : ${getOrDefault(name, parentPath.length)}, isString ? null : ${getOrDefault(params, parentPath.length)});
  130. };
  131. exprs.push(expr);
  132. }
  133. default: {
  134. }
  135. }
  136. });
  137. return macro $b{exprs};
  138. }
  139. #if macro
  140. public static function buildPrefab() {
  141. var fields = Context.getBuildFields();
  142. var toSerialize = [], toCopy = [];
  143. var isRoot = Context.getLocalClass().toString() == "hrt.prefab.Prefab";
  144. var localType = haxe.macro.Tools.TTypeTools.toComplexType(Context.getLocalType());
  145. var changed = false;
  146. for( f in fields ) {
  147. if( f.name == "copy" && !isRoot ) {
  148. // inject auto cast to copy parameter
  149. switch( f.kind ) {
  150. case FFun(f) if( f.args.length == 1 && f.expr != null ):
  151. var name = f.args[0].name;
  152. var expr = f.expr;
  153. f.expr = macro @:pos(f.expr.pos) { var $name : $localType = cast $i{name}; $expr; }
  154. changed = true;
  155. default:
  156. }
  157. }
  158. if( f.meta == null ) continue;
  159. for( m in f.meta ) {
  160. switch( m.name ) {
  161. case ":s":
  162. toSerialize.push(f);
  163. case ":c":
  164. toCopy.push(f.name);
  165. default:
  166. }
  167. }
  168. }
  169. if( toSerialize.length + toCopy.length == 0 )
  170. return changed ? fields : null;
  171. var ser = [], unser = [], copy = [];
  172. var pos = Context.currentPos();
  173. for( f in toSerialize ) {
  174. switch( f.kind ) {
  175. case FProp(_, _, t, e), FVar(t,e):
  176. var name = f.name;
  177. var serCond = null;
  178. if( e == null ) {
  179. var setDef = true;
  180. var c : Constant = switch( t ) {
  181. case null: Context.error("Invalid var decl", f.pos);
  182. case TPath({ pack : [], name : "Int"|"Float" }): CInt("0");
  183. case TPath({ pack : [], name : "Bool" }): CIdent("false");
  184. //case TPath(p): setDef = false; trace(p); CIdent("null");
  185. default: setDef = false; CIdent("null");
  186. }
  187. e = { expr : EConst(c), pos : f.pos };
  188. if( setDef ) {
  189. f.kind = switch( f.kind ) {
  190. case FVar(t,_): FVar(t,e);
  191. case FProp(get,set,t,_): FProp(get,set,t,e);
  192. default: throw "assert";
  193. }
  194. }
  195. } else {
  196. var echeck = e;
  197. if( e.expr.match(EArrayDecl([])) )
  198. serCond = macro @:pos(f.pos) this.$name.length != 0;
  199. }
  200. if( serCond == null ) {
  201. var defVal = e.expr.match(EConst(_) | EBinop(_) | EUnop(_)) ? e : macro @:pos(f.pos) null;
  202. serCond = macro @:pos(pos) this.$name != $defVal;
  203. }
  204. ser.push(macro @:pos(pos) if( $serCond ) obj.$name = hrt.impl.Macros.serializeValue(this.$name));
  205. unser.push(macro @:pos(pos) hrt.impl.Macros.fixupEnumUnserialise(this.$name,obj.$name));
  206. unser.push(macro @:pos(pos) this.$name = obj.$name == null ? $e : obj.$name);
  207. copy.push(macro @:pos(pos) this.$name = p.$name);
  208. default:
  209. Context.error("Invalid serialization field", f.pos);
  210. }
  211. }
  212. for( name in toCopy ) {
  213. copy.push(macro @:pos(pos) this.$name = p.$name);
  214. }
  215. if( !isRoot ) {
  216. ser.unshift(macro @:pos(pos) super.saveSerializedFields(obj));
  217. unser.unshift(macro @:pos(pos) super.loadSerializedFields(obj));
  218. copy.unshift(macro @:pos(pos) var p : $localType = cast p);
  219. copy.unshift(macro @:pos(pos) super.copySerializedFields(p));
  220. }
  221. function makeFun(name,block) : Field {
  222. return {
  223. name : name,
  224. kind : FFun({
  225. ret : null,
  226. expr : { expr : EBlock(block), pos : pos },
  227. args : [{ name : "obj", type : macro : Dynamic }],
  228. }),
  229. meta : [{ name : ":noCompletion", pos : pos }],
  230. access : isRoot ? [] : [AOverride],
  231. pos : pos,
  232. };
  233. }
  234. if( toSerialize.length > 0 ) {
  235. fields.push(makeFun("saveSerializedFields",ser));
  236. fields.push(makeFun("loadSerializedFields",unser));
  237. }
  238. fields.push({
  239. name : "copySerializedFields",
  240. kind : FFun({
  241. ret : null,
  242. expr : { expr : EBlock(copy), pos : pos },
  243. args : [{ name : "p", type : macro : hrt.prefab.Prefab }],
  244. }),
  245. meta : [{ name : ":noCompletion", pos : pos }],
  246. access : isRoot ? [] : [AOverride],
  247. pos : pos,
  248. });
  249. return fields;
  250. }
  251. #end
  252. }