1 module zua.vm.reflection;
2 import zua.vm.engine;
3 import zua.vm.engine : ValueType;
4 import std.traits;
5 import std.typecons;
6 import std.variant;
7 import std.meta;
8 import std.conv;
9 import std.uuid;
10 
11 // this interop library was created mostly for the standard library; it is dirty and low-level and should not be used in user code
12 // use zua.interop instead
13 package:
14 
15 private U fromLua(U, alias Default)(lazy string errorMsg, Nullable!Value nval) {
16 	alias T = Unqual!U;
17 
18 	void error(const string expected) {
19 		if (expected == "") {
20 			throw new LuaError(Value(errorMsg ~ " (value expected)"));
21 		}
22 		else {
23 			const string got = nval.isNull ? "no value" : nval.get.typeStr;
24 			throw new LuaError(Value(errorMsg ~ " (" ~ expected ~ " expected, got " ~ got ~ ")"));
25 		}
26 	}
27 
28 	static if (!is(Default == void)) {
29 		if (nval.isNull) {
30 			return cast(U) Default;
31 		}
32 	}
33 
34 	static if (is(T == Nullable!K, K)) {
35 		if (nval.isNull)
36 			return cast(U) Nullable!K();
37 
38 		const Value val = nval.get;
39 
40 		if (val.type == ValueType.Nil)
41 			return cast(U) Nullable!K();
42 
43 		return cast(U) fromLua!(K, Default)(errorMsg, nval).nullable;
44 	}
45 	else static if (is(T == VariantN!(n, K), size_t n, K...)) {
46 		static foreach (i; 0 .. K.length - 1) { // @suppress(dscanner.suspicious.length_subtraction)
47 			try {
48 				return cast(U)T(fromLua!(K[i], Default)(errorMsg, nval));
49 			}
50 			catch (LuaError e) {
51 			}
52 		}
53 
54 		return cast(U) T(fromLua!(K[$ - 1], Default)(errorMsg, nval));
55 	}
56 	else static if (isNumeric!T) {
57 		if (nval.isNull)
58 			error("number");
59 
60 		const Value val = nval.get;
61 
62 		double num;
63 		if (val.type == ValueType.String) {
64 			try {
65 				num = val.str.to!double;
66 			}
67 			catch (ConvException) {
68 				error("number");
69 			}
70 		}
71 		else if (val.type == ValueType.Number) {
72 			num = val.num;
73 		}
74 		else
75 			error("number");
76 
77 		return cast(U) num;
78 	}
79 	else static if (is(T == char)) {
80 		if (nval.isNull)
81 			error("string");
82 
83 		const Value val = nval.get;
84 
85 		string str;
86 		if (val.type == ValueType.String) {
87 			str = val.str;
88 		}
89 		else if (val.type == ValueType.Number) {
90 			str = val.num.to!string;
91 		}
92 		else
93 			error("string");
94 
95 		if (str.length != 1) {
96 			throw new LuaError(Value(errorMsg ~ " (string of length 1 expected)"));
97 		}
98 
99 		return cast(U) str[0];
100 	}
101 	else static if (is(T == string) || is(T == wstring) || is(T == dstring)) {
102 		if (nval.isNull)
103 			error("string");
104 
105 		const Value val = nval.get;
106 
107 		T str;
108 		if (val.type == ValueType.String) {
109 			str = val.str.to!T;
110 		}
111 		else if (val.type == ValueType.Number) {
112 			str = val.num.to!T;
113 		}
114 		else
115 			error("string");
116 
117 		return cast(U) str;
118 	}
119 	else static if (is(T == bool)) {
120 		if (nval.isNull)
121 			return cast(U)false;
122 
123 		return cast(U) nval.get.toBool;
124 	}
125 	else static if (is(T == TableValue)) {
126 		if (nval.isNull)
127 			error("table");
128 
129 		Value val = nval.get;
130 
131 		if (val.type == ValueType.Table) {
132 			return cast(U) val.table;
133 		}
134 
135 		error("table");
136 	}
137 	else static if (is(T == FunctionValue)) {
138 		if (nval.isNull)
139 			error("function");
140 
141 		Value val = nval.get;
142 
143 		if (val.type == ValueType.Function) {
144 			return cast(U) val.func;
145 		}
146 
147 		error("function");
148 	}
149 	else static if (is(T == ThreadValue)) {
150 		if (nval.isNull)
151 			error("thread");
152 
153 		Value val = nval.get;
154 
155 		if (val.type == ValueType.Thread) {
156 			return cast(U) val.thread;
157 		}
158 
159 		error("thread");
160 	}
161 	else static if (is(T == Value)) {
162 		if (nval.isNull)
163 			error("");
164 
165 		return cast(U) nval.get;
166 	}
167 	else
168 		static assert(0, "Unsupported type '" ~ fullyQualifiedName!T ~ "'");
169 	assert(0);
170 }
171 
172 private T fromLua(T, string func, alias Default)(size_t arg, Nullable!Value val) {
173 	return fromLua!(T, Default)("bad argument #" ~ (arg + 1).to!string ~ " to '" ~ func ~ "'", val);
174 }
175 
176 private Value toLua(U)(U val) {
177 	alias T = Unqual!U;
178 
179 	static if (is(T == Nullable!K, K)) {
180 		if (val.isNull) {
181 			return Value();
182 		}
183 		else {
184 			return val.get.toLua;
185 		}
186 	}
187 	else static if (is(T == VariantN!(n, K), size_t n, K...)) {
188 		static foreach (i; 0 .. K.length) {
189 			if (auto ptr = val.peek!(K[i])) {
190 				static if (is(K[i] == void))
191 					return Value();
192 				else
193 					return toLua(*ptr);
194 			}
195 		}
196 		return Value();
197 	}
198 	else static if (isNumeric!T) {
199 		return Value(cast(double) val);
200 	}
201 	else static if (is(T == char)) {
202 		return Value([cast(T) val]);
203 	}
204 	else static if (is(T == string) || is(T == wstring) || is(T == dstring)) {
205 		return Value(val.to!string);
206 	}
207 	else static if (is(T == bool)) {
208 		return Value(cast(T) val);
209 	}
210 	else static if (is(T == TableValue) || is(T == FunctionValue)
211 		|| is(T == ThreadValue)) {
212 		if (val is null) return Value();
213 		return Value(cast(T) val);
214 	}
215 	else static if (is(T == Tuple!K, K...)) {
216 		Value[] res;
217 		static foreach (i; 0 .. T.fieldNames.length) {
218 			res ~= val[i].toLua;
219 		}
220 		return Value.makeTuple(res);
221 	}
222 	else static if (is(T == Value[])) {
223 		return Value.makeTuple(val);
224 	}
225 	else static if (is(T == K[], K)) {
226 		Value[] res;
227 		foreach (v; val) {
228 			res ~= v.toLua;
229 		}
230 		return Value.makeTuple(res);
231 	}
232 	else static if (is(T == Value)) {
233 		return cast(T) val;
234 	}
235 	else
236 		static assert(0, "Unsupported type '" ~ fullyQualifiedName!T ~ "'");
237 }
238 
239 private string getPrologue(params...)() {
240 	string res = "";
241 	static foreach (i; 0 .. params.length) {
242 		res ~= fullyQualifiedName!(params[i]) ~ " param_" ~ i.to!string ~ ";\n";
243 	}
244 	return res;
245 }
246 
247 private string getArgs(bool trail, params...)() {
248 	string res = "";
249 	static foreach (i; 0 .. params.length) {
250 		res ~= "param_" ~ i.to!string ~ ", ";
251 	}
252 	if (res == "")
253 		return "";
254 	return trail ? res : res[0 .. $ - 2];
255 }
256 
257 /** Make a Lua function from a D function */
258 Value exposeFunction(alias dfunc, string funcname)() {
259 	alias STC = ParameterStorageClass;
260 	alias pstc = ParameterStorageClassTuple!dfunc;
261 	static foreach (i; 0 .. pstc.length) {
262 		static if (pstc[i] & STC.lazy_)
263 			static assert(0, "Lazy parameters cannot be used from Lua");
264 		static if (pstc[i] & STC.ref_)
265 			static assert(0, "Reference parameters cannot be used from Lua");
266 		static if (pstc[i] & STC.out_)
267 			static assert(0, "Out parameters cannot be used from Lua");
268 	}
269 
270 	FunctionValue func = new FunctionValue;
271 	func.env = null;
272 	func.engine = new class Engine {
273 
274 		override Value[] callf(FunctionValue, Value[] args) {
275 			alias variadic = variadicFunctionStyle!dfunc;
276 			assert(variadicFunctionStyle!dfunc == Variadic.typesafe || variadicFunctionStyle!dfunc == Variadic.no,
277 					"Variadic functions should be made using `T[] args...` syntax");
278 			static if (variadic == Variadic.typesafe) {
279 				alias params = Parameters!dfunc[0 .. $ - 1];
280 			}
281 			else {
282 				alias params = Parameters!dfunc;
283 			}
284 			mixin(getPrologue!params);
285 			alias ParamDefaults = ParameterDefaults!dfunc;
286 			static foreach (i; 0 .. params.length) {
287 				if (i >= args.length) {
288 					mixin("param_", i.to!string,
289 							" = fromLua!(params[i], funcname, ParamDefaults[i])(i, Nullable!Value());");
290 				}
291 				else {
292 					mixin("param_", i.to!string,
293 							" = fromLua!(params[i], funcname, ParamDefaults[i])(i, args[i].nullable);");
294 				}
295 			}
296 
297 			static if (is(ReturnType!dfunc == void)) {
298 				alias ResType = typeof(null);
299 			}
300 			else {
301 				alias ResType = ReturnType!dfunc;
302 			}
303 
304 			ResType res;
305 
306 			static if (variadic == Variadic.typesafe) {
307 				alias Last = Parameters!dfunc[$ - 1];
308 				Last tuple;
309 				if (args.length > params.length) {
310 					foreach (i; params.length .. args.length) {
311 						tuple ~= fromLua!(ForeachType!Last, funcname, void)(i, args[i].nullable);
312 					}
313 				}
314 				try {
315 					static if (is(ReturnType!dfunc == void))
316 						mixin("dfunc(", getArgs!(true, params), "tuple);");
317 					else
318 						res = mixin("dfunc(", getArgs!(true, params), "tuple)");
319 				}
320 				catch (LuaError e) {
321 					throw e;
322 				}
323 				catch (Exception e) {
324 					throw new LuaError(Value(e.msg));
325 				}
326 			}
327 			else {
328 				try {
329 					static if (is(ReturnType!dfunc == void))
330 						mixin("dfunc(", getArgs!(false, params), ");");
331 					else
332 						res = mixin("dfunc(", getArgs!(false, params), ")");
333 				}
334 				catch (LuaError e) {
335 					throw e;
336 				}
337 				catch (Exception e) {
338 					throw new LuaError(Value(e.msg));
339 				}
340 			}
341 			static if (is(ReturnType!dfunc == void)) {
342 				return [];
343 			}
344 			else {
345 				auto luaRes = res.toLua;
346 				if (luaRes.type == ValueType.Tuple) {
347 					return luaRes.tuple;
348 				}
349 				else {
350 					return [luaRes];
351 				}
352 			}
353 		}
354 
355 	};
356 	return Value(func);
357 }
358 
359 unittest {
360 	import std.stdio : writeln;
361 
362 	int add2(Nullable!int i) {
363 		if (i.isNull)
364 			return -1;
365 		return i.get + 2;
366 	}
367 
368 	string sumAll(string pre, int[] args...) {
369 		int res = 0;
370 		foreach (i; args) {
371 			res += i;
372 		}
373 		return pre ~ res.to!string;
374 	}
375 
376 	alias Mystery = Algebraic!(int, string);
377 
378 	Mystery mystery(int x) {
379 		if (x < 0)
380 			return Mystery("invalid");
381 		return Mystery(x + 3);
382 	}
383 
384 	int mystery2(Algebraic!(int, bool) val) {
385 		if (val.peek!int) {
386 			return 0;
387 		}
388 		else {
389 			return 1;
390 		}
391 	}
392 
393 	Value m = exposeFunction!(mystery, "mystery");
394 	assert(m.call([Value("-6e10")]) == [Value("invalid")]);
395 	assert(m.call([Value("7")]) == [Value(10)]);
396 
397 	Value m2 = exposeFunction!(mystery2, "mystery");
398 	assert(m2.call([Value("-6e10")]) == [Value(0)]);
399 	assert(m2.call([Value(false)]) == [Value(1)]);
400 
401 	Value v = exposeFunction!(sumAll, "sumAll");
402 	assert(v.call([Value("3 + 5 = "), Value("3"), Value("5")]) == [
403 			Value("3 + 5 = 8")
404 			]);
405 
406 	Value a = exposeFunction!(add2, "add2");
407 	assert(a.call([Value(5)]) == [Value(7)]);
408 	assert(a.call([Value("8.674")]) == [Value(10)]);
409 	assert(a.call([Value()]) == [Value(-1)]);
410 	assert(a.call([]) == [Value(-1)]);
411 	try {
412 		a.call([Value("a")]);
413 		assert(0);
414 	}
415 	catch (LuaError e) {
416 		assert(e.data == Value("bad argument #1 to 'add2' (number expected, got string)"));
417 	}
418 }