1 /** 2 Internal module for pushing and getting class types. 3 This feature is still a work in progress, currently, only the simplest of _classes are supported. 4 See the source code for details. 5 */ 6 module luad.conversions.classes; 7 8 import luad.conversions.functions; 9 10 import luad.c.all; 11 import luad.stack; 12 import luad.base; 13 14 import core.memory; 15 16 import std.traits; 17 import std..string : toStringz; 18 19 extern(C) private int classCleaner(lua_State* L) 20 { 21 GC.removeRoot(lua_touserdata(L, 1)); 22 return 0; 23 } 24 25 private void pushMeta(T)(lua_State* L, T obj) 26 { 27 if(luaL_newmetatable(L, T.mangleof.ptr) == 0) 28 return; 29 30 pushValue(L, T.stringof); 31 lua_setfield(L, -2, "__dclass"); 32 33 pushValue(L, T.mangleof); 34 lua_setfield(L, -2, "__dmangle"); 35 36 lua_newtable(L); //__index fallback table 37 38 foreach(member; __traits(derivedMembers, T)) 39 { 40 static if(__traits(getProtection, __traits(getMember, T, member)) == "public" && //ignore non-public fields 41 member != "this" && member != "__ctor" && //do not handle 42 member != "Monitor" && member != "toHash" && //do not handle 43 member != "toString" && member != "opEquals" && //handle below 44 member != "opCmp") //handle below 45 { 46 static if(__traits(getOverloads, T.init, member).length > 0 && !__traits(isStaticFunction, mixin("T." ~ member))) 47 { 48 pushMethod!(T, member)(L); 49 lua_setfield(L, -2, toStringz(member)); 50 } 51 } 52 } 53 54 lua_setfield(L, -2, "__index"); 55 56 pushMethod!(T, "toString")(L); 57 lua_setfield(L, -2, "__tostring"); 58 59 pushMethod!(T, "opEquals")(L); 60 lua_setfield(L, -2, "__eq"); 61 62 //TODO: handle opCmp here 63 64 65 lua_pushcfunction(L, &classCleaner); 66 lua_setfield(L, -2, "__gc"); 67 68 lua_pushvalue(L, -1); 69 lua_setfield(L, -2, "__metatable"); 70 } 71 72 void pushClassInstance(T)(lua_State* L, T obj) if (is(T == class)) 73 { 74 T* ud = cast(T*)lua_newuserdata(L, obj.sizeof); 75 *ud = obj; 76 77 pushMeta(L, obj); 78 lua_setmetatable(L, -2); 79 80 GC.addRoot(ud); 81 } 82 83 //TODO: handle foreign userdata properly (i.e. raise errors) 84 T getClassInstance(T)(lua_State* L, int idx) if (is(T == class)) 85 { 86 if(lua_getmetatable(L, idx) == 0) 87 { 88 luaL_error(L, "attempt to get 'userdata: %p' as a D object", lua_topointer(L, idx)); 89 } 90 91 lua_getfield(L, -1, "__dmangle"); //must be a D object 92 93 static if(!is(T == Object)) //must be the right object 94 { 95 size_t manglelen; 96 auto cmangle = lua_tolstring(L, -1, &manglelen); 97 if(cmangle[0 .. manglelen] != T.mangleof) 98 { 99 lua_getfield(L, -2, "__dclass"); 100 auto cname = lua_tostring(L, -1); 101 luaL_error(L, `attempt to get instance %s as type "%s"`, cname, toStringz(T.stringof)); 102 } 103 } 104 lua_pop(L, 2); //metatable and metatable.__dmangle 105 106 Object obj = *cast(Object*)lua_touserdata(L, idx); 107 return cast(T)obj; 108 } 109 110 template hasCtor(T) 111 { 112 enum hasCtor = __traits(compiles, __traits(getOverloads, T.init, "__ctor")); 113 } 114 115 // TODO: exclude private members (I smell DMD bugs...) 116 template isStaticMember(T, string member) 117 { 118 static if(__traits(compiles, mixin("&T." ~ member))) 119 { 120 static if(is(typeof(mixin("&T.init." ~ member)) == delegate)) 121 enum isStaticMember = __traits(isStaticFunction, mixin("T." ~ member)); 122 else 123 enum isStaticMember = true; 124 } 125 else 126 enum isStaticMember = false; 127 } 128 129 // For use as __call 130 void pushCallMetaConstructor(T)(lua_State* L) 131 { 132 alias typeof(__traits(getOverloads, T.init, "__ctor")) Ctor; 133 134 static T ctor(LuaObject self, ParameterTypeTuple!Ctor args) 135 { 136 return new T(args); 137 } 138 139 pushFunction(L, &ctor); 140 } 141 142 // TODO: Private static fields are mysteriously pushed without error... 143 // TODO: __index should be a function querying the static fields directly 144 void pushStaticTypeInterface(T)(lua_State* L) 145 { 146 lua_newtable(L); 147 148 enum metaName = T.mangleof ~ "_static"; 149 if(luaL_newmetatable(L, metaName.ptr) == 0) 150 { 151 lua_setmetatable(L, -2); 152 return; 153 } 154 155 static if(hasCtor!T) 156 { 157 pushCallMetaConstructor!T(L); 158 lua_setfield(L, -2, "__call"); 159 } 160 161 lua_newtable(L); 162 163 foreach(member; __traits(derivedMembers, T)) 164 { 165 static if(isStaticMember!(T, member)) 166 { 167 enum isFunction = is(typeof(mixin("T." ~ member)) == function); 168 169 static if(isFunction) 170 pushValue(L, mixin("&T." ~ member)); 171 else 172 pushValue(L, mixin("T." ~ member)); 173 174 lua_setfield(L, -2, member.ptr); 175 } 176 } 177 178 lua_setfield(L, -2, "__index"); 179 180 lua_setmetatable(L, -2); 181 } 182 183 version(unittest) 184 { 185 import luad.testing; 186 private lua_State* L; 187 } 188 189 unittest 190 { 191 L = luaL_newstate(); 192 193 static class A 194 { 195 private: 196 string s; 197 198 public: 199 int n; 200 201 this(int n, string s) 202 { 203 this.n = n; 204 this.s = s; 205 } 206 207 string foo(){ return s; } 208 209 int bar(int i) 210 { 211 return n += i; 212 } 213 214 void verifyN(int n) 215 { 216 assert(this.n == n); 217 } 218 } 219 220 static class B : A 221 { 222 this(int a, string s) 223 { 224 super(a, s); 225 } 226 227 override string foo() { return "B"; } 228 229 override string toString() { return "B"; } 230 } 231 232 void addA(in char* name, A a) 233 { 234 pushValue(L, a); 235 lua_setglobal(L, name); 236 } 237 238 auto a = new A(2, "foo"); 239 addA("a", a); 240 241 pushValue(L, a.toString()); 242 lua_setglobal(L, "a_toString"); 243 244 auto b = new B(2, "foo"); 245 addA("b", b); 246 addA("otherb", b); 247 248 pushValue(L, (A a) 249 { 250 assert(a); 251 a.bar(2); 252 }); 253 lua_setglobal(L, "func"); 254 255 luaL_openlibs(L); 256 unittest_lua(L, ` 257 --assert(a.n == 2) 258 assert(a:bar(2) == 4) 259 --assert(a.n == 4) 260 func(a) 261 assert(a:bar(2) == 8) 262 263 --a.n = 42 264 --a:verifyN(42) 265 --assert(a.n == 42) 266 267 assert(a:foo() == "foo") 268 assert(tostring(a) == a_toString) 269 270 assert(b:bar(2) == 4) 271 func(b) 272 assert(b:bar(2) == 8) 273 274 assert(b:foo() == "B") 275 assert(tostring(b) == "B") 276 277 assert(a ~= b) 278 assert(b == otherb) 279 `); 280 281 pushValue(L, cast(B)null); 282 lua_setglobal(L, "c"); 283 unittest_lua(L, `assert(c == nil)`); 284 285 pushValue(L, (B b) => assert(b is null)); 286 lua_setglobal(L, "checkNull"); 287 unittest_lua(L, `checkNull(nil)`); 288 }