1 /// Everything to do with functions.
2 module lumars.function_;
3 
4 import bindbc.lua, lumars;
5 
6 enum LuaFuncWrapperType
7 {
8     isAliasFunc,
9     isDelegate,
10     isFunction
11 }
12 
13 /++
14  + Calls a `LuaFunc` or `LuaFuncWeak` in protected mode, which means an exception is thrown
15  + if the function produces a LUA error.
16  +
17  + Notes:
18  +  As with all functions that return a `LuaValue`, strings are copied onto the GC heap so are safe to
19  +  copy around.
20  +
21  +  As with all functions that return a `LuaValue`, the "weak" variants of data types are never returned.
22  +
23  +  Calling this function on a `LuaFuncWeak` will permanently pop the function off the stack. This is because
24  +  weak references don't have the ability to push their values.
25  +
26  + Params:
27  +  results = The maximum number of results expected to be returned by the function.
28  +            Any values that are not provided by the function are instead set to `LuaValue(LuaNil())`
29  +
30  + Returns:
31  +  A static array of `LuaValue`s representing the values returned by the LUA function.
32  + ++/
33 template pcall(size_t results)
34 {
35     static if(results > 0)
36     {
37         LuaValue[results] pcall(LuaFuncT, Args...)(LuaFuncT func, Args args)
38         {
39             cast(void)func.push();
40 
41             static foreach(arg; args)
42                 func.lua.push(arg);
43 
44             if(func.lua.pcall(args.length, results, 0) != LuaStatus.ok)
45             {
46                 const error = func.lua.get!string(-1);
47                 func.lua.pop(1);
48                 throw new Exception(error);
49             }
50 
51             typeof(return) ret;
52             static foreach(i; 1..results+1)
53                 ret[$-i] = func.lua.get!LuaValue(cast(int)-i);
54 
55             func.lua.pop(results);
56             return ret;
57         }
58     }
59     else
60     {
61         void pcall(LuaFuncT, Args...)(LuaFuncT func, Args args)
62         {
63             cast(void)func.push();
64 
65             static foreach(arg; args)
66                 func.lua.push(arg);
67 
68             if(func.lua.pcall(args.length, 0, 0) != LuaStatus.ok)
69             {
70                 const error = func.lua.get!string(-1);
71                 func.lua.pop(1);
72                 throw new Exception(error);
73             }
74         }
75     }
76 }
77 
78 private mixin template LuaFuncFuncs()
79 {
80     /++
81      + Binds this lua function into a statically typed wrapper.
82      +
83      + Params:
84      +  ReturnT = The singular return value (or void) produced by this function.
85      +  Params  = The parameters that this function takes.
86      + 
87      + Returns:
88      +  The statically typed wrapper.
89      +
90      + See_Also:
91      +  `LuaBoundFunc`
92      + ++/
93     auto bind(alias ReturnT, Params...)()
94     {
95         auto bound = LuaBoundFunc!(typeof(this), ReturnT, Params).init;
96         bound.func = this;
97         return bound;
98     }
99 }
100 
101 /++
102  + The struct used as the wrapper produced by the `LuaFunc.bind` and `LuaFuncWeak.bind` functions.
103  +
104  + This struct implements `opCall` so it can be used like a normal function.
105  +
106  + Params:
107  +  LuaFuncT = Either `LuaFunc` or `LuaFuncWeak`.
108  +  ReturnT  = The singular return value (or void) produced by this function.
109  +  Params   = The parameters that this function takes.
110  + ++/
111 struct LuaBoundFunc(alias LuaFuncT, alias ReturnT, Params...)
112 {
113     /// The underlying function
114     LuaFuncT func;
115 
116     /++
117      + Allows this wrapper to be called like a normal function.
118      +
119      + Params:
120      +  params = The parameters to pass through.
121      +
122      + Returns:
123      +  Either nothing (`ResultT == void`) or the returned value, statically ensured to be of type `ReturnT`.
124      + ++/
125     ReturnT opCall(Params params)
126     {
127         static if(is(ReturnT == void))
128             this.func.pcall!0(params);
129         else
130         {
131             auto result = this.func.pcall!1(params)[0];
132             this.func.lua.push(result);
133             scope(exit) this.func.lua.pop(1);
134             return this.func.lua.get!ReturnT(-1);
135         }
136     }
137 
138     /// Allows taking a pointer to the `opCall` function, so a LUA function can be passed around like a D one!
139     alias asDelegate = opCall;
140 }
141 
142 /++
143  + A weak reference to a lua function that currently exists on the LUA stack.
144  +
145  + Notes:
146  +  As with all weak references, while they're marginally more efficient, they're harder to use, and their
147  +  pop and push functions are no-ops.
148  + ++/
149 struct LuaFuncWeak 
150 {
151     mixin LuaFuncFuncs;
152 
153     private
154     {
155         LuaState* _lua;
156         int _index;
157     }
158 
159     /++
160      + Creates a new `LuaFuncWeak` that references a function at `index`.
161      +
162      + Throws:
163      +  `Exception` if the value at `index` in the stack isn't a function.
164      +
165      + Params:
166      +  lua   = The lua state to use.
167      +  index = The index of the function.
168      + ++/
169     this(LuaState* lua, int index)
170     {
171         lua.enforceType(LuaValue.Kind.func, index);
172         this._index = index;
173         this._lua = lua;
174     }
175 
176     /// This function is a no-op and exists to make generic code easier to write.
177     ///
178     /// Returns:
179     ///  The index on the stack of the function being referenced.
180     @safe @nogc 
181     int push() nothrow pure const
182     {
183         return this._index;
184     }
185 
186     /// This function is a no-op and exists to make generic code easier to write.
187     void pop()
188     {
189         this.lua.enforceType(LuaValue.Kind.func, this._index);
190     }
191 
192     /// Returns: The underlying `LuaState`.
193     @property @safe @nogc
194     LuaState* lua() nothrow pure
195     {
196         return this._lua;
197     }
198 }
199 
200 /++
201  + A strong reference to a LUA function.
202  +
203  + Notes:
204  +  This struct contains a ref-counted store used to keep track of both the `LuaState` as well as the table reference.
205  +
206  +  As with all strong references, the original value does not need to exist on the LUA stack, and this struct may be used
207  +  to continously refer to the value. 
208  + ++/
209 struct LuaFunc 
210 {
211     import std.typecons : RefCounted;
212     mixin LuaFuncFuncs;
213 
214     private
215     {
216         static struct State
217         {
218             LuaState* lua;
219             int ref_;
220             bool isWrapper;
221             
222             ~this()
223             {
224                 if(this.lua && !this.isWrapper)
225                     luaL_unref(this.lua.handle, LUA_REGISTRYINDEX, this.ref_);
226             }
227         }
228         RefCounted!State _state;
229     }
230 
231     /++
232      + Creates a new `LuaFunc` using the function on the top of the LUA stack as the referenced value.
233      + This function pops the original value off the stack.
234      + ++/
235     static LuaFunc makeRef(LuaState* lua)
236     {
237         lua.enforceType(LuaValue.Kind.func, -1);
238         RefCounted!State state;
239         state.lua = lua;
240         state.ref_ = luaL_ref(lua.handle, LUA_REGISTRYINDEX);
241         state.isWrapper = lua._isWrapper;
242 
243         return LuaFunc(state);
244     }
245 
246     /++
247      + Creates a new `LuaFunc` using the provided `func` as the referenced value.
248      + ++/
249     static LuaFunc makeNew(LuaState* lua, lua_CFunction func)
250     {
251         lua.push(func);
252         return LuaFunc.makeRef(lua);
253     }
254 
255     /++
256      + Pushes the function onto the stack.
257      +
258      + Returns:
259      +  The positive index of the pushed function.
260      + ++/
261     @nogc
262     int push() nothrow
263     {
264         lua_rawgeti(this._state.lua.handle, LUA_REGISTRYINDEX, this._state.ref_);
265         return this._state.lua.top;
266     }
267     
268     /++
269      + Pops the stack, ensuring that the top value is a function.
270      + ++/
271     void pop()
272     {
273         this._state.lua.enforceType(LuaValue.Kind.func, -1);
274         this._state.lua.pop(1);
275     }
276 
277     /// Returns: The underlying LUA state.
278     LuaState* lua()
279     {
280         return this._state.lua;
281     }
282 }
283 
284 /++
285  + The bare minimum wrapper needed to allow LUA to call a D function.
286  +
287  + Notes:
288  +  Any throwables will instead be converted into a lua_error.
289  +
290  + Params:
291  +  Func = The D function to wrap. This function must take a `LuaState*` as its only parameter, and it can optionally return an int
292  +         to signify how many values it has returned.
293  + ++/
294 int luaCWrapperBasic(alias Func)(lua_State* state) nothrow
295 {
296     import std.exception : assumeWontThrow;
297     import std.format : format;
298     scope wrapper = LuaState(state);
299 
300     try
301     {
302         static if(is(typeof(Func(&wrapper)) == int))
303             return Func(&wrapper);
304         else
305         {
306             Func(&wrapper);
307             return 0;
308         }
309     }
310     catch(Throwable e) // Can't allow any Throwable to execute normally as the backtrace code will crash.
311     {
312         wrapper.error(format!"A D function threw an exception: %s"(e.msg).assumeWontThrow);
313         return 0;
314     }
315 }
316 
317 /++
318  + A higher level wrapper that allows most D functions to be naturally interact with LUA.
319  +
320  + This is your go-to wrapper as it's capable of exposing most functions to LUA.
321  +
322  + Notes:
323  +  Any throwables will instead be converted into a lua_error.
324  +
325  +  The return value (if any) of `Func` will automatically be converted into a LUA value.
326  +
327  +  The parameters of `Func` will automatically be converted from the values passed by LUA.
328  +
329  +  `Func` may optionally ask for the lua state by specifying `LuaState*` as its $(B first) parameter.
330  +
331  + Params:
332  +  Func = The D function to wrap.
333  +  Type = User code shouldn't ever need to set this, please leave it as the default.
334  +
335  + Example:
336  +  `luaState.register!(std.path.buildPath!(string[]))("buildPath")`
337  + ++/
338 int luaCWrapperSmart(alias Func, LuaFuncWrapperType Type = LuaFuncWrapperType.isAliasFunc)(lua_State* state) nothrow
339 {
340     import std.format : format;
341     import std.traits : Parameters, ReturnType;
342 
343     return luaCWrapperBasic!((lua)
344     {
345         alias Params = Parameters!Func;
346         Params params;
347 
348         const argsGiven = lua.top();
349         if(argsGiven != Params.length)
350             throw new Exception("Expected exactly %s args, but was given %s.".format(Params.length, argsGiven));
351         
352         static if(is(Params[0] == LuaState*))
353         {
354             params[0] = lua;
355             static foreach(i; 1..Params.length)
356                 params[i] = lua.get!(Params[i])(i);
357         }
358         else
359         {
360             static foreach(i; 0..Params.length)
361                 params[i] = lua.get!(Params[i])(i+1);
362         }
363         alias RetT = ReturnType!Func;
364 
365         static if(Type == LuaFuncWrapperType.isDelegate)
366         {
367             alias FuncWithContext = RetT function(Params, void*);
368 
369             auto context = lua_touserdata(lua.handle, lua_upvalueindex(1));
370             auto func    = lua_touserdata(lua.handle, lua_upvalueindex(2));
371             auto dFunc   = cast(FuncWithContext)func;
372             
373             static if(is(RetT == void))
374             {
375                 dFunc(params, context);
376                 return 0;
377             }
378             else
379             {
380                 lua.push(dFunc(params, context));
381                 return 1;
382             }
383         }
384         else static if(Type == LuaFuncWrapperType.isFunction)
385         {
386             auto func = cast(Func)lua_touserdata(lua.handle, lua_upvalueindex(1));
387             static if(is(RetT == void))
388             {
389                 func(params);
390                 return 0;
391             }
392             else
393             {
394                 lua.push(func(params));
395                 return 1;
396             }
397         }
398         else
399         {
400             static if(is(RetT == void))
401             {
402                 Func(params);
403                 return 0;
404             }
405             else
406             {
407                 lua.push(Func(params));
408                 return 1;
409             }
410         }
411     })(state);
412 }
413 
414 unittest
415 {
416     auto l = LuaState(null);
417     l.doString("return function(...) return ... end");
418     auto f = l.get!LuaFuncWeak(-1);
419     auto result = f.pcall!1("Henlo!");
420     assert(result[0].textValue == "Henlo!");
421 }
422 
423 unittest
424 {
425     auto l = LuaState(null);
426     l.push(&luaCWrapperSmart!(
427         (string a, int[] b, bool[string] c)
428         {
429             assert(a == "Hello");
430             assert(b == [4, 2, 0]);
431             assert(c["true"]);
432             return true;
433         }
434     ));
435     auto f = l.get!LuaFunc(-1);
436     auto result = f.pcall!1("Hello", [4, 2, 0], ["true": true]);
437     assert(result[0].booleanValue);
438     
439     auto f2 = f.bind!(bool, string, int[], bool[string])();
440     assert(f2("Hello", [4, 2, 0], ["true": true]));
441 
442     alias F = bool delegate(string, int[], bool[string]);
443     F f3 = &f2.asDelegate;
444     assert(f3("Hello", [4, 2, 0], ["true": true]));
445 }
446 
447 unittest
448 {
449     static string func(string a, int b)
450     {
451         assert(a == "bc");
452         assert(b == 123);
453         return "doe ray me";
454     }
455 
456     auto l = LuaState(null);
457     l.push(&func);
458     auto f = LuaFuncWeak(&l, -1);
459     auto fb = f.bind!(string, string, int);
460     assert(fb("bc", 123) == "doe ray me");
461 }
462 
463 unittest
464 {
465     int closedValue;
466     void del(string a)
467     {
468         assert(a == "bc");
469         closedValue = 123;
470     }
471 
472     auto l = LuaState(null);
473     l.push(&del);
474     auto f = LuaFuncWeak(&l, -1);
475     f.pcall!0("bc");
476     assert(closedValue == 123);
477 }
478 
479 unittest
480 {
481     import std.exception : assertThrown, assertNotThrown;
482 
483     auto l = LuaState(null);
484     l.register("test", &luaCWrapperSmart!((string s){  }));
485 
486     auto f = l.globalTable.get!LuaFunc("test");
487     f.pcall!0().assertThrown;
488     f.pcall!0("abc").assertNotThrown;
489     f.pcall!0("abc", "123").assertThrown;
490 }