Macros.hx 7.6 KB

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