1 module pixelperfectengine.scripting.lua;
2 
3 import bindbc.lua;
4 
5 import core.vararg;
6 
7 import std.string : toStringz, fromStringz;
8 import std.variant;
9 import std.typetuple;
10 import std.traits;
11 import std.exception : enforce;
12 
13 import pixelperfectengine.system.exc;
14 public import pixelperfectengine.scripting.lualib : registerLibForScripting;
15 public import pixelperfectengine.scripting.globals;
16 
17 import collections.linkedmap;
18 
19 /** 
20  * Initializes the Lua scripting engine.
21  * Returns: true if successful.
22  */
23 public bool initLua() {
24 	LuaSupport ver = loadLua();
25 	return ver == LuaSupport.lua54;
26 }
27 ///A default allocator for Lua.
28 extern(C)
29 package void* luaAllocator(void* ud, void* ptr, size_t osize, size_t nsize) @system nothrow {
30 	import core.memory;
31 	if (nsize == 0) {
32 		GC.free(ptr);
33 		return null;
34 	} else {
35 		return GC.realloc(ptr, nsize);
36 	}
37 }
38 /** 
39  * Calls a Lua function with the given name and arguments.
40  * Params:
41  *   state = The Lua state, where the function is located.
42  *   ... = The arguments to be passed to the function.
43  * Template params:
44  *   T = The return type.
45  *   funcName = The name of the function.
46  * Returns: An expected return type, which can be set to a tagged algebraic type to avoid potential mismatches.
47  * Throws: A LuaException if the return type isn't matched, the execution ran into an error, or the function isn't 
48  * found.
49  */
50 public T callLuaFunc(T, string funcName)(lua_State* state, ...) @system {
51 	lua_getglobal(state, funcName);
52 	if (!lua_isfunction(state, 0 /* LUA_TOP */))
53 		throw new LuaException(8,"Function not found");
54 	foreach (arg; _arguments) {
55 		if (arg == typeid(byte)) {
56 			lua_pushinteger(state, va_arg!byte(_argptr));
57 		} else if (arg == typeid(short)) {
58 			lua_pushinteger(state, va_arg!short(_argptr));
59 		} else if (arg == typeid(int)) {
60 			lua_pushinteger(state, va_arg!int(_argptr));
61 		} else if (arg == typeid(long)) {
62 			lua_pushinteger(state, va_arg!long(_argptr));
63 		} else if (arg == typeid(ushort)) {
64 			lua_pushinteger(state, va_arg!ushort(_argptr));
65 		} else if (arg == typeid(uint)) {
66 			lua_pushinteger(state, va_arg!uint(_argptr));
67 		} else if (arg == typeid(ubyte)) {
68 			lua_pushinteger(state, va_arg!ubyte(_argptr));
69 		} else if (arg == typeid(bool)) {
70 			lua_pushboolean(state, va_arg!bool(_argptr));
71 		} else if (arg == typeid(double)) {
72 			lua_pushnumber(state, va_arg!double(_argptr));
73 		} else if (arg == typeid(string)) {
74 			lua_pushstring(state, toStringz(va_arg!string(_argptr)));
75 		} else if (arg == typeid(LuaVar)) {
76 			va_arg!LuaVar(_argptr).pushToLuaState(state);
77 		} else assert(0, "Argument not supported!");
78 	}
79 	int errorCode = lua_pcall(state, cast(int)_arguments.length, is(T == void) ? 0 : 1, 0);
80 	static if (!is(T == void)) {
81 		LuaVar result = LuaVar(state, -1);
82 		lua_pop(state, 1);
83 		static if (is(T == LuaVar)) {
84 			return result;
85 		} else {
86 			return result.get!T;
87 		}
88 	}
89 }
90 /** 
91  * Registers a D function to be called from Lua.
92  * Code is modified from MrcSnm's example found in the HipremeEngine.
93  * Params:
94  *   state = The Lua state to handle the data from the Lua side of things.
95  * Template params:
96  *   Func = The function to be registered.
97  */
98 extern(C) public int registerDFunction(alias Func)(lua_State* state) nothrow
99 		if(isSomeFunction!(Func)) {
100 	import std.traits:Parameters, ReturnType;
101 	
102 	Parameters!Func params;
103 	int stackCounter = 0;
104 	try {
105 		foreach_reverse(ref param; params) {
106 			stackCounter--;
107 			param = luaGetFromIndex!(typeof(param))(state, stackCounter);
108 		}
109 	} catch (Exception e) {
110 		luaL_error(state, "Argument type mismatch with D functions!");
111 	}
112 	
113 	try {
114 		static if(is(ReturnType!Func == void)) {
115 			Func(params);
116 			return 0;
117 		} else static if(is(ReturnType!Func == struct)) {
118 			auto retVal = Func(params);
119 			static foreach (key ; (ReturnType!Func).tupleof) {
120 				LuaVar(__traits(child, retVal, key)).pushToLuaState(state);
121 			}
122 			return cast(int)retVal.tupleof.length;
123 		} else {
124 			LuaVar(Func(params)).pushToLuaState(state);
125 			return 1;
126 		}
127 	} catch (Exception e) {
128 		//luaPushVar(L, null);
129 		lastLuaToDException = e;
130 		try {
131 			luaL_error(state, ("A D function threw: "~e.toString~"!\0").ptr);
132 		} catch(Exception e) { 
133 			luaL_error(state, "D threw when stringifying exception!");
134 		}
135 		return 1;
136 	}
137 }
138 /** 
139  * Registers a D delegate to be called from Lua.
140  * Code is modified from MrcSnm's example found in the HipremeEngine.
141  * Params:
142  *   state = The Lua state to handle the data from the Lua side of things.
143  * Template params:
144  *   Func = The function to be registered.
145  * Note: When calling a D delegate from Lua, the first parameter is always a light user data
146  * containing the class instance that the delegate should be executed on.
147  */
148 extern (C) public int registerDDelegate(alias Func)(lua_State* state) nothrow
149 		if(isSomeFunction!(Func)) {
150 	import std.traits:Parameters, ReturnType;
151 	alias ClassType = __traits(parent, Func);
152 	Parameters!Func params;
153 	int stackCounter = 0;
154 	stackCounter--;
155 	ClassType c;
156 	try {
157 		c = luaGetFromIndex!ClassType(state, stackCounter);
158 		foreach_reverse(ref param; params) {
159 			stackCounter--;
160 			param = luaGetFromIndex!(typeof(param))(state, stackCounter);
161 		}
162 	} catch (Exception e) {
163 		luaL_error(state, "Argument type mismatch with D functions!");
164 	}
165 	
166 	try {
167 		static if(is(ReturnType!Func == void)) {
168 			__traits(child, c, Func)(params);//c.Func(params);
169 			return 0;
170 		} else static if(is(ReturnType!Func == struct)) {
171 			ReturnType!Func retVal = __traits(child, c, Func)(params);//auto retVal = c.Func(params);
172 			static foreach (key ; (ReturnType!Func).tupleof) {
173 				LuaVar(__traits(child, retVal, key)).pushToLuaState(state);
174 			}
175 			return cast(int)retVal.tupleof.length;
176 		} else {
177 			LuaVar(__traits(child, c, Func)(params)).pushToLuaState(state);//LuaVar(c.Func(params)).pushToLuaState(state);
178 			return 1;
179 		}
180 	} catch (Exception e) {
181 		//luaPushVar(L, null);
182 		lastLuaToDException = e;
183 		try {
184 			luaL_error(state, ("A D function threw: "~e.toString~"!\0").ptr);
185 		} catch(Exception e) { 
186 			luaL_error(state, "D threw when stringifying exception!");
187 		}
188 		return 1;
189 	}
190 }
191 ///Contains the pointer to the exception thrown by a D function called from the Lua side.
192 public static Exception lastLuaToDException;
193 ///Fetches a value from a lua_State variable.
194 package T luaGetFromIndex(T)(lua_State* L, int ind) {
195 	static if(isIntegral!T || isSomeChar!T) {
196 		if (!lua_isinteger(L, ind)) 
197 			throw new LuaException(7,"Type mismatch!");
198 		lua_Integer i = lua_tointeger(L, ind);
199 		return cast(T)i;
200 	} else static if(isFloatingPoint!T) {
201 		if (!lua_isnumber(L, ind))
202 			throw new LuaException(7,"Type mismatch!");
203 		lua_Number n = lua_tonumber(L, ind);
204 		return cast(T)n;
205 	} else static if(is(T == class) || is(T == interface)) {
206 		if (!lua_islightuserdata(L, ind))
207 			throw new LuaException(7,"Type mismatch!");
208 		void* data = lua_touserdata(L, ind);
209 		return cast(T)data;
210 	} else static if(is(T == string)) {
211 		import std.string : fromStringz;
212 		if (!lua_isstring(L, ind))
213 			throw new LuaException(7,"Type mismatch!");
214 		return cast(string)(fromStringz(lua_tostring(L, ind)));
215 	} else static if(is(T == void*)) {
216 		if (!lua_islightuserdata(L, ind))
217 			throw new LuaException(7,"Type mismatch!");
218 		void* data = lua_touserdata(L, ind);
219 		return data;
220 	} else static if(is(T == LuaVar)) {
221 		return LuaVar(L, ind);
222 	} else static assert(0, "Type not supported!");
223 	
224 }
225 /**
226  * Contains type identifiers related to Lua.
227  */
228 public enum LuaVarType {
229 	Null,
230 	Boolean,
231 	Number,
232 	Integer,
233 	String,
234 	Function,
235 	Userdata,
236 	Thread,
237 	Table
238 }
239 /** 
240  * Implements a Lua variable with all the underlying stuff required for it.
241  */
242 public struct LuaVar {
243 	///Contains the type of the given Lua variable.
244 	private LuaVarType		_type;
245 	private union {
246 		void*				dataPtr;
247 		long				dataInt;
248 		double				dataNum;
249 	}
250 	///Creates a Lua variable of type `void`.
251 	public static LuaVar voidType() @safe pure nothrow {
252 		LuaVar result;
253 		result._type = LuaVarType.Null;
254 		return result;
255 	}
256 	/**
257 	 * Initializes the value with the type of `val`
258 	 */
259 	public this(T)(T val) @safe pure nothrow {
260 		static if (is(T == void*) || is(T == LuaTable*)) {
261 			dataPtr = val;
262 		} else static if (isIntegral!T || isBoolean!T) {
263 			dataInt = val;
264 		} else static if (isFloatingPoint!T) {
265 			dataNum = val;
266 		} else static if (is(T == string)) {
267 			void __workaround() @system pure nothrow {
268 				dataPtr = cast(void*)toStringz(val);
269 			}
270 			void _workaround() @trusted pure nothrow {
271 				__workaround();
272 			}
273 			_workaround();
274 		} else static if (is(T == const(char)*)) {
275 			dataPtr = cast(void*)val;
276 		}
277 		setType!(T);
278 	}
279 	/**
280 	 * Fetches the given index (idx) from the lua_State (state), and initializes a LuaVar based on its type.
281 	 */
282 	public this(lua_State* state, int idx) nothrow {
283 		int type = lua_type(state, idx);
284 		switch (type) {
285 			case LUA_TLIGHTUSERDATA:
286 				dataPtr = lua_touserdata(state, idx);
287 				_type = LuaVarType.Userdata;
288 				break;
289 			case LUA_TBOOLEAN:
290 				dataInt = lua_toboolean(state, idx);
291 				_type = LuaVarType.Boolean;
292 				break;
293 			case LUA_TNUMBER:
294 				if (lua_isinteger(state, idx)) {
295 					dataInt = lua_tointeger(state, idx);
296 					_type = LuaVarType.Integer;
297 				} else {
298 					dataNum = lua_tonumber(state, idx);
299 					_type = LuaVarType.Number;
300 				}
301 				break;
302 			case LUA_TSTRING:
303 				dataPtr = cast(void*)lua_tostring(state, idx);
304 				_type = LuaVarType.String;
305 				break;
306 			default:
307 				break;
308 		}
309 	}
310 	///Sets the type of this variable internally.
311 	private void setType(T)() @nogc @safe pure nothrow {
312 		static if (isIntegral!T || isSomeChar!T) {
313 			_type = LuaVarType.Integer;
314 		} else static if (isBoolean!T) {
315 			_type = LuaVarType.Boolean;
316 		} else static if (isFloatingPoint!T) {
317 			_type = LuaVarType.Number;
318 		} else static if (is(T == string) || is(T == const(char)*)) {
319 			_type = LuaVarType.String;
320 		} else static if (is(T == void*) || is(T == class) || is(T == interface)) {
321 			_type = LuaVarType.Userdata;
322 		} else static if (is(T == LuaTable)) {
323 			_type = LuaVarType.Table;
324 		} else {
325 			_type = LuaVarType.Null;
326 		}
327 	}
328 	///Returns the type held by this stucture.
329 	public LuaVarType type() const @nogc @safe pure nothrow {
330 		return _type;
331 	}
332 	///Internal dereference.
333 	private T deRef(T)() const @nogc @system pure nothrow {
334 		static if (is(T == void*) || is(T == LuaTable*)) {
335 			return cast(T)dataPtr;
336 		} else static if (isIntegral!T || isBoolean!T) {
337 			return cast(T)dataInt;
338 		} else static if (isFloatingPoint!T) {
339 			return cast(T)dataNum;
340 		} else static if (is(T == string)) {
341 			return fromStringz(cast(const(char*))dataPtr);
342 		} else static if (is(T == const(char*))) {
343 			return cast(const(char*))dataPtr;
344 		} else static assert(0, "Unsupported type!");
345 	}
346 	/**
347 	 * Pushes the struct's value to the given lua_State.
348 	 */
349 	package void pushToLuaState(lua_State* state) @system nothrow {
350 		final switch (_type) with (LuaVarType) {
351 			case Null:
352 				lua_pushnil(state);
353 				break;
354 			case Boolean:
355 				lua_pushboolean(state, deRef!bool());
356 				break;
357 			case Number:
358 				lua_pushnumber(state, deRef!double());
359 				break;
360 			case Integer:
361 				lua_pushinteger(state, deRef!long());
362 				break;
363 			case String:
364 				lua_pushstring(state, cast(const(char*))dataPtr);
365 				break;
366 			case Function:
367 				break;
368 			case Userdata:
369 				lua_pushlightuserdata(state, dataPtr);
370 				break;
371 			case Thread:
372 				break;
373 			case Table:
374 				break;
375 		}
376 	}
377 	/**
378 	 * Returns the given type of `T`.
379 	 * Throws: LuaException in case of type mismatch.
380 	 */
381 	public T get(T)() const @trusted pure {
382 		static if (isIntegral!T) {
383 			if (_type == LuaVarType.Integer)
384 				return cast(T)deRef!long;
385 		} else static if (isFloatingPoint!T) {
386 			if (_type == LuaVarType.Integer)
387 				return cast(T)deRef!double;
388 		} else static if (is(T == string)) {
389 			if (_type == LuaVarType.String)
390 				return deRef!string;
391 		} else static if (is(T == const(char*))){
392 			if (_type == LuaVarType.String)
393 				return deRef!(const(char*));
394 		} else static if (is(T == void*)) {
395 			if (_type == LuaVarType.Userdata)
396 				return deRef!(void*);
397 		} else static if (is(T == bool)) {
398 			if (_type == LuaVarType.Boolean)
399 				return deRef!bool;
400 		} else static if (is(typeof(T) == LuaTable*)) {
401 			if (_type == LuaVarType.Table)
402 				return deRef!(LuaTable*);
403 		} else static assert(0, "Type not supported!");
404 		throw new LuaException(7, "Wrong type!");
405 	}
406 	/**
407 	 * used to implement an interface with tables in Lua.
408 	 */
409 	public bool opEquals(const LuaVar other) const @nogc @trusted pure nothrow {
410 		if (_type != other._type) return false;
411 		switch (_type) {
412 			case LuaVarType.Number:
413 				return deRef!double() == other.deRef!double();
414 			case LuaVarType.Integer:
415 				return deRef!long() == other.deRef!long();
416 			case LuaVarType.String:
417 				return deRef!string() == other.deRef!string();
418 			case LuaVarType.Boolean:
419 				return deRef!bool() == other.deRef!bool();
420 			case LuaVarType.Userdata:
421 				return deRef!(void*)() == other.deRef!(void*)();
422 			default:
423 				return false;
424 		}
425 	}
426 	/**
427 	 * Assigns the value to this struct and sets the type if needed.
428 	 */
429 	public auto opAssign(T)(T val) @safe pure nothrow {
430 		static if (is(T == void*) || is(T == LuaTable*)) {
431 			dataPtr = val;
432 		} else static if (isIntegral!T || isBoolean!T) {
433 			dataInt = val;
434 		} else static if (isFloatingPoint!T) {
435 			dataNum = val;
436 		}
437 		setType!T;
438 		return this;
439 	}
440 	/**
441 	 * Casts the type to `T` if possible.
442 	 * Throws: LuaException, if implicit type casting is impossible.
443 	 */
444 	T opCast(T)() const @safe pure {
445 		import std.math : nearbyint;
446 		static if (isIntegral!T) {
447 			if (_type == LuaVarType.Integer) {
448 				return cast(T)dataInt;
449 			} else if (_type == LuaVarType.Number) {
450 				return cast(T)nearbyint(dataNum);
451 			}
452 		} else static if (isFloatingPoint!T) {
453 			if (_type == LuaVarType.Integer) {
454 				return cast(T)dataInt;
455 			} else if (_type == LuaVarType.Number) {
456 				return cast(T)dataNum;
457 			}
458 		} else static if (is(T == string)) {
459 			string ww() const @system pure {
460 				return fromStringz(cast(char*)dataPtr);
461 			}
462 			string w() const @trusted pure {
463 				return ww;
464 			}
465 			if (_type == LuaVarType.String) {
466 				return w;
467 			}
468 		}
469 		return get!T();
470 	}
471 }
472 
473 alias LuaTable = LinkedMap!(LuaVar, LuaVar);
474 /**
475  * Implements an interface to the lua_State* variable with automatic garbage management and some basic functionality.
476  */
477 public class LuaScript {
478 	protected lua_State*		state;
479 	protected string			source;
480 	protected bool				isLoaded;
481 	/**
482 	 * Initializes a Lua script from the provided source code.
483 	 * Params:
484 	 *   source = The source code of the script file.
485 	 *   name = The name of the file.
486 	 * Throws: LuaException, if either a syntax or memory error was encountered.
487 	 */
488 	this(string source, const(char*) name) {
489 		this.source = source;
490 		state = lua_newstate(&luaAllocator, null);
491 		const int errorCode = lua_load(state, &reader, cast(void*)this, name, null);
492 		switch (errorCode) {
493 			default:
494 				break;
495 			case LUA_ERRSYNTAX:
496 				throw new LuaException(LUA_ERRSYNTAX, "Syntax error in file!");
497 			case LUA_ERRMEM:
498 				throw new LuaException(LUA_ERRMEM, "Memory error!");
499 		}
500 		registerLibForScripting(state);
501 	}
502 	///Automatically deallocates the lua_State variable.
503 	~this() {
504 		lua_close(state);
505 	}
506 	///Returns the lua_State variable for any manual use.
507 	public lua_State* getState() @nogc nothrow pure {
508 		return state;
509 	}
510 	/**
511 	 * Executes the main function of the scipt.
512 	 * Returns: A LuaVar variable with the appropriate return value if there was any.
513 	 * Throws: A LuaException if the execution ran into an error, or the function isn't found.
514 	 */
515 	public LuaVar runMain() {
516 		return callLuaFunc!(LuaVar, "main")(state);
517 	}
518 	extern(C)
519 	private static const(char*) reader(lua_State* st, void* data, size_t* size) nothrow {
520 		LuaScript ls = cast(LuaScript)data;
521 		if (ls.isLoaded) {
522 			*size = 0;
523 			return null;
524 		} else {
525 			ls.isLoaded = true;
526 			*size = ls.source.length;
527 			return ls.source.ptr;
528 		}
529 	}
530 }
531 /**
532  * Thrown on errors encountered during Lua script execution.
533  */
534 public class LuaException : PPEException {
535 	public int errorCode;
536 	///
537 	@nogc @safe pure nothrow this(int errorCode, string msg, string file = __FILE__, size_t line = __LINE__, 
538 			Throwable nextInChain = null)
539 	{
540 		this.errorCode = errorCode;
541 		super(msg, file, line, nextInChain);
542 	}
543 	///
544 	@nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__)
545 	{
546 		super(msg, file, line, nextInChain);
547 	}
548 }