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 }