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 }