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 }