1 /// Everything to do with functions.
2 module lumars.function_;
3 
4 import bindbc.lua, lumars, std;
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     mixin LuaFuncFuncs;
212 
213     private
214     {
215         static struct State
216         {
217             LuaState* lua;
218             int ref_;
219             
220             ~this()
221             {
222                 if(this.lua)
223                     luaL_unref(this.lua.handle, LUA_REGISTRYINDEX, this.ref_);
224             }
225         }
226         RefCounted!State _state;
227     }
228 
229     /++
230      + Creates a new `LuaFunc` using the function on the top of the LUA stack as the referenced value.
231      + This function pops the original value off the stack.
232      + ++/
233     static LuaFunc makeRef(LuaState* lua)
234     {
235         lua.enforceType(LuaValue.Kind.func, -1);
236         RefCounted!State state;
237         state.lua = lua;
238         state.ref_ = luaL_ref(lua.handle, LUA_REGISTRYINDEX);
239 
240         return LuaFunc(state);
241     }
242 
243     /++
244      + Creates a new `LuaFunc` using the provided `func` as the referenced value.
245      + ++/
246     static LuaFunc makeNew(LuaState* lua, lua_CFunction func)
247     {
248         lua.push(func);
249         return LuaFunc.makeRef(lua);
250     }
251 
252     /++
253      + Pushes the function onto the stack.
254      +
255      + Returns:
256      +  The positive index of the pushed function.
257      + ++/
258     @nogc
259     int push() nothrow
260     {
261         lua_rawgeti(this._state.lua.handle, LUA_REGISTRYINDEX, this._state.ref_);
262         return this._state.lua.top;
263     }
264     
265     /++
266      + Pops the stack, ensuring that the top value is a function.
267      + ++/
268     void pop()
269     {
270         this._state.lua.enforceType(LuaValue.Kind.func, -1);
271         this._state.lua.pop(1);
272     }
273 
274     /// Returns: The underlying LUA state.
275     LuaState* lua()
276     {
277         return this._state.lua;
278     }
279 }
280 
281 /++
282  + The bare minimum wrapper needed to allow LUA to call a D function.
283  +
284  + Notes:
285  +  Any throwables will instead be converted into a lua_error.
286  +
287  + Params:
288  +  Func = The D function to wrap. This function must take a `LuaState*` as its only parameter, and it can optionally return an int
289  +         to signify how many values it has returned.
290  + ++/
291 int luaCWrapperBasic(alias Func)(lua_State* state) nothrow
292 {
293     scope wrapper = LuaState(state);
294 
295     try
296     {
297         static if(is(typeof(Func(&wrapper)) == int))
298             return Func(&wrapper);
299         else
300         {
301             Func(&wrapper);
302             return 0;
303         }
304     }
305     catch(Throwable e) // Can't allow any Throwable to execute normally as the backtrace code will crash.
306     {
307         wrapper.error(format!"A D function threw an exception: %s"(e.msg).assumeWontThrow);
308         return 0;
309     }
310 }
311 
312 /++
313  + A higher level wrapper that allows most D functions to be naturally interact with LUA.
314  +
315  + This is your go-to wrapper as it's capable of exposing most functions to LUA.
316  +
317  + Notes:
318  +  Any throwables will instead be converted into a lua_error.
319  +
320  +  The return value (if any) of `Func` will automatically be converted into a LUA value.
321  +
322  +  The parameters of `Func` will automatically be converted from the values passed by LUA.
323  +
324  +  `Func` may optionally ask for the lua state by specifying `LuaState*` as its $(B first) parameter.
325  +
326  + Params:
327  +  Func = The D function to wrap.
328  +  Type = User code shouldn't ever need to set this, please leave it as the default.
329  +
330  + Example:
331  +  `luaState.register!(std.path.buildPath!(string[]))("buildPath")`
332  + ++/
333 int luaCWrapperSmart(alias Func, LuaFuncWrapperType Type = LuaFuncWrapperType.isAliasFunc)(lua_State* state) nothrow
334 {
335     return luaCWrapperBasic!((lua)
336     {
337         alias Params = Parameters!Func;
338         Params params;
339         
340         static if(is(Params[0] == LuaState*))
341         {
342             params[0] = lua;
343             static foreach(i; 1..Params.length)
344                 params[i] = lua.get!(Params[i])(i);
345         }
346         else
347         {
348             static foreach(i; 0..Params.length)
349                 params[i] = lua.get!(Params[i])(i+1);
350         }
351         alias RetT = ReturnType!Func;
352 
353         static if(Type == LuaFuncWrapperType.isDelegate)
354         {
355             alias FuncWithContext = RetT function(Params, void*);
356 
357             auto context = lua_touserdata(lua.handle, lua_upvalueindex(1));
358             auto func    = lua_touserdata(lua.handle, lua_upvalueindex(2));
359             auto dFunc   = cast(FuncWithContext)func;
360             
361             static if(is(RetT == void))
362             {
363                 dFunc(params, context);
364                 return 0;
365             }
366             else
367             {
368                 lua.push(dFunc(params, context));
369                 return 1;
370             }
371         }
372         else static if(Type == LuaFuncWrapperType.isFunction)
373         {
374             auto func = cast(Func)lua_touserdata(lua.handle, lua_upvalueindex(1));
375             static if(is(RetT == void))
376             {
377                 func(params);
378                 return 0;
379             }
380             else
381             {
382                 lua.push(func(params));
383                 return 1;
384             }
385         }
386         else
387         {
388             static if(is(RetT == void))
389             {
390                 Func(params);
391                 return 0;
392             }
393             else
394             {
395                 lua.push(Func(params));
396                 return 1;
397             }
398         }
399     })(state);
400 }
401 
402 unittest
403 {
404     auto l = LuaState(null);
405     l.doString("return function(...) return ... end");
406     auto f = l.get!LuaFuncWeak(-1);
407     auto result = f.pcall!1("Henlo!");
408     assert(result[0].textValue == "Henlo!");
409 }
410 
411 unittest
412 {
413     auto l = LuaState(null);
414     l.push(&luaCWrapperSmart!(
415         (string a, int[] b, bool[string] c)
416         {
417             assert(a == "Hello");
418             assert(b == [4, 2, 0]);
419             assert(c["true"]);
420             return true;
421         }
422     ));
423     auto f = l.get!LuaFunc(-1);
424     auto result = f.pcall!1("Hello", [4, 2, 0], ["true": true]);
425     assert(result[0].booleanValue);
426     
427     auto f2 = f.bind!(bool, string, int[], bool[string])();
428     assert(f2("Hello", [4, 2, 0], ["true": true]));
429 
430     alias F = bool delegate(string, int[], bool[string]);
431     F f3 = &f2.asDelegate;
432     assert(f3("Hello", [4, 2, 0], ["true": true]));
433 }
434 
435 unittest
436 {
437     static string func(string a, int b)
438     {
439         assert(a == "bc");
440         assert(b == 123);
441         return "doe ray me";
442     }
443 
444     auto l = LuaState(null);
445     l.push(&func);
446     auto f = LuaFuncWeak(&l, -1);
447     auto fb = f.bind!(string, string, int);
448     assert(fb("bc", 123) == "doe ray me");
449 }
450 
451 unittest
452 {
453     int closedValue;
454     void del(string a)
455     {
456         assert(a == "bc");
457         closedValue = 123;
458     }
459 
460     auto l = LuaState(null);
461     l.push(&del);
462     auto f = LuaFuncWeak(&l, -1);
463     f.pcall!0("bc");
464     assert(closedValue == 123);
465 }