1 module lua;
2 public import lua.backend.types;
3 import lua.backend;
4 import std.string;
5 import std.traits;
6 import std.conv;
7 import std.variant;
8 
9 public:
10 private __gshared bool isInitialized;
11 
12 alias LuaStateFunction = lua_CFunction;
13 alias LuaStatePtr = lua_State*;
14 
15 __gshared string LUA_PATH = "content/scripts/?.lua";
16 
17 /// Expose this to Lua via LuaUserData
18 struct Expose;
19 
20 /// Expose this to function to lua via auto-generated getters/setters
21 struct ExposeFunction;
22 
23 class LuaException : Exception {
24 public:
25     this(string message, string origin) {
26         super("<%s> from %s".format(message, origin));
27     }
28 }
29 
30 /// Gets a new Lua state.
31 LuaState newState() {
32     import std.process;
33 
34     // Make sure that lua is only loaded once in to memory.
35     if (!isInitialized) {
36         bool loaded = loadLua();
37     }
38 
39     lua_State* sptr = luaL_newstate();
40 
41     environment["LUA_PATH"] = LUA_PATH;
42     // Bind lua libs
43     // Sandbox away potentially dangerous stuff for now.
44     /*luaopen_base(sptr);
45     luaopen_string(sptr);
46     luaopen_math(sptr);
47     luaopen_table(sptr);
48 
49     // This only works via lua calls ¯\_(ツ)_/¯
50     // Tbh this might be a little dangerous, but its what i can do rn.
51     // Also pushcfunction doesn't take extern(C) elements so we do some dumb casting.
52     lua_pushcfunction(sptr, cast(LuaStateFunction) luaopen_package);
53     lua_pushliteral(sptr, LUA_LOADLIBNAME);
54 
55     // This is named wrong but w/e
56     luaL_call(sptr, 1, 0);*/
57     luaL_openlibs(sptr);
58     return new LuaState(sptr);
59 }
60 
61 /// Disposes lua from memory, run on exit.
62 /// or not, whatever floats your goat.
63 void disposeLua() {
64     unloadLua();
65 }
66 
67 enum IsInteger(T) = (is(typeof(T) == byte) || is(typeof(T) == short)
68             || is(typeof(T) == int) || is(typeof(T) == long) || is(typeof(T) == lua_Integer));
69 enum IsNumber(T) = (is(typeof(T) == float) || is(typeof(T) == double) || is(typeof(T) == lua_Number));
70 enum IsBoolean(T) = (is(typeof(T) == bool));
71 enum IsUnsigned(T) = (is(typeof(T) == ubyte) || is(typeof(T) == ushort)
72             || is(typeof(T) == uint) || is(T : ulong) || is(typeof(T) == lua_Unsigned));
73 enum IsKContext(T) = (is(typeof(T) == ptrdiff_t) || is(typeof(T) == lua_KContext));
74 enum IsString(T) = (is(T : string) || is(typeof(T) == const(char)*));
75 enum IsFunction(T) = (is(T : lua_CFunction) || is(T : LuaStateFunction));
76 
77 Variant stackToVariant(LuaState state) {
78     return stackToVariant(state.state);
79 }
80 
81 Variant stackToVariant(lua_State* state) {
82     immutable(int) type = lua_type(state, -1);
83     Variant x;
84     switch (type) {
85     case (LUA_TBOOLEAN):
86         x = lua_toboolean(state, -1);
87         break;
88     case (LUA_TLIGHTUSERDATA):
89         x = lua_touserdata(state, -1);
90         break;
91     case (LUA_TNUMBER):
92         x = lua_tonumber(state, -1);
93         break;
94     case (LUA_TSTRING):
95         x = lua_tostring(state, -1).text;
96         break;
97     default:
98         lua_pop(state, 1);
99         break;
100     }
101     return x;
102 }
103 
104 private void g_push(T)(lua_State* state, T value) {
105     static if (IsUnsigned!T) {
106         lua_pushinteger(state, cast(lua_Unsigned) value);
107     } else static if (IsInteger!T) {
108         lua_pushinteger(state, cast(lua_Integer) value);
109     } else static if (IsNumber!T) {
110         lua_pushnumber(state, cast(lua_Number) value);
111     } else static if (IsBoolean!T) {
112         lua_pushboolean(state, cast(int) value);
113     } else static if (IsString!T) {
114         lua_pushstring(state, toStringz(value));
115     } else static if (IsFunction!T) {
116         lua_pushcfunction(state, cast(lua_CFunction) value);
117     } else {
118         lua_pushlightuserdata(state, value);
119     }
120 }
121 
122 private T g_pop(T)(lua_State* state) {
123     static if (IsUnsigned!T) {
124         return cast(T) lua_tointeger(state, -1);
125     } else static if (IsInteger!T) {
126         return cast(T) lua_tointeger(state, -1);
127     } else static if (IsNumber!T) {
128         return cast(T) lua_tonumber(state, -1);
129     } else static if (IsBoolean!T) {
130         return cast(T) lua_toboolean(state, -1);
131     } else static if (IsString!T) {
132         return lua_tostring(state, -1).text;
133     } else static if (IsFunction!T) {
134         return lua_tocfunction(state.state, -1);
135     } else {
136         return cast(T) lua_touserdata(state, -1);
137     }
138 }
139 
140 mixin template luaTableDef() {
141     lua_State* state;
142 
143     void push(T)(T value) {
144         return g_push!T(state, value);
145     }
146 
147     T pop(T)() {
148         return g_pop!T(state);
149     }
150 }
151 
152 class LuaRegistry {
153 private:
154     mixin luaTableDef;
155 
156     this(LuaState state) {
157         this(state.state);
158     }
159 
160     this(lua_State* state) {
161         this.state = state;
162     }
163 
164 public:
165     void set(T, TX)(TX id, T value) {
166         push!TX(id);
167         push!T(value);
168         lua_settable(state, LUA_REGISTRYINDEX);
169     }
170 
171     T get(T, TX)(TX id) {
172         push!TX(id);
173         lua_gettable(state, LUA_REGISTRYINDEX);
174         return pop!T;
175     }
176 
177     LuaTable newTable(string name) {
178         return new LuaTable(state, name, true, false);
179     }
180 }
181 
182 /// A temporary table which will can set set in a function
183 /// You can only set values in this table, not get them.
184 class LuaLocalTable {
185 private:
186     mixin luaTableDef;
187 public:
188     this(LuaState state) {
189         this(state.state);
190     }
191 
192     this(lua_State* state) {
193         import std.stdio;
194 
195         this.state = state;
196 
197         // create table.
198         lua_newtable(this.state);
199     }
200 
201     void set(T, TX)(T id, TX value) {
202         import std.stdio;
203 
204         //push!string(id.text);
205 
206         push!TX(value);
207         lua_setfield(state, -2, toStringz(id.text));
208 
209         /*writeln("lua_settable");
210         lua_settable(state, -3);*/
211     }
212 
213     void setTable(int id) {
214         lua_settable(state, id);
215     }
216 
217     void setMetaTable(int id) {
218         lua_setmetatable(state, id);
219     }
220 
221     void bindMetatable(LuaMetaTable table) {
222         table.setMetatable();
223     }
224 }
225 
226 class LuaMetaTable {
227 private:
228     mixin luaTableDef;
229     string name;
230 
231 public:
232     this(LuaState state, string name) {
233         this(state.state, name);
234     }
235 
236     this(lua_State* state, string name) {
237         this.state = state;
238         this.name = name;
239 
240         // create table.
241         luaL_newmetatable(this.state, toStringz(name));
242     }
243 
244     /// metatable shenannigans
245     void setMetatable(int id = -3) {
246         import std.stdio;
247 
248         push!string(name);
249         lua_gettable(state, LUA_REGISTRYINDEX);
250         lua_setmetatable(state, id);
251     }
252 
253     // pushes this to the lua stack
254     void toStack() {
255         lua_gettable(state, LUA_REGISTRYINDEX);
256     }
257 
258     void set(T, TX)(T id, TX value) {
259         //push!T(id);
260         push!TX(value);
261         lua_setfield(state, -2, toStringz(id.text));
262         //lua_settable(state, -3);
263     }
264 }
265 
266 /// A lua table.
267 class LuaTable {
268 private:
269     mixin luaTableDef;
270     string name;
271     string isMetatableTo = null;
272     immutable(char)* nameRef;
273 
274     bool parentRegistry;
275 
276     this(lua_State* state) {
277         this.state = state;
278     }
279 
280     this(lua_State* state, string name, bool parentRegistry = false, bool exists = false) {
281         this(state);
282         name = name;
283         nameRef = toStringz(name);
284         parentRegistry = parentRegistry;
285 
286         if (!parentRegistry) {
287             // create table.
288             if (!exists) {
289                 lua_newtable(this.state);
290                 lua_setglobal(this.state, nameRef);
291             } else {
292                 lua_getglobal(this.state, nameRef);
293             }
294         } else {
295             lua_newtable(this.state);
296             if (!exists) {
297                 push!string(name);
298                 lua_newtable(this.state);
299                 lua_settable(state, LUA_REGISTRYINDEX);
300             } else {
301                 push!string(name);
302                 lua_gettable(state, LUA_REGISTRYINDEX);
303             }
304         }
305     }
306 
307 public:
308      ~this() {
309         if (!parentRegistry) {
310             // push this table to the stack.
311             lua_getglobal(state, nameRef);
312         } else {
313             push!string(name);
314             lua_gettable(state, LUA_REGISTRYINDEX);
315         }
316 
317         lua_pushnil(state);
318         lua_settable(state, -2);
319     }
320 
321     this(LuaState state, string name, bool exists = false) {
322         this(state.state, name, exists);
323     }
324 
325     void set(T)(int id, T value) {
326         if (isMetatableTo !is null) {
327             throw new Exception("Please restore metatable before modifying");
328         }
329         if (!parentRegistry) {
330             // push this table to the stack.
331             lua_getglobal(state, nameRef);
332         } else {
333             push!string(name);
334             lua_gettable(state, LUA_REGISTRYINDEX);
335         }
336 
337         lua_pushinteger(state, id);
338 
339         push!T(value);
340 
341         lua_settable(state, -3);
342     }
343 
344     void set(T)(string name, T value) {
345         if (isMetatableTo !is null) {
346             throw new Exception("Please restore metatable before modifying");
347         }
348         if (!parentRegistry) {
349             // push this table to the stack.
350             lua_getglobal(state, nameRef);
351         } else {
352             push!string(name);
353             lua_gettable(state, LUA_REGISTRYINDEX);
354         }
355 
356         lua_pushstring(state, toStringz(name));
357 
358         push!T(value);
359 
360         lua_settable(state, -3);
361     }
362 
363     T get(T)(int id) {
364         if (isMetatableTo !is null) {
365             throw new Exception("Please restore metatable before modifying");
366         }
367         if (!parentRegistry) {
368             // push this table to the stack.
369             lua_getglobal(state, nameRef);
370         } else {
371             push!string(name);
372             lua_gettable(state, LUA_REGISTRYINDEX);
373         }
374 
375         // push value to stack and convert it.
376         lua_rawgeti(state, -1, id);
377         T p = pop!T(state);
378 
379         // Pop value on stack and return.
380         lua_pop(state, 2);
381         return p;
382     }
383 
384     T get(T)(string name) {
385         if (isMetatableTo !is null) {
386             throw new Exception("Please restore metatable before modifying");
387         }
388         if (!parentRegistry) {
389             // push this table to the stack.
390             lua_getglobal(state, nameRef);
391         } else {
392             push!string(name);
393             lua_gettable(state, LUA_REGISTRYINDEX);
394         }
395 
396         // push value to stack and convert it.
397         lua_getfield(state, -1, toStringz(name));
398         T p = pop!T;
399 
400         // Pop value on stack and return.
401         lua_pop(state, 2);
402         return p;
403 
404     }
405 
406     // pushes this to the lua stack
407     void toStack() {
408         lua_getglobal(state, nameRef);
409     }
410 
411     /// Deletes this table from global space and sets it as metatable to another table
412     void metatableTo(LuaTable table) {
413         lua_getglobal(state, table.nameRef);
414         lua_getglobal(state, nameRef);
415         lua_setmetatable(state, -2);
416         lua_setglobal(state, table.nameRef);
417 
418         // Remove old table ref
419         lua_pushnil(state);
420         lua_setglobal(state, nameRef);
421 
422         isMetatableTo = table.name;
423     }
424 
425     void bindMetatable(LuaMetaTable table) {
426         lua_getglobal(state, nameRef);
427         table.setMetatable();
428     }
429 
430     /// restores this table in to global scope.
431     void restoreThis() {
432         if (isMetatableTo !is null) {
433             throw new Exception("This table is not bound.");
434         }
435 
436         lua_getglobal(state, toStringz(isMetatableTo));
437         if (lua_getmetatable(state, -1) > 0) {
438             lua_setglobal(state, nameRef);
439 
440             // Now remove the old table.
441             lua_getglobal(state, toStringz(isMetatableTo));
442             lua_pushnil(state);
443             lua_setmetatable(state, -2);
444 
445             isMetatableTo = null;
446         } else {
447             throw new Exception("Metatable seems to have been removed outside of scope.");
448         }
449     }
450 }
451 
452 class LuaThread {
453 private:
454     lua_State* state;
455     LuaState parent;
456 
457     this(LuaState parent) {
458         this.parent = parent;
459     }
460 
461 public:
462      ~this() {
463         lua_close(state);
464     }
465 
466     /// Executes a string as lua code in the thread.
467     void executeString(string code, string name = "unnamed") {
468         if (luaL_dostring(state, toStringz(code)) != LUA_OK) {
469             throw new LuaException(lua_tostring(state, -1).text, name);
470         }
471         lua_close(state);
472     }
473 }
474 
475 mixin template luaImpl(T) {
476 
477     import std.traits;
478     import lua.backend;
479     import std.conv;
480     import std.string;
481     import std.variant;
482 
483     mixin luaImplFuncs!T;
484 
485     static LuaStateFunction __new = (state) {
486 
487         // constructor begin
488         Variant[] params;
489         immutable(int) stack = lua_gettop(state);
490         foreach (i; 1 .. stack) {
491             import std.stdio;
492 
493             params ~= stackToVariant(state);
494             lua_pop(state, stack);
495         }
496 
497         // Instantiate T.
498         static if (is(T == struct)) {
499             T* tInstance = new T;
500             tInstance.instantiate(params);
501         } else {
502             T tInstance = new T;
503             tInstance.instantiate(params);
504         }
505 
506         // Create local table.
507         LuaLocalTable mainTable = new LuaLocalTable(state);
508         int mtabId = lua_gettop(state);
509 
510         mainTable.set!(string, T*)("selfPtr", tInstance);
511 
512         // Trait dark magic hide your children.
513         static foreach (element; __traits(derivedMembers, T)) {
514 
515             // Make sure only public members are considered
516             static if (__traits(compiles, __traits(getMember, T, element))) {
517 
518                 // Make sure it's an exposed function.
519                 static if (hasUDA!(__traits(getMember, T, element), ExposeFunction)) {
520 
521                     // bind functions.
522                     mixin(q{mainTable.set!(string, LuaStateFunction)("%s", __lua_%s_call_%s);}.format(element,
523                             T.stringof, element));
524 
525                 }
526             }
527         }
528 
529         LuaMetaTable metaTable = new LuaMetaTable(state, "A");
530         int mttabId = lua_gettop(state);
531 
532         metaTable.set!(string, LuaStateFunction)("__index", __index);
533         metaTable.set!(string, LuaStateFunction)("__newindex", __newindex);
534         metaTable.setMetatable();
535         lua_pop(state, 1);
536 
537         return 1;
538     };
539 
540     // __index function
541     static LuaStateFunction __index = (state) {
542 
543         // Index.
544         string index = lua_tostring(state, -1).text;
545 
546         if (index != "selfPtr") {
547             // get self pointer
548             lua_pushstring(state, toStringz("selfPtr"));
549             lua_rawget(state, 1);
550 
551             // self pointer.
552             T* tPtr = (cast(T*) lua_touserdata(state, -1));
553             lua_pop(state, 1);
554 
555             // Trait dark magic hide your children.
556             static foreach (element; __traits(derivedMembers, T)) {
557 
558                 // Make sure only public members are considered
559                 static if (__traits(compiles, __traits(getMember, T, element))) {
560 
561                     // They also need to have the @Expose attribute
562                     static if (hasUDA!(__traits(getMember, T, element), Expose)) {
563 
564                         // And the the right index.
565                         if (element == index) {
566 
567                             // Finally return it.
568                             mixin(q{g_push!(typeof(__traits(getMember, T, element)))(state, tPtr.%s);}.format(
569                                     element));
570                             return 1;
571                         }
572                     }
573                 }
574             }
575         }
576 
577         // return other things users might've set.
578         lua_pushstring(state, toStringz(index));
579         lua_rawget(state, 1);
580         return 1;
581     };
582 
583     static LuaStateFunction __newindex = (state) {
584 
585         // Get value
586         Variant val = stackToVariant(state);
587 
588         // ! remember to pop first
589         lua_pop(state, 1);
590 
591         // get index.
592         string index = stackToVariant(state).coerce!string;
593 
594         if (index != "selfPtr") {
595 
596             // Push string of self pointer.
597             lua_pushstring(state, toStringz("selfPtr"));
598             lua_rawget(state, 1);
599 
600             T* tPtr = (cast(T*) lua_touserdata(state, -1));
601             lua_pop(state, 1);
602 
603             // Trait dark magic hide your children.
604             static foreach (element; __traits(derivedMembers, T)) {
605 
606                 // Make sure only public members are considered
607                 static if (__traits(compiles, __traits(getMember, T, element))) {
608 
609                     // They also need to have the @Expose attribute
610                     static if (hasUDA!(__traits(getMember, T, element), Expose)) {
611 
612                         // And the the right index.
613                         if (element == index) {
614 
615                             // Finally change it.
616                             mixin(q{tPtr.%s = val.coerce!%s;}.format(element,
617                                     typeof(__traits(getMember, T, element)).stringof));
618                             return 0;
619                         }
620                     }
621                 }
622             }
623         }
624 
625         // WE DON'T WANT THE USER TO CHANGE THE SELFPTR.
626         return 0;
627     };
628 
629     LuaTable bindToLua(LuaState state) {
630 
631         LuaTable tableRef = state.newTable(T.stringof);
632         tableRef.set!LuaStateFunction("new", __new);
633         LuaTable table = state.newTable(T.stringof ~ "__callMeta");
634         table.set!LuaStateFunction("__call", __new);
635         table.metatableTo(tableRef);
636 
637         return tableRef;
638     }
639 }
640 
641 int paramCount(T, string element)() {
642     return Parameters!(typeof(__traits(getMember, T, element))).length;
643 }
644 
645 string[] params(T, string element)() {
646     string[] o = new string[paramCount!(T, element)()];
647     foreach (i, param; Parameters!(typeof(__traits(getMember, T, element)))) {
648         o[i] = param.stringof;
649     }
650     return o;
651 }
652 
653 string generateDataSetters(string[] params)() {
654     string o = q{
655         Variant[] params;
656         immutable(int) stack = lua_gettop(state);
657         foreach (i; 0 .. stack) {
658             params ~= stackToVariant(state);
659             lua_pop(state, stack);
660         }
661     };
662     static foreach (i, param; params) {
663         o ~= q{
664             %s _var_%s = params[%s].coerce!%s;
665         }.format(param, i, i, param);
666     }
667     return o;
668 }
669 
670 string generateFunctionParams(int count)() {
671     string o = "";
672     static foreach (i; 0 .. count) {
673         static if (i <= count - 2) {
674             o ~= q{_var_%s, }.format(i);
675         } else {
676             o ~= q{_var_%s}.format(i);
677         }
678     }
679     return o;
680 }
681 
682 string generateFunction(T, string element)() {
683     static if (params!(T, element).length == 0) {
684         return q{
685             static LuaStateFunction __lua_%s_call_%s = (state) {
686                 import std.stdio;
687                 import std.variant;
688                 import lua.backend;
689                 lua_pushstring(state, toStringz("selfPtr"));
690                 lua_rawget(state, 0);
691 
692                 T* tPtr = (cast(T*) lua_touserdata(state, -1));
693                 tPtr.%s();
694 
695                 return 0;
696             };
697         }.format(T.stringof, element, element);
698 
699     } else {
700         return q{
701             static LuaStateFunction __lua_%s_call_%s = (state) {
702                 import std.stdio;
703                 import std.variant;
704                 import lua.backend;
705 
706                 // Get data.
707                 %s
708 
709                 // Get selfPtr
710                 lua_pushstring(state, toStringz("selfPtr"));
711                 lua_rawget(state, 0);
712 
713                 T* tPtr = (cast(T*) lua_touserdata(state, -1));
714                 lua_pop(state, 1);
715 
716 
717                 static if (is(ReturnType!(T.%s) == void)) {
718                     // Call function
719                     tPtr.%s(%s);
720 
721                     return lua_gettop(state);
722                 } else {
723                     g_push!(ReturnType!(T.%s))(state, tPtr.%s(%s));
724                     return lua_gettop(state);
725                 }
726             };
727         }.format(// first pair  
728                 T.stringof, element,// get data
729                 generateDataSetters!(params!(T,
730                 element)), element,// can't return
731                 element, generateFunctionParams!(paramCount!(T, element)),
732 
733                 // can return 
734                 element, element, generateFunctionParams!(paramCount!(T, element)));
735     }
736 }
737 
738 mixin template luaImplFuncs(T) {
739 
740     // Trait dark magic hide your children.
741     static foreach (element; __traits(derivedMembers, T)) {
742 
743         // Make sure only public members are considered
744         static if (__traits(compiles, __traits(getMember, T, element))) {
745 
746             // Make sure it's an exposed function.
747             static if (hasUDA!(__traits(getMember, T, element), ExposeFunction)) {
748 
749                 pragma(msg, "luaBinding: __lua_" ~ T.stringof ~ "_call_" ~ element);
750                 pragma(msg, "binding: " ~ generateFunction!(T, element));
751 
752                 mixin(generateFunction!(T, element));
753 
754             }
755 
756         }
757 
758     }
759 
760 }
761 
762 /// Binding to D data.
763 class ManagedUserData(T) {
764 private:
765     LuaState state;
766     LuaTable tableRef;
767 
768     mixin luaImpl!T;
769 
770     this()(LuaState state) {
771         // TODO: Do binding via template magic.
772         state = state;
773 
774         tableRef = bindToLua(state);
775     }
776 
777     LuaTable newTable(string name, Variant[] args) {
778         return new LuaTable(state, "a");
779     }
780 }
781 
782 class LuaState {
783 private:
784     lua_State* state;
785     LuaRegistry registry;
786     LuaThread[] threads;
787 
788     this(lua_State* state, bool wrapper = false) {
789         this.state = state;
790         if (!wrapper)
791             registry = new LuaRegistry(this);
792     }
793 
794 public:
795      ~this() {
796         destroy(threads);
797         destroy(registry);
798         lua_close(state);
799     }
800 
801     /// Creates a binding to some user data (via pointer)
802     ManagedUserData!T bindUserData(T)() {
803         return new ManagedUserData!T(this);
804     }
805 
806     /**
807         Creates a new table.
808     */
809     LuaTable newTable(string name) {
810         return new LuaTable(this, name);
811     }
812 
813     /**
814         Creates a new table, which doesn't exist globally.
815         Values can only get set while the table is active.
816         Table becomes inactive as soon as other data is managed.
817     */
818     LuaLocalTable newLocalTable() {
819         return new LuaLocalTable(this);
820     }
821 
822     /**
823         Creates a new callstack (called threads in lua)
824 
825         This can be used cross-threads, be sure to use mutex when needed.
826     */
827     LuaThread newThread() {
828         LuaThread thread = new LuaThread(this);
829         threads ~= thread;
830         return thread;
831     }
832 
833     /**
834         Execute a string.
835     */
836     void executeString(string code, string name = "unnamed", bool coroutine = false) {
837         if (coroutine) {
838             LuaThread t = newThread();
839             t.executeString(code);
840             return;
841         }
842         if (luaL_dostring(state, toStringz(code)) != LUA_OK) {
843             throw new LuaException(lua_tostring(state, -1).text, name);
844         }
845     }
846 
847     /**
848         Execute a file.
849     */
850     void executeFile(string path, bool coroutine = false) {
851         import std.file;
852 
853         string data = path.readText;
854         executeString(data, path, coroutine);
855     }
856 
857     ref LuaRegistry getRegistry() {
858         return registry;
859     }
860 }