1 module lumars.state;
2 
3 import bindbc.lua, taggedalgebraic, lumars;
4 import taggedalgebraic : visit;
5 import std.typecons : Nullable;
6 
7 /// Used to represent LUA's `nil`.
8 struct LuaNil {}
9 
10 /// See `LuaValue`
11 union LuaValueUnion
12 {
13     /// LUA `nil`
14     LuaNil nil;
15 
16     /// A lua number
17     lua_Number number;
18 
19     /// A weak reference to some text. This text is managed by LUA and not D's GC so is unsafe to escape
20     const(char)[] textWeak;
21 
22     /// GC-managed text
23     string text;
24 
25     /// A bool
26     bool boolean;
27 
28     /// A weak reference to a table currently on the LUA stack.
29     LuaTableWeak tableWeak;
30 
31     /// A strong reference to a table which is in the LUA registry.
32     LuaTable table;
33 
34     /// A weak reference to a function currently on the LUA stack.
35     LuaFuncWeak funcWeak;
36 
37     /// A strong reference to a function which is in the LUA registry.
38     LuaFunc func;
39 
40     void* userData;
41 }
42 
43 /// An enumeration of various status codes LUA may return.
44 enum LuaStatus
45 {
46     ok = 0,
47     yield = LUA_YIELD,
48     errRun = LUA_ERRRUN,
49     errSyntax = LUA_ERRSYNTAX,
50     errMem = LUA_ERRMEM,
51     errErr = LUA_ERRERR,
52 }
53 
54 /// A `TaggedUnion` of `LuaValueUnion` which is used to bridge the gap between D and Lua values.
55 alias LuaValue = TaggedUnion!LuaValueUnion;
56 alias LuaNumber = lua_Number;
57 alias LuaCFunc = lua_CFunction;
58 
59 /++
60  + A light wrapper around `lua_State` with some higher level functions for quality of life purposes.
61  +
62  + This struct cannot be copied, so put it on the heap or store it as a global.
63  + ++/
64 struct LuaState
65 {
66     import std.string : toStringz;
67     import std.stdio : writefln, writeln, writef;
68 
69     @disable this(this){}
70 
71     private
72     {
73         lua_State*      _handle;
74         LuaTablePseudo  _G;
75     }
76     package bool        _isWrapper;
77 
78     /// Creates a wrapper around the given `lua_state`, or creates a new state if the given value is null.
79     @trusted
80     this(lua_State* wrapAround)
81     {
82         if(wrapAround)
83         {
84             this._handle = wrapAround;
85             this._isWrapper = true;
86         }
87         else
88         {
89             loadLuaIfNeeded();
90 
91             this._handle = luaL_newstate();
92             luaL_openlibs(this.handle);
93         }
94 
95         this._G = LuaTablePseudo(&this, LUA_GLOBALSINDEX);
96     }
97 
98     /// For non-wrappers, destroy the lua state.
99     @trusted @nogc
100     ~this() nothrow
101     {
102         if(this._handle && !this._isWrapper)
103             lua_close(this._handle);
104     }
105 
106     @nogc
107     LuaTablePseudo globalTable() nothrow
108     {
109         return this._G;
110     }
111     
112     @nogc
113     lua_CFunction atPanic(lua_CFunction func) nothrow
114     {
115         return lua_atpanic(this.handle, func);
116     }
117 
118     @nogc
119     void call(int nargs, int nresults) nothrow
120     {
121         lua_call(this.handle, nargs, nresults);
122     }
123 
124     @nogc
125     bool checkStack(int amount) nothrow
126     {
127         return lua_checkstack(this.handle, amount) != 0;
128     }
129 
130     @nogc
131     void concat(int nargs) nothrow
132     {
133         lua_concat(this.handle, nargs);
134     }
135 
136     @nogc
137     bool equal(int index1, int index2) nothrow
138     {
139         return lua_equal(this.handle, index1, index2) != 0;
140     }
141 
142     @nogc
143     void error() nothrow
144     {
145         lua_error(this.handle);
146     }
147 
148     void error(const char[] msg) nothrow
149     {
150         luaL_error(this.handle, "%s", msg.toStringz);
151     }
152 
153     LuaTableWeak pushMetatable(int ofIndex)
154     {
155         lua_getmetatable(this.handle, ofIndex);
156         return LuaTableWeak(&this, -1);
157     }
158 
159     LuaTable getMetatable(int ofIndex)
160     {
161         lua_getmetatable(this.handle, ofIndex);
162         return LuaTable.makeRef(&this);
163     }
164 
165     @nogc
166     bool lessThan(int index1, int index2) nothrow
167     {
168         return lua_lessthan(this.handle, index1, index2) != 0;
169     }
170     
171     @nogc
172     bool rawEqual(int index1, int index2) nothrow
173     {
174         return lua_rawequal(this.handle, index1, index2) != 0;
175     }
176 
177     @nogc
178     void pushTable(int tableIndex) nothrow
179     {
180         return lua_gettable(this.handle, tableIndex);
181     }
182 
183     @nogc
184     void insert(int index) nothrow
185     {
186         return lua_insert(this.handle, index);
187     }
188 
189     @nogc
190     size_t len(int index) nothrow
191     {
192         return lua_objlen(this.handle, index);
193     }
194 
195     @nogc
196     LuaStatus pcall(int nargs, int nresults, int errFuncIndex) nothrow
197     {
198         return cast(LuaStatus)lua_pcall(this.handle, nargs, nresults, errFuncIndex);
199     }
200 
201     @nogc
202     void copy(int index) nothrow
203     {
204         lua_pushvalue(this.handle, index);
205     }
206 
207     @nogc
208     void rawGet(int tableIndex) nothrow
209     {
210         lua_rawget(this.handle, tableIndex);
211     }
212 
213     @nogc
214     void rawGet(int tableIndex, int indexIntoTable) nothrow
215     {
216         lua_rawgeti(this.handle, tableIndex, indexIntoTable);
217     }
218 
219     @nogc
220     void rawSet(int tableIndex) nothrow
221     {
222         lua_rawset(this.handle, tableIndex);
223     }
224 
225     @nogc
226     void rawSet(int tableIndex, int indexIntoTable) nothrow
227     {
228         lua_rawseti(this.handle, tableIndex, indexIntoTable);
229     }
230 
231     void getGlobal(const char[] name)
232     {
233         lua_getglobal(this.handle, name.toStringz);
234     }
235 
236     void setGlobal(const char[] name)
237     {
238         lua_setglobal(this.handle, name.toStringz);
239     }
240 
241     void register(const char[] name, LuaCFunc func) nothrow
242     {
243         lua_register(this.handle, name.toStringz, func);
244     }
245 
246     /++
247      + Registers the given D function into Lua, under a specific name within the global table.
248      +
249      + `Func` can be any normal D function. By default it is wrapped using `luaCWrapperSmart`
250      + to allow a (mostly) seamless way of interfacing D and Lua together. Please see `luaCWrapperSmart`'s
251      + documentation if you'd like to learn more.
252      +
253      + Params:
254      +  Func = The function to wrap.
255      +  name = The name to register the function under.
256      + ++/
257     void register(alias Func)(const char[] name)
258     {
259         this.register(name, &luaCWrapperSmart!Func);
260     }
261 
262     /++
263      + Similar to the other register functions, except this one will register the functions
264      + into a single table, before registering the resulting table into the global table.
265      +
266      + In other words: If you want to make a "library" table then this is the function for you.
267      +
268      + `Args` must have an even number of elements, where each two elements are a pair.
269      +
270      + For each pair in `Args`, the first element is the name to register the function under, and
271      + the last element is the function itself to register.
272      +
273      + For example, if you did: `register!("a", (){}, "b", (){})("library")`, then the result is
274      + a global table called "library" with the functions "a" and "b" (e.g. `library.a()`).
275      + ++/
276     void register(Args...)(const char[] libname)
277     if(Args.length % 2 == 0)
278     {
279         import std.traits : getUDAs;
280         luaL_Reg[(Args.length / 2) + 1] reg;
281 
282         static foreach(i; 0..Args.length/2)
283             reg[i] = luaL_Reg(Args[i*2].ptr, &luaCWrapperSmart!(Args[i*2+1]));
284 
285         luaL_register(this.handle, libname.toStringz, reg.ptr);
286     }
287 
288     @nogc
289     void remove(int index) nothrow
290     {
291         lua_remove(this.handle, index);
292     }
293 
294     @nogc
295     void replace(int index) nothrow
296     {
297         lua_replace(this.handle, index);
298     }
299 
300     @nogc
301     void setMetatable(int ofIndex) nothrow
302     {
303         lua_setmetatable(this.handle, ofIndex);
304     }
305 
306     void checkArg(bool condition, int argNum, const(char)[] extraMsg = null) nothrow
307     {
308         luaL_argcheck(this.handle, condition ? 1 : 0, argNum, extraMsg ? extraMsg.toStringz : null);
309     }
310 
311     bool callMetamethod(int index, const char[] method) nothrow
312     {
313         return luaL_callmeta(this.handle, index, method.toStringz) != 0;
314     }
315 
316     @nogc
317     void checkAny(int arg) nothrow
318     {
319         luaL_checkany(this.handle, arg);
320     }
321 
322     @nogc
323     ptrdiff_t checkInt(int arg) nothrow
324     {
325         return luaL_checkinteger(this.handle, arg);
326     }
327 
328     @nogc
329     const(char)[] checkStringWeak(int arg) nothrow
330     {
331         size_t len;
332         const ptr = luaL_checklstring(this.handle, arg, &len);
333         return ptr[0..len];
334     }
335 
336     string checkString(int arg) nothrow
337     {
338         return checkStringWeak(arg).idup;
339     }
340 
341     @nogc
342     LuaNumber checkNumber(int arg) nothrow
343     {
344         return luaL_checknumber(this.handle, arg);
345     }
346 
347     @nogc
348     void checkType(LuaValue.Kind type, int arg) nothrow
349     {
350         int t;
351 
352         final switch(type) with(LuaValue.Kind)
353         {
354             case nil: t = LUA_TNIL; break;
355             case number: t = LUA_TNUMBER; break;
356             case textWeak:
357             case text: t = LUA_TSTRING; break;
358             case boolean: t = LUA_TBOOLEAN; break;
359             case tableWeak:
360             case table: t = LUA_TTABLE; break;
361             case funcWeak:
362             case func: t = LUA_TFUNCTION; break;
363             case userData: t = LUA_TLIGHTUSERDATA; break;
364         }
365 
366         luaL_checktype(this.handle, arg, t);
367     }
368 
369     @nogc
370     bool isType(T)(int index) nothrow
371     {
372         return this.type(index) == LuaState.luaValueKindFor!T;
373     }
374 
375     @nogc
376     static LuaValue.Kind luaValueKindFor(T)() pure nothrow
377     {
378         import std.typecons : Nullable;
379         import std.traits   : isNumeric, isDynamicArray, isAssociativeArray, 
380                               isPointer, KeyType, ValueType,
381                               isInstanceOf, TemplateArgsOf;
382 
383         static if(is(T == string))
384             return LuaValue.Kind.text;
385         else static if(is(T == const(char)[]))
386             return LuaValue.Kind.text;
387         else static if(is(T : const(bool)))
388             return LuaValue.Kind.boolean;
389         else static if(isNumeric!T)
390             return LuaValue.Kind.number;
391         else static if(is(T == typeof(null)) || is(T == LuaNil))
392             return LuaValue.Kind.nil;
393         else static if(is(T == LuaTableWeak))
394             return LuaValue.Kind.table;
395         else static if(is(T == LuaTable))
396             return LuaValue.Kind.table;
397         else static if(isDynamicArray!T)
398             return LuaValue.Kind.table;
399         else static if(isAssociativeArray!T)
400             return LuaValue.Kind.table;
401         else static if(is(T == LuaCFunc))
402             return LuaValue.Kind.func;
403         else static if(is(T == LuaFuncWeak))
404             return LuaValue.Kind.func;
405         else static if(is(T == LuaFunc))
406             return LuaValue.Kind.func;
407         else static if(isInstanceOf!(Nullable, T))
408             return luaValueKindFor!(TemplateArgsOf!T[0])();
409         else static if(isPointer!T || is(T == class))
410             return LuaValue.Kind.userData;
411         else static if(is(T == struct))
412             return LuaValue.Kind.table;
413         else static assert(false, "Don't know how to convert any LUA values into type: "~T.stringof);
414     }
415 
416     void doFile(const char[] file)
417     {
418         const status = luaL_dofile(this.handle, file.toStringz);
419         if(status != LuaStatus.ok)
420         {
421             const error = this.get!string(-1);
422             this.pop(1);
423             throw new LuaException(error);
424         }
425     }
426 
427     void doFile(const char[] file, scope ref LuaTable table)
428     {
429         const loadStatus = luaL_loadfile(this.handle, file.toStringz);
430         if(loadStatus != LuaStatus.ok)
431         {
432             const error = this.get!string(-1);
433             this.pop(1);
434             throw new LuaException(error);
435         }
436 
437         table.push();
438         const fenvResult = lua_setfenv(this.handle, -2);
439         if(fenvResult == 0)
440             throw new LuaException("Failed to set function environment");
441 
442         const callStatus = lua_pcall(this.handle, 0, 0, 0);
443         if(callStatus != LuaStatus.ok)
444         {
445             const error = this.get!string(-1);
446             this.pop(1);
447             throw new LuaException(error);
448         }
449     }
450 
451     void doString(const char[] str)
452     {
453         const status = luaL_dostring(this.handle, str.toStringz);
454         if(status != LuaStatus.ok)
455         {
456             const error = this.get!string(-1);
457             this.pop(1);
458             throw new LuaException(error);
459         }
460     }
461 
462     void doString(const char[] str, scope ref LuaTable table)
463     {
464         const loadStatus = luaL_loadstring(this.handle, str.toStringz);
465         if(loadStatus != LuaStatus.ok)
466         {
467             const error = this.get!string(-1);
468             this.pop(1);
469             throw new LuaException(error);
470         }
471 
472         table.push();
473         const fenvResult = lua_setfenv(this.handle, -2);
474         if(fenvResult == 0)
475             throw new LuaException("Failed to set function environment");
476 
477         const callStatus = lua_pcall(this.handle, 0, 0, 0);
478         if(callStatus != LuaStatus.ok)
479         {
480             const error = this.get!string(-1);
481             this.pop(1);
482             throw new LuaException(error);
483         }
484     }
485 
486     void loadFile(const char[] file)
487     {
488         const status = luaL_loadfile(this.handle, file.toStringz);
489         if(status != LuaStatus.ok)
490         {
491             const error = this.get!string(-1);
492             this.pop(1);
493             throw new LuaException(error);
494         }
495     }
496 
497     void loadString(const char[] str)
498     {
499         const status = luaL_loadstring(this.handle, str.toStringz);
500         if(status != LuaStatus.ok)
501         {
502             const error = this.get!string(-1);
503             this.pop(1);
504             throw new LuaException(error);
505         }
506     }
507 
508     @nogc
509     ptrdiff_t optInt(int arg, ptrdiff_t default_) nothrow
510     {
511         return luaL_optinteger(this.handle, arg, default_);
512     }
513 
514     @nogc
515     LuaNumber optNumber(int arg, LuaNumber default_) nothrow
516     {
517         return luaL_optnumber(this.handle, arg, default_);
518     }
519 
520     void printStack()
521     {
522         writeln("[LUA STACK]");
523         foreach(i; 0..this.top)
524         {
525             const type = lua_type(this.handle, i+1);
526             writef("\t[%s] \t", i+1);
527 
528             switch(type)
529             {
530                 case LUA_TBOOLEAN: writefln("%s\t%s", "BOOL", this.get!bool(i+1)); break;
531                 case LUA_TFUNCTION: writefln("%s\t%s", "FUNC", lua_tocfunction(this.handle, i+1)); break;
532                 case LUA_TLIGHTUSERDATA: writefln("%s\t%s", "LIGHT", lua_touserdata(this.handle, i+1)); break;
533                 case LUA_TNIL: writefln("%s", "NIL"); break;
534                 case LUA_TNUMBER: writefln("%s\t%s", "NUM", this.get!lua_Number(i+1)); break;
535                 case LUA_TSTRING: writefln("%s\t%s", "STR", this.get!(const(char)[])(i+1)); break;
536                 case LUA_TTABLE: writefln("%s", "TABL"); break;
537                 case LUA_TTHREAD: writefln("%s\t%s", "THRD", lua_tothread(this.handle, i+1)); break;
538                 case LUA_TUSERDATA: writefln("%s\t%s", "USER", lua_touserdata(this.handle, i+1)); break;
539                 default: writefln("%s\t%s", "UNKN", type); break;
540             }
541         }
542     }
543 
544     void push(T)(T value)
545     {
546         import std.conv : to;
547         import std.traits : isNumeric, isDynamicArray, isAssociativeArray, isDelegate, isPointer, isFunction,
548                             PointerTarget, KeyType, ValueType, FieldNameTuple, TemplateOf, TemplateArgsOf;
549 
550         static if(is(T == typeof(null)) || is(T == LuaNil))
551             lua_pushnil(this.handle);
552         else static if(is(T : const(char)[]))
553             lua_pushlstring(this.handle, value.ptr, value.length);
554         else static if(isNumeric!T)
555             lua_pushnumber(this.handle, value.to!lua_Number);
556         else static if(is(T : const(bool)))
557             lua_pushboolean(this.handle, value ? 1 : 0);
558         else static if(isDynamicArray!T)
559         {
560             alias ValueT = typeof(value[0]);
561 
562             lua_createtable(this.handle, 0, value.length.to!int);
563             foreach(i, v; value)
564             {
565                 this.push(v);
566                 lua_rawseti(this.handle, -2, cast(int)i+1);
567             }
568         }
569         else static if(isAssociativeArray!T)
570         {
571             alias KeyT = KeyType!T;
572             alias ValueT = ValueType!T;
573 
574             lua_createtable(this.handle, 0, value.length.to!int);
575             foreach(k, v; value)
576             {
577                 this.push(k);
578                 this.push(v);
579                 lua_rawset(this.handle, -3);
580             }
581         }
582         else static if(is(T == LuaTable) || is(T == LuaFunc))
583             value.push();
584         else static if(is(T == LuaTableWeak) || is(T == LuaFuncWeak))
585             this.copy(value.push());
586         else static if(is(T : lua_CFunction))
587             lua_pushcfunction(this.handle, value);
588         else static if(isDelegate!T)
589         {
590             lua_pushlightuserdata(this.handle, value.ptr);
591             lua_pushlightuserdata(this.handle, value.funcptr);
592             lua_pushcclosure(this.handle, &luaCWrapperSmart!(T, LuaFuncWrapperType.isDelegate), 2);
593         }
594         else static if(isPointer!T && isFunction!(PointerTarget!T))
595         {
596             lua_pushlightuserdata(this.handle, value);
597             lua_pushcclosure(this.handle, &luaCWrapperSmart!(T, LuaFuncWrapperType.isFunction), 1);
598         }
599         else static if(__traits(isSame, TemplateOf!T, Nullable))
600         {
601             if (value.isNull)
602                 lua_pushnil(this.handle);
603             else
604                 push!(TemplateArgsOf!T)(value.get());
605         }
606         else static if(isPointer!T)
607             lua_pushlightuserdata(this.handle, value);
608         else static if(is(T == class))
609             lua_pushlightuserdata(this.handle, cast(void*)value);
610         else static if(is(T == struct))
611         {
612             lua_newtable(this.handle);
613 
614             static foreach(member; FieldNameTuple!T)
615             {
616                 this.push(member);
617                 this.push(mixin("value."~member));
618                 lua_settable(this.handle, -3);
619             }
620         }
621         else static assert(false, "Don't know how to push type: "~T.stringof);
622     }
623 
624     void push(LuaValue value)
625     {
626         value.visit!(
627             (_){ this.push(_); }
628         );
629     }
630 
631     @nogc
632     int top() nothrow
633     {
634         return lua_gettop(this.handle);
635     }
636 
637     @nogc
638     void pop(int amount) nothrow
639     {
640         lua_pop(this.handle, amount);
641     }
642 
643     T get(T)(int index)
644     {
645         import std.conv : to;
646         import std.format : format;
647         import std.traits : isNumeric, isDynamicArray, isAssociativeArray, isPointer, KeyType, ValueType, TemplateOf, TemplateArgsOf;
648         import std.typecons : isTuple;
649 
650         static if(is(T == string))
651         {
652             if(this.isType!LuaNil(index))
653                 return null;
654 
655             this.enforceType(LuaValue.Kind.text, index);
656             size_t len;
657             auto ptr = lua_tolstring(this.handle, index, &len);
658             return ptr[0..len].idup;
659         }
660         else static if(is(T == const(char)[]))
661         {
662             if(this.isType!LuaNil(index))
663                 return null;
664 
665             this.enforceType(LuaValue.Kind.text, index);
666             size_t len;
667             auto ptr = lua_tolstring(this.handle, index, &len);
668             return ptr[0..len];
669         }
670         else static if(is(T : const(bool)))
671         {
672             this.enforceType(LuaValue.Kind.boolean, index);
673             return lua_toboolean(this.handle, index) != 0;
674         }
675         else static if(isNumeric!T)
676         {
677             this.enforceType(LuaValue.Kind.number, index);
678             return lua_tonumber(this.handle, index).to!T;
679         }
680         else static if(is(T == typeof(null)) || is(T == LuaNil))
681         {
682             this.enforceType(LuaValue.Kind.nil, index);
683             return LuaNil();
684         }
685         else static if(is(T == LuaTableWeak))
686         {
687             this.enforceType(LuaValue.Kind.table, index);
688             return T(&this, index);
689         }
690         else static if(is(T == LuaTable))
691         {
692             this.enforceType(LuaValue.Kind.table, index);
693             this.copy(index);
694             return T.makeRef(&this);
695         }
696         else static if(isDynamicArray!T)
697         {
698             if(this.isType!LuaNil(index))
699                 return null;
700 
701             this.enforceType(LuaValue.Kind.table, index);
702             T ret;
703             ret.length = lua_objlen(this.handle, index);
704 
705             this.push(null);
706             const tableIndex = index < 0 ? index - 1 : index;
707             while(this.next(tableIndex))
708             {
709                 ret[this.get!size_t(-2) - 1] = this.get!(typeof(ret[0]))(-1);
710                 this.pop(1);
711             }
712 
713             return ret;
714         }
715         else static if(isAssociativeArray!T)
716         {
717             if(this.isType!LuaNil(index))
718                 return null;
719 
720             this.enforceType(LuaValue.Kind.table, index);
721             T ret;
722 
723             this.push(null);
724             const tableIndex = index < 0 ? index - 1 : index;
725             while(this.next(tableIndex))
726             {
727                 ret[this.get!(KeyType!T)(-2)] = this.get!(ValueType!T)(-1);
728                 this.pop(1);
729             }
730 
731             return ret;
732         }
733         else static if(is(T == LuaCFunc))
734         {
735             this.enforceType(LuaValue.Kind.func, index);
736             return lua_tocfunction(this.handle, index);
737         }
738         else static if(is(T == LuaFuncWeak))
739         {
740             this.enforceType(LuaValue.Kind.func, index);
741             return LuaFuncWeak(&this, index);
742         }
743         else static if(is(T == LuaFunc))
744         {
745             this.enforceType(LuaValue.Kind.func, index);
746             this.copy(index);
747             return T.makeRef(&this);
748         }
749         else static if(isPointer!T || is(T == class))
750         {
751             if(this.isType!LuaNil(index))
752                 return null;
753 
754             this.enforceType(LuaValue.Kind.userData, index);
755             return cast(T)lua_touserdata(this.handle, index);
756         }
757         else static if(is(T == LuaValue))
758         {
759             switch(this.type(index))
760             {
761                 case LuaValue.Kind.text: return LuaValue(this.get!string(index));
762                 case LuaValue.Kind.number: return LuaValue(this.get!lua_Number(index));
763                 case LuaValue.Kind.boolean: return LuaValue(this.get!bool(index));
764                 case LuaValue.Kind.nil: return LuaValue(this.get!LuaNil(index));
765                 case LuaValue.Kind.table: return LuaValue(this.get!LuaTable(index));
766                 case LuaValue.Kind.func: return LuaValue(this.get!LuaFunc(index));
767                 case LuaValue.Kind.userData: return LuaValue(this.get!(void*)(index));
768                 default: throw new LuaException("Don't know how to convert type into a LuaValue: "~this.type(index).to!string);
769             }
770         }
771         else static if(__traits(isSame, TemplateOf!(T), Nullable))
772         {
773             if(this.isType!LuaNil(index))
774                 return T.init;
775             return cast(T)get!(TemplateArgsOf!(T)[0])(index);
776         }
777         else static if (isTuple!T)
778         {
779             T ret;
780             alias params = T.Types;
781             int idx = 0;
782 
783             static foreach (i, p; params)
784             {
785                 idx = cast(int)(i - params.length);
786                 if (this.isType!(params[i])(idx))
787                 {
788                     ret[i] = this.get!(p)(idx);
789                 }
790                 else
791                 {
792                     const firstArgIndex = cast(int)(-params.length);
793                     static if(i == 0)
794                     {
795                         if(this.type(firstArgIndex) == LuaValue.Kind.table)
796                             return getAsStruct!(T, T.fieldNames)(firstArgIndex);
797 
798                         throw new LuaTypeException(
799                             format!(
800                                 "When parsing tuple %s, expected value %s to be %s, but it was %s. "
801                                 ~" Attempted to parse 0th value as a struct instead, but 0th value is not a table."
802                             )(
803                                 T.stringof,
804                                 idx,
805                                 LuaState.luaValueKindFor!(params[i]),
806                                 this.type(firstArgIndex),
807                             )
808                         );
809                     }
810                     else
811                     {
812                         throw new LuaTypeException(
813                             format!"When parsing tuple %s, expected value %s to be %s, but it was %s."(
814                                 T.stringof,
815                                 idx,
816                                 LuaState.luaValueKindFor!(params[i]),
817                                 this.type(firstArgIndex)
818                             )
819                         );
820                     }
821                 }
822             }
823 
824             return ret;
825         }
826         else static if(is(T == struct))
827         {
828             return getAsStruct!(T, __traits(allMembers, T))(index);
829         }
830         else static assert(false, "Don't know how to convert any LUA values into type: "~T.stringof);
831     }
832 
833     T getAsStruct(T, Names ...)(int index)
834     {
835         this.enforceType(LuaValue.Kind.table, index);
836         T ret;
837 
838         this.push(null);
839         const tableIndex = index < 0 ? index - 1 : index;
840         While: while(this.next(tableIndex))
841         {
842             const field = this.get!(const(char)[])(-2);
843 
844             static foreach(member; Names)
845             {
846                 if(field == member)
847                 {
848                     mixin("ret."~member~"= this.get!(typeof(ret."~member~"))(-1);");
849                     this.pop(1);
850                     continue While;
851                 }
852             }
853 
854             this.pop(1);
855         }
856         return ret; 
857     }
858 
859     @nogc
860     bool next(int index) nothrow
861     {
862         this.assertIndex(index);
863         return lua_next(this.handle, index) != 0;
864     }
865 
866     void enforceType(LuaValue.Kind expected, int index)
867     {
868         import std.exception : enforce;
869         import std.format    : format;
870         const type = this.type(index);
871         enforce!LuaTypeException(type == expected, "Expected value at stack index %s to be of type %s but it is %s".format(
872             index, expected, type
873         ));
874     }
875 
876     @nogc
877     LuaValue.Kind type(int index) nothrow
878     {
879         assert(this.top > 0, "Stack is empty.");
880         this.assertIndex(index);
881         const type = lua_type(this.handle, index);
882 
883         switch(type)
884         {
885             case LUA_TBOOLEAN: return LuaValue.Kind.boolean;
886             case LUA_TNIL: return LuaValue.Kind.nil;
887             case LUA_TNUMBER: return LuaValue.Kind.number;
888             case LUA_TSTRING: return LuaValue.Kind.text;
889             case LUA_TTABLE: return LuaValue.Kind.table;
890             case LUA_TFUNCTION: return LuaValue.Kind.func;
891             case LUA_TLIGHTUSERDATA: return LuaValue.Kind.userData;
892 
893             default: 
894                 return LuaValue.Kind.nil;
895         }
896     }
897 
898     /++
899      + Attempts to call `debug.traceback`. A string is expected to be on top of the stack,
900      + otherwise the function will abort the attempt.
901      +
902      + Otherwise, the string on top of the stack is consumed and a new string will be pushed containing
903      + the traceback (if possible).
904      +
905      + Args:
906      +  level = Same as the `level` parameter for `debug.traceback`. Essentially, how many functions to not include in the traceback
907      + ++/
908     void traceback(int level = 2)
909     {
910         if(!this.isType!string(-1))
911             return;
912 
913         const str = this.get!string(-1);
914         this.pop(1);
915 
916         bool found;
917         auto debugTable = this.globalTable.tryGet!LuaTable("debug", found);
918         if(!found)
919         {
920             this.push(str~"\n"~"[Could not produce traceback: `debug` does not exist in _G table]");
921             return;
922         }
923 
924         auto tracebackFunc = debugTable.tryGet!LuaFunc("traceback", found);
925         if(!found)
926         {
927             this.push(str~"\n"~"[Could not produce traceback: `traceback` does not exist in `debug` table]");
928             return;
929         }
930 
931         this.push(tracebackFunc);
932         this.push(str);
933         this.push(level);
934         this.call(2, 1);
935     }
936 
937     @property @safe @nogc
938     inout(lua_State*) handle() nothrow pure inout
939     {
940         return this._handle;
941     }
942 
943     @nogc
944     private void assertIndex(int index) nothrow
945     {
946         if(index > 0)
947             assert(this.top >= index, "Index out of bounds");
948         else
949             assert(this.top + index >= 0, "Index out of bounds");
950     }
951 }
952 
953 private void loadLuaIfNeeded()
954 {
955     version(BindLua_Static){}
956     else
957     {
958         const ret = loadLua();
959         if(ret != luaSupport) {
960             if(ret == LuaSupport.noLibrary) 
961                 throw new LuaException("Lua library not found.");
962             else if(ret == LuaSupport.badLibrary) 
963                 throw new LuaException("Lua library is corrupt or for a different platform.");
964             else
965                 throw new LuaException("Lua library is the wrong version, or some unknown error occured.");
966         }
967     }
968 }
969 
970 import std.exception : basicExceptionCtors;
971 class LuaException : Exception
972 {
973     mixin basicExceptionCtors;
974 }
975 class LuaTypeException : LuaException
976 {
977     mixin basicExceptionCtors;
978 }
979 class LuaArgumentException : LuaException
980 {
981     mixin basicExceptionCtors;
982 }
983 class LuaStackException : LuaException
984 {
985     mixin basicExceptionCtors;
986 }
987 
988 unittest
989 {
990     auto l = LuaState(null);
991     l.push(null);
992     assert(l.type(-1) == LuaValue.Kind.nil);
993     assert(l.get!LuaValue(-1).kind == LuaValue.Kind.nil);
994     assert(l.get!string(-1) == null);
995     assert(l.get!(const(char)[])(-1) == null);
996     l.pop(1);
997 
998     l.push(LuaNil());
999     assert(l.type(-1) == LuaValue.Kind.nil);
1000     assert(l.get!LuaValue(-1).kind == LuaValue.Kind.nil);
1001     assert(l.get!string(-1) == null);
1002     assert(l.get!(const(char)[])(-1) == null);
1003     l.pop(1);
1004 
1005     l.push(false);
1006     assert(l.get!LuaValue(-1).kind == LuaValue.Kind.boolean);
1007     assert(!l.get!bool(-1));
1008     l.pop(1);
1009 
1010     l.push(20);
1011     assert(l.get!LuaValue(-1).kind == LuaValue.Kind.number);
1012     assert(l.get!int(-1) == 20);
1013     l.pop(1);
1014 
1015     l.push("abc");
1016     assert(l.get!LuaValue(-1).kind == LuaValue.Kind.text);
1017     assert(l.get!string(-1) == "abc");
1018     assert(l.get!(const(char)[])(-1) == "abc");
1019     l.pop(1);
1020 
1021     l.push(["abc", "one"]);
1022     assert(l.get!(string[])(-1) == ["abc", "one"]);
1023     l.pop(1);
1024 
1025     l.push([LuaValue(200), LuaValue("abc")]);
1026     assert(l.get!(LuaValue[])(-1) == [LuaValue(200), LuaValue("abc")]);
1027     l.pop(1);
1028 
1029     l.push(LuaNil());
1030     assert(l.get!(Nullable!bool)(-1) == Nullable!(bool).init);
1031     l.pop(1);
1032 
1033     Nullable!bool nb;
1034     l.push(nb);
1035     assert(l.get!(Nullable!bool)(-1) == Nullable!(bool).init);
1036     l.pop(1);
1037 
1038     l.push(123);
1039     assert(l.get!(Nullable!int)(-1) == 123);
1040     l.pop(1);
1041 
1042     Nullable!int ni = 234;
1043     l.push(ni);
1044     assert(l.get!(Nullable!int)(-1) == 234);
1045     l.pop(1);
1046 }
1047 
1048 unittest
1049 {
1050     auto l = LuaState(null);
1051     l.register!(() => 123)("abc");
1052     l.doString("assert(abc() == 123)");
1053 }
1054 
1055 unittest
1056 {
1057     auto l = LuaState(null);
1058     l.register!(
1059         "funcA", () => "a",
1060         "funcB", () => "b"
1061     )("lib");
1062     l.doString("assert(lib.funcA() == 'a') assert(lib.funcB() == 'b')");
1063 }
1064 
1065 unittest
1066 {
1067     auto l = LuaState(null);
1068     l.doString("abba = 'chicken tikka'");
1069     assert(l.globalTable.get!string("abba") == "chicken tikka");
1070     l.globalTable["baab"] = "tikka chicken";
1071     assert(l.globalTable.get!string("baab") == "tikka chicken");
1072 }
1073 
1074 unittest
1075 {
1076     static struct B
1077     {
1078         string a;
1079     }
1080 
1081     static struct C
1082     {
1083         string a;
1084     }
1085  
1086     static struct A
1087     {
1088         string a;
1089         B[] b;
1090         C[string] c;
1091     }
1092 
1093     auto a = A(
1094         "bc",
1095         [B("c")],
1096         ["c": C("123")]
1097     );
1098 
1099     auto l = LuaState(null);
1100     l.push(a);
1101 
1102     auto luaa = l.get!A(-1);
1103     assert(luaa.a == "bc");
1104     assert(luaa.b.length == 1);
1105     assert(luaa.b == [B("c")]);
1106     assert(luaa.c.length == 1);
1107     assert(luaa.c["c"] == C("123"));
1108 }
1109 
1110 unittest
1111 {
1112     auto state = LuaState(null);
1113     auto print = state._G.get!LuaFunc("print");
1114     auto _G1 = LuaTable.makeNew(&state);
1115     auto _G2 = LuaTable.makeNew(&state);
1116     _G1["abc"] = 123;
1117     _G1["print"] = print;
1118     _G2["abc"] = 321;
1119     _G2["print"] = print;
1120 
1121     const code = "print(abc)";
1122 
1123     state.doString(code, _G1);
1124     state.doString(code, _G2);
1125 }