1 module zua.interop;
2 import zua.interop.table;
3 import zua.interop.userdata;
4 import zua.interop.functions;
5 import zua.interop.classwrapper;
6 import zua.vm.engine;
7 import zua.vm.engine : ValueType;
8 import std.variant;
9 import std.traits;
10 import std.typecons;
11 import std.uni;
12 import std.conv;
13 import std.algorithm.iteration;
14 import std.range;
15 
16 alias DConsumableFunction = DConsumable[] delegate(DConsumable[] args);
17 
18 /** Returns true if the type is convertible to/from a DConsumable; false otherwise */
19 enum bool isConvertible(T) =
20 	is(Unqual!T == typeof(null))
21 	|| is(Unqual!T == void)
22 	|| isNumeric!T
23 	|| is(Unqual!T == string)
24 	|| is(Unqual!T == wstring)
25 	|| is(Unqual!T == dstring)
26 	|| is(Unqual!T == bool)
27 	|| is(Unqual!T == Table)
28 	|| is(Unqual!T == Userdata)
29 	|| is(Unqual!T == DConsumable)
30 	|| is(Unqual!T == Value)
31 	|| isSomeFunction!(Unqual!T)
32 	|| is(Unqual!T == class);
33 
34 /** Returns the ValueType that corresponds to the given D-side type, or null if the given D-side type is dynamic */
35 Nullable!ValueType getValueType(T)() if (isConvertible!T) {
36 	alias U = Unqual!T;
37 	static if (is(U == typeof(null))) {
38 		return Nullable!ValueType(ValueType.Nil);
39 	}
40 	else static if (is(U == void)) {
41 		return Nullable!ValueType(ValueType.Nil);
42 	}
43 	else static if (isNumeric!T) {
44 		return Nullable!ValueType(ValueType.Number);
45 	}
46 	else static if (is(U == string) || is(U == wstring) || is(U == dstring)) {
47 		return Nullable!ValueType(ValueType.String);
48 	}
49 	else static if (is(U == bool)) {
50 		return Nullable!ValueType(ValueType.Boolean);
51 	}
52 	else static if (is(U == Table)) {
53 		return Nullable!ValueType(ValueType.Table);
54 	}
55 	else static if (is(U == Userdata) || is(U == class)) {
56 		return Nullable!ValueType(ValueType.Userdata);
57 	}
58 	else static if (is(U == DConsumableFunction)) {
59 		return Nullable!ValueType(ValueType.Function);
60 	}
61 	else static if (is(U == DConsumable) || is(U == Value)) {
62 		return Nullable!ValueType();
63 	}
64 	else static assert(0);
65 }
66 
67 /** Convert a ValueType to its canonical string form */
68 string valueTypeToString(ValueType type) {
69 	return type.to!string.toLower;
70 }
71 
72 /** A type that is consumable by D code without much fuss */
73 struct DConsumable {
74 
75 	private Value internalValue;
76 
77 	/** The value of this DConsumable */
78 	Algebraic!(
79 		typeof(null),
80 		double,
81 		string,
82 		bool,
83 		Table,
84 		Userdata,
85 		DConsumableFunction,
86 	) value;
87 
88 	alias value this;
89 
90 	/** Create a new DConsumable from the given type */
91 	this(T, string preferredName = "?")(T rawValue) if (isConvertible!T) {
92 		alias U = Unqual!T;
93 		U v = cast(U)rawValue;
94 
95 		static if (is(U == typeof(null))) {
96 			this(Value(), cast(typeof(value))null);
97 		}
98 		else static if (isNumeric!T) {
99 			double dvalue = v.to!double;
100 			this(Value(dvalue), cast(typeof(value))dvalue);
101 		}
102 		else static if (is(U == string) || is(U == wstring) || is(U == dstring)) {
103 			string svalue = v.to!string;
104 			this(Value(svalue), cast(typeof(value))svalue);
105 		}
106 		else static if (is(U == bool)) {
107 			this(Value(v), cast(typeof(value))v);
108 		}
109 		else static if (is(U == Table)) {
110 			this(v._internalTable, cast(typeof(value))v);
111 		}
112 		else static if (is(U == Userdata)) {
113 			this(v._internalUserdata, cast(typeof(value))v);
114 		}
115 		else static if (is(U == DConsumable)) {
116 			this(v.internalValue, cast(typeof(value))v.value);
117 		}
118 		else static if (is(U == Value)) {
119 			DConsumable dvalue;
120 
121 			switch (v.type) {
122 			case ValueType.Nil: dvalue = DConsumable(v, cast(typeof(DConsumable.value))null); break;
123 			case ValueType.Number: dvalue = DConsumable(v, cast(typeof(DConsumable.value))v.num); break;
124 			case ValueType.String: dvalue = DConsumable(v, cast(typeof(DConsumable.value))v.str); break;
125 			case ValueType.Boolean: dvalue = DConsumable(v, cast(typeof(DConsumable.value))v.boolean); break;
126 			case ValueType.Table: dvalue = DConsumable(v, cast(typeof(DConsumable.value))Table(v)); break;
127 			case ValueType.Userdata: dvalue = DConsumable(v, cast(typeof(DConsumable.value))Userdata(v)); break;
128 			case ValueType.Function:
129 				dvalue = DConsumable(v, cast(typeof(DConsumable.value))delegate(DConsumable[] args) {
130 					Value[] luaArgs;
131 					luaArgs.reserve(luaArgs.length);
132 					foreach (i; 0..args.length) {
133 						luaArgs ~= makeInternalValue(args[i]);
134 					}
135 					Value[] luaRes = v.func.ccall(luaArgs);
136 					DConsumable[] res;
137 					res.reserve(luaRes.length);
138 					foreach (i; 0..luaRes.length) {
139 						res ~= DConsumable(luaRes[i]);
140 					}
141 					return res;
142 				});
143 				break;
144 			// case ValueType.Thread:
145 			// TODO: this
146 			default: assert(0);
147 			}
148 
149 			this(dvalue);
150 		}
151 		else static if (isSomeFunction!U) {
152 			DConsumable[] luaFunction(DConsumable[] consumableArgs) {
153 				try {
154 					auto args = convertParameters!(U, preferredName)(consumableArgs);
155 					static if (is(ReturnType!U == void)) {
156 						v(args.expand);
157 						return [];
158 					}
159 					else {
160 						return v(args.expand).convertReturn!U;
161 					}
162 				}
163 				catch (Exception e) {
164 					if (cast(LuaError)e) {
165 						throw e;
166 					}
167 					else {
168 						throw new LuaError(Value(e.msg));
169 					}
170 				}
171 			}
172 
173 			FunctionValue funcValue = new FunctionValue;
174 			funcValue.env = null;
175 			funcValue.engine = new class Engine {
176 				override Value[] callf(FunctionValue, Value[] args) {
177 					DConsumable[] consumableArgs;
178 					consumableArgs.reserve(args.length);
179 					foreach (v; args) {
180 						consumableArgs ~= DConsumable(v);
181 					}
182 					DConsumable[] consumableResult = luaFunction(consumableArgs);
183 					Value[] res;
184 					res.reserve(consumableResult.length);
185 					foreach (v; consumableResult) {
186 						res ~= v.internalValue;
187 					}
188 					return res;
189 				}
190 			};
191 
192 			this(Value(funcValue), cast(typeof(DConsumable.value))&luaFunction);
193 		}
194 		else static if (is(U == class)) {
195 			Tuple!(DConsumable, ClassConverter!T) classWrapper = makeClassWrapper!T;
196 			Userdata uv = classWrapper[1](v);
197 			this(uv._internalUserdata, cast(typeof(DConsumable.value))uv);
198 		}
199 		else static assert(0);
200 	}
201 
202 	private this(Value internalValue, typeof(value) value) {
203 		this.internalValue = internalValue;
204 		this.value = value;
205 	}
206 
207 	/** Convert the DConsumable to a native D type, possibly failing; if exact, type coercion is not performed */
208 	Nullable!T convert(T, bool exact = false)() const {
209 		alias U = Unqual!T;
210 		static if (is(U == typeof(null))) {
211 			if (internalValue.type == ValueType.Nil) return cast(Nullable!T)null;
212 			else return Nullable!T();
213 		}
214 		else static if (isNumeric!T) {
215 			if (internalValue.type == ValueType.Number) return cast(Nullable!T)(internalValue.num.to!U);
216 			else if (internalValue.type == ValueType.String && !exact) {
217 				try {
218 					return cast(Nullable!T)(internalValue.str.to!U);
219 				}
220 				catch (ConvException e) {
221 					return Nullable!T();
222 				}
223 			}
224 			else return Nullable!T();
225 		}
226 		else static if (is(U == string) || is(U == wstring) || is(U == dstring)) {
227 			if (internalValue.type == ValueType.String) return cast(Nullable!T)(internalValue.str.to!U);
228 			else if (internalValue.type == ValueType.Number && !exact) return cast(Nullable!T)(internalValue.num.to!U);
229 			else return Nullable!T();
230 		}
231 		else static if (is(U == bool)) {
232 			static if (exact) {
233 				if (internalValue.type == ValueType.Boolean) {
234 					return Nullable!T(internalValue.boolean);
235 				}
236 				else {
237 					return Nullable!T();
238 				}
239 			}
240 			else {
241 				if (internalValue.type == ValueType.Nil || (internalValue.type == ValueType.Boolean && !internalValue.boolean)) {
242 					return Nullable!T(false);
243 				}
244 				else {
245 					return Nullable!T(true);
246 				}
247 			}
248 		}
249 		else static if (is(U == Table)) {
250 			if (internalValue.type == ValueType.Table) return cast(Nullable!T)value.get!Table;
251 			else return Nullable!T();
252 		}
253 		else static if (is(U == Userdata)) {
254 			if (internalValue.type == ValueType.Userdata) return cast(Nullable!T)value.get!Userdata;
255 			else return Nullable!T();
256 		}
257 		else static if (is(U == DConsumable)) {
258 			return Nullable!T(this);
259 		}
260 		else static if (is(U == Value)) {
261 			return Nullable!T(internalValue);
262 		}
263 		else static if (is(U == DConsumableFunction)) {
264 			return Nullable!T(delegate DConsumable[](DConsumable[] args) {
265 				return (cast(Value)internalValue).func.ccall(args.map!makeInternalValue.array).makeConsumable;
266 			});
267 		}
268 		else static if (is(U == class)) {
269 			if (internalValue.type == ValueType.Userdata) {
270 				Userdata data = value.get!Userdata;
271 				if (isClassWrapper(data)) {
272 					Object obj = cast(Object)data.data;
273 					if (cast(T)obj) {
274 						return Nullable!T(cast(T)obj);
275 					}
276 				}
277 			}
278 
279 			return Nullable!T();
280 		}
281 		else static assert(0, "Unable to convert to this type");
282 	}
283 
284 	/** Cast operation */
285 	T opCast(T)() const {
286 		Nullable!T res = convert!T;
287 		if (res.isNull) {
288 			throw new LuaError(Value("bad argument #1 to '?' (" ~ getValueType!T.get.valueTypeToString ~ " expected, got "
289 				~ internalValue.type.valueTypeToString ~ ")"));
290 		}
291 		else {
292 			return res.get;
293 		}
294 	}
295 
296 	/** Cast operation */
297 	T opCast(T, int position)() const {
298 		Nullable!T res = convert!T;
299 		if (res.isNull) {
300 			throw new LuaError(Value("bad argument #" ~ position.to!string ~ " to '?' (" ~ getValueType!T.get.valueTypeToString
301 				~ " expected, got " ~ internalValue.type.valueTypeToString ~ ")"));
302 		}
303 		else {
304 			return res.get;
305 		}
306 	}
307 
308 }
309 
310 /** Convert the DConsumable type into an internal-representation Value */
311 pragma(inline) Value makeInternalValue(DConsumable value) {
312 	return value.internalValue;
313 }
314 
315 /** Convert an internal-representation Value array into a DConsumable array */
316 pragma(inline) DConsumable[] makeConsumable(Value[] value) {
317 	DConsumable[] res;
318 	res.reserve(value.length);
319 	foreach (i; 0..value.length) {
320 		res ~= DConsumable(value[i]);
321 	}
322 	return res;
323 }
324 
325 unittest {
326 	import zua;
327 
328 	static assert(isConvertible!double);
329 	static assert(isConvertible!int);
330 
331 	import std.stdio;
332 
333 	Common c = new Common(GlobalOptions.FullAccess);
334 
335 	c.env["callD1"] = delegate(int a, int b) {
336 		return (a + b) * 1000 + a * b;
337 	};
338 
339 	c.env["callD2"] = delegate(string a, string b) {
340 		return a ~ b;
341 	};
342 
343 	c.env.expose!"callD3"(delegate(string a, string b) {
344 		return a ~ b;
345 	});
346 
347 	string cs;
348 
349 	c.env.expose!"callD4"(delegate(string a, string b) {
350 		cs = a ~ b;
351 	});
352 
353 	c.env.expose!"callD5"(delegate() {
354 		return cs;
355 	});
356 
357 	try {
358 		c.run("file.lua", q"{
359 			assert(callD1("2", 3) == 5006)
360 			assert(callD2(2, 7) == "27")
361 			assert(select(2, pcall(callD2, 2, {})) == "bad argument #2 to '?' (string expected, got table)")
362 			assert(select(2, pcall(callD3, {}, 3)) == "bad argument #1 to 'callD3' (string expected, got table)")
363 			assert(callD5() == "")
364 			assert(not callD4("a", "b"))
365 			assert(callD5() == "ab")
366 		}");
367 	}
368 	catch (LuaError e) {
369 		stderr.writeln("Error: " ~ e.data.toString);
370 		assert(0);
371 	}
372 }