1 /// Everything to do with functions. 2 module lumars.function_; 3 4 import bindbc.lua, lumars; 5 6 // Mostly for internal use - controls how luaCWrapperSmart calls the underlying D function. 7 enum LuaFuncWrapperType 8 { 9 isAliasFunc, 10 isDelegate, 11 isFunction 12 } 13 14 /++ 15 + When used as the _last_ parameter of a function: Allows the function to capture any amount of additional arguments 16 + passed in from Lua. 17 + 18 + When used as the return type of a function: Allows the function to return a dynamic amount of values to Lua. 19 + ++/ 20 struct LuaVariadic 21 { 22 alias array this; 23 LuaValue[] array; 24 } 25 26 // (replacing with Tuple soon, not documenting) 27 deprecated("Deprecated in favour of using std.typecons.Tuple") 28 struct LuaMultiReturn(T...) 29 { 30 alias ValueTuple = T; 31 alias values this; 32 T values; 33 34 this()(T values) 35 { 36 this.values = values; 37 } 38 } 39 40 /++ 41 + See: `luaCWrapperSmart` 42 + 43 + In essence though, marking a function with this UDA means that the function wants to take 44 + full control of interfacing with Lua, instead of having `luaCWrapperSmart` do all the heavy lifting. 45 + 46 + This is needed in some specific cases, such as `luaOverloads`. 47 + ++/ 48 struct LuaBasicFunction{} 49 50 /++ 51 + Calls a `LuaFunc` or `LuaFuncWeak` in protected mode, which means an exception is thrown 52 + if the function produces a LUA error. 53 + 54 + Notes: 55 + As with all functions that return a `LuaValue`, strings are copied onto the GC heap so are safe to 56 + copy around. 57 + 58 + As with all functions that return a `LuaValue`, the "weak" variants of data types are never returned. 59 + 60 + Calling this function on a `LuaFuncWeak` will permanently pop the function off the stack. This is because 61 + weak references don't have the ability to push their values. 62 + 63 + Params: 64 + results = The maximum number of results expected to be returned by the function. 65 + Any values that are not provided by the function are instead set to `LuaValue(LuaNil())` 66 + 67 + Returns: 68 + A static array of `LuaValue`s representing the values returned by the LUA function. 69 + ++/ 70 template pcall(size_t results) 71 { 72 static if(results > 0) 73 { 74 LuaValue[results] pcall(LuaFuncT, Args...)(LuaFuncT func, Args args) 75 { 76 cast(void)func.push(); 77 78 static foreach(arg; args) 79 func.lua.push(arg); 80 81 if(func.lua.pcall(args.length, results, 0) != LuaStatus.ok) 82 { 83 const error = func.lua.get!string(-1); 84 func.lua.pop(1); 85 throw new Exception(error); 86 } 87 88 typeof(return) ret; 89 static foreach(i; 1..results+1) 90 ret[$-i] = func.lua.get!LuaValue(cast(int)-i); 91 92 func.lua.pop(results); 93 return ret; 94 } 95 } 96 else 97 { 98 void pcall(LuaFuncT, Args...)(LuaFuncT func, Args args) 99 { 100 cast(void)func.push(); 101 102 static foreach(arg; args) 103 func.lua.push(arg); 104 105 if(func.lua.pcall(args.length, 0, 0) != LuaStatus.ok) 106 { 107 const error = func.lua.get!string(-1); 108 func.lua.pop(1); 109 throw new Exception(error); 110 } 111 } 112 } 113 } 114 115 private mixin template LuaFuncFuncs() 116 { 117 /++ 118 + Binds this lua function into a statically typed wrapper. 119 + 120 + Params: 121 + ReturnT = The singular return value (or void) produced by this function. 122 + Params = The parameters that this function takes. 123 + 124 + Returns: 125 + The statically typed wrapper. 126 + 127 + See_Also: 128 + `LuaBoundFunc` 129 + ++/ 130 auto bind(alias ReturnT, Params...)() 131 { 132 auto bound = LuaBoundFunc!(typeof(this), ReturnT, Params).init; 133 bound.func = this; 134 return bound; 135 } 136 } 137 138 /++ 139 + The struct used as the wrapper produced by the `LuaFunc.bind` and `LuaFuncWeak.bind` functions. 140 + 141 + This struct implements `opCall` so it can be used like a normal function. 142 + 143 + Params: 144 + LuaFuncT = Either `LuaFunc` or `LuaFuncWeak`. 145 + ReturnT = The singular return value (or void) produced by this function. 146 + Params = The parameters that this function takes. 147 + ++/ 148 struct LuaBoundFunc(alias LuaFuncT, alias ReturnT, Params...) 149 { 150 /// The underlying function 151 LuaFuncT func; 152 153 /++ 154 + Allows this wrapper to be called like a normal function. 155 + 156 + Params: 157 + params = The parameters to pass through. 158 + 159 + Returns: 160 + Either nothing (`ResultT == void`) or the returned value, statically ensured to be of type `ReturnT`. 161 + ++/ 162 ReturnT opCall(Params params) 163 { 164 import std.typecons : isTuple; 165 166 static if(is(ReturnT == void)) 167 this.func.pcall!0(params); 168 else static if (isTuple!ReturnT) 169 { 170 auto results = this.func.pcall!(ReturnT.Types.length)(params); 171 foreach (r; results) 172 { 173 this.func.lua.push(r); 174 } 175 scope(exit) this.func.lua.pop(ReturnT.Types.length); 176 return this.func.lua.get!ReturnT(-1); 177 } 178 else 179 { 180 auto result = this.func.pcall!1(params)[0]; 181 this.func.lua.push(result); 182 scope(exit) this.func.lua.pop(1); 183 return this.func.lua.get!ReturnT(-1); 184 } 185 } 186 187 /// Allows taking a pointer to the `opCall` function, so a LUA function can be passed around like a D one! 188 alias asDelegate = opCall; 189 } 190 191 /++ 192 + A weak reference to a lua function that currently exists on the LUA stack. 193 + 194 + Notes: 195 + As with all weak references, while they're marginally more efficient, they're harder to use, and their 196 + pop and push functions are no-ops. 197 + ++/ 198 struct LuaFuncWeak 199 { 200 mixin LuaFuncFuncs; 201 202 private 203 { 204 LuaState* _lua; 205 int _index; 206 } 207 208 /++ 209 + Creates a new `LuaFuncWeak` that references a function at `index`. 210 + 211 + Throws: 212 + `Exception` if the value at `index` in the stack isn't a function. 213 + 214 + Params: 215 + lua = The lua state to use. 216 + index = The index of the function. 217 + ++/ 218 this(LuaState* lua, int index) 219 { 220 lua.enforceType(LuaValue.Kind.func, index); 221 this._index = index; 222 this._lua = lua; 223 } 224 225 /// This function is a no-op and exists to make generic code easier to write. 226 /// 227 /// Returns: 228 /// The index on the stack of the function being referenced. 229 @safe @nogc 230 int push() nothrow pure const 231 { 232 return this._index; 233 } 234 235 /// This function is a no-op and exists to make generic code easier to write. 236 void pop() 237 { 238 this.lua.enforceType(LuaValue.Kind.func, this._index); 239 } 240 241 /// Returns: The underlying `LuaState`. 242 @property @safe @nogc 243 LuaState* lua() nothrow pure 244 { 245 return this._lua; 246 } 247 } 248 249 /++ 250 + A strong reference to a LUA function. 251 + 252 + Notes: 253 + This struct contains a ref-counted store used to keep track of both the `LuaState` as well as the table reference. 254 + 255 + As with all strong references, the original value does not need to exist on the LUA stack, and this struct may be used 256 + to continously refer to the value. 257 + ++/ 258 struct LuaFunc 259 { 260 import std.typecons : RefCounted; 261 mixin LuaFuncFuncs; 262 263 private 264 { 265 static struct State 266 { 267 LuaState* lua; 268 int ref_; 269 bool isWrapper; 270 271 ~this() 272 { 273 if(this.lua && !this.isWrapper) 274 luaL_unref(this.lua.handle, LUA_REGISTRYINDEX, this.ref_); 275 } 276 } 277 RefCounted!State _state; 278 } 279 280 /++ 281 + Creates a new `LuaFunc` using the function on the top of the LUA stack as the referenced value. 282 + This function pops the original value off the stack. 283 + ++/ 284 static LuaFunc makeRef(LuaState* lua) 285 { 286 lua.enforceType(LuaValue.Kind.func, -1); 287 RefCounted!State state; 288 state.lua = lua; 289 state.ref_ = luaL_ref(lua.handle, LUA_REGISTRYINDEX); 290 state.isWrapper = lua._isWrapper; 291 292 return LuaFunc(state); 293 } 294 295 /++ 296 + Creates a new `LuaFunc` using the provided `func` as the referenced value. 297 + ++/ 298 static LuaFunc makeNew(LuaState* lua, lua_CFunction func) 299 { 300 lua.push(func); 301 return LuaFunc.makeRef(lua); 302 } 303 304 /++ 305 + Pushes the function onto the stack. 306 + 307 + Returns: 308 + The positive index of the pushed function. 309 + ++/ 310 @nogc 311 int push() nothrow 312 { 313 lua_rawgeti(this._state.lua.handle, LUA_REGISTRYINDEX, this._state.ref_); 314 return this._state.lua.top; 315 } 316 317 /++ 318 + Pops the stack, ensuring that the top value is a function. 319 + ++/ 320 void pop() 321 { 322 this._state.lua.enforceType(LuaValue.Kind.func, -1); 323 this._state.lua.pop(1); 324 } 325 326 /// Returns: The underlying LUA state. 327 LuaState* lua() 328 { 329 return this._state.lua; 330 } 331 } 332 333 /++ 334 + The bare minimum wrapper needed to allow LUA to call a D function. 335 + 336 + Notes: 337 + Any throwables will instead be converted into a lua_error. 338 + 339 + Params: 340 + Func = The D function to wrap. This function must take a `LuaState*` as its only parameter, and it can optionally return an int 341 + to signify how many values it has returned. 342 + ++/ 343 extern(C) 344 int luaCWrapperBasic(alias Func)(lua_State* state) nothrow 345 { 346 import std.exception : assumeWontThrow; 347 import std.format : format; 348 scope LuaState wrapper; 349 350 try wrapper = LuaState(state); 351 catch(Throwable ex) // @suppress(dscanner.suspicious.catch_em_all) 352 return 0; 353 354 try 355 { 356 static if(is(typeof(Func(&wrapper)) == int)) 357 return Func(&wrapper); 358 else 359 { 360 Func(&wrapper); 361 return 0; 362 } 363 } 364 catch(Throwable e) // Can't allow any Throwable to execute normally as the backtrace code will crash. // @suppress(dscanner.suspicious.catch_em_all) 365 { 366 try 367 { 368 wrapper.push(e.msg); 369 wrapper.traceback(); 370 371 const str = wrapper.get!string(-1).assumeWontThrow; 372 wrapper.pop(1); 373 374 wrapper.error(str); 375 return 0; 376 } 377 catch(Throwable e2) // @suppress(dscanner.suspicious.catch_em_all) 378 { 379 wrapper.error(e.msg~"\n[WARN] Traceback code failed: "~e2.msg); 380 return 0; 381 } 382 } 383 } 384 385 /++ 386 + A higher level wrapper that allows most D functions to be naturally interact with LUA. 387 + 388 + This is your go-to wrapper as it's capable of exposing most functions to LUA. 389 + 390 + Notes: 391 + Any throwables will instead be converted into a lua_error. 392 + 393 + The return value (if any) of `Func` will automatically be converted into a LUA value. 394 + 395 + The parameters of `Func` will automatically be converted from the values passed by LUA. 396 + 397 + `Func` may optionally ask for the lua state by specifying `LuaState*` as its $(B first) parameter. 398 + 399 + `Func` may be made variadic by specifying `LuaVariadic` as its $(B last) parameter. 400 + 401 + If `Func` is annotated with `@LuaBasicFunction`, then this function actually acts the same as 402 + `luaCWrapperBasic`. The reason for this is so that we don't have to have a bunch of conditional 403 + logic in the other parts of the code to select between the two wrappers, but instead we can just put the conditional logic 404 + here to seamlessly support this usecase throughout the code. 405 + 406 + Params: 407 + Func = The D function to wrap. 408 + Type = User code shouldn't ever need to set this, please leave it as the default. 409 + 410 + Example: 411 + `luaState.register!(std.path.buildPath!(string[]))("buildPath")` 412 + ++/ 413 extern(C) 414 int luaCWrapperSmart(alias Func, LuaFuncWrapperType Type = LuaFuncWrapperType.isAliasFunc)(lua_State* state) nothrow 415 { 416 import std.traits : getUDAs; 417 418 static if(__traits(compiles, getUDAs!(Func, LuaBasicFunction))) 419 enum IsBasicFunction = getUDAs!(Func, LuaBasicFunction).length > 0; 420 else 421 enum IsBasicFunction = false; 422 423 static if(IsBasicFunction) 424 return luaCWrapperBasic!Func(state); 425 else 426 { 427 return luaCWrapperBasic!( 428 luaCWrapperSmartImpl!(Func, Type) 429 )(state); 430 } 431 } 432 433 private int luaCWrapperSmartImpl( 434 alias Func, 435 LuaFuncWrapperType Type = LuaFuncWrapperType.isAliasFunc 436 )( 437 LuaState* lua 438 ) 439 { 440 import std.format : format; 441 import std.traits : Parameters, ReturnType, isInstanceOf, ParameterDefaults; 442 import std.meta : AliasSeq, staticIndexOf, Reverse; 443 import std.typecons : isTuple; 444 445 alias Params = Parameters!Func; 446 alias Defaults = AliasSeq!(ParameterDefaults!Func); 447 448 static if(Params.length) 449 { 450 const ParamsLength = 451 Params.length 452 - (is(Params[0] == LuaState*) ? 1 : 0) 453 - (is(Params[$-1] == LuaVariadic) ? 1 : 0); 454 455 const ParamsMinLength = staticIndexOf!(void, Defaults) == -1 ? 0 : (ParamsLength - staticIndexOf!(void, Reverse!Defaults)); 456 } 457 else 458 { 459 const ParamsLength = 0; 460 const ParamsMinLength = 0; 461 } 462 463 enum HasVariadic = Params.length > 0 && is(Params[$-1] == LuaVariadic); 464 enum HasDefault = ParamsMinLength != ParamsLength; 465 466 Params params; 467 468 const argsGiven = lua.top(); 469 if(!HasVariadic && (argsGiven < ParamsMinLength || argsGiven > ParamsLength)) 470 static if (HasDefault) 471 throw new LuaArgumentException("Expected %s ~ %s args, but was given %s.".format(ParamsMinLength, ParamsLength, argsGiven)); 472 else 473 throw new LuaArgumentException("Expected exactly %s args, but was given %s.".format(ParamsLength, argsGiven)); 474 else if(HasVariadic && argsGiven < ParamsMinLength) 475 throw new LuaArgumentException("Expected at least %s args, but was given %s.".format(ParamsMinLength, argsGiven)); 476 477 static if(is(Params[0] == LuaState*)) 478 { 479 params[0] = lua; 480 static foreach(i; 0..ParamsLength) 481 if (i < argsGiven) 482 params[i+1] = lua.get!(Params[i+1])(i+1); 483 } 484 else 485 { 486 static foreach(i; 0..ParamsLength) 487 if (i < argsGiven) 488 params[i] = lua.get!(Params[i])(i+1); 489 } 490 491 static if(HasVariadic) 492 foreach(i; 0..argsGiven-ParamsLength) 493 params[$-1] ~= lua.get!LuaValue(cast(int)(i+ParamsLength+1)); 494 495 static foreach(i; ParamsMinLength..ParamsLength) 496 if (i >= argsGiven) 497 { 498 static if(is(Params[0] == LuaState*)) 499 params[i+1] = Defaults[i+1]; 500 else 501 params[i] = Defaults[i]; 502 } 503 504 alias RetT = ReturnType!Func; 505 506 static if(Type == LuaFuncWrapperType.isDelegate) 507 { 508 alias FuncWithContext = RetT function(Params, void*); 509 510 auto context = lua_touserdata(lua.handle, lua_upvalueindex(1)); 511 auto func = lua_touserdata(lua.handle, lua_upvalueindex(2)); 512 auto dFunc = cast(FuncWithContext)func; 513 514 static if(is(RetT == void)) 515 { 516 dFunc(params, context); 517 return 0; 518 } 519 else static if(isInstanceOf!(LuaMultiReturn, RetT)) 520 { 521 auto multiRet = dFunc(params, context); 522 static foreach(i; 0..multiRet.ValueTuple.length) 523 lua.push(multiRet[i]); 524 return multiRet.ValueTuple.length; 525 } 526 else static if(is(RetT == LuaVariadic)) 527 { 528 auto multiRet = dFunc(params, context); 529 foreach(value; multiRet) 530 lua.push(value); 531 return cast(int)multiRet.length; 532 } 533 else static if(isTuple!RetT) 534 { 535 auto multiRet = dFunc(params); 536 static foreach(i; 0..multiRet.length) 537 lua.push(multiRet[i]); 538 return multiRet.length; 539 } 540 else 541 { 542 lua.push(dFunc(params, context)); 543 return 1; 544 } 545 } 546 else static if(Type == LuaFuncWrapperType.isFunction) 547 { 548 auto func = cast(Func)lua_touserdata(lua.handle, lua_upvalueindex(1)); 549 static if(is(RetT == void)) 550 { 551 func(params); 552 return 0; 553 } 554 else static if(isInstanceOf!(LuaMultiReturn, RetT)) 555 { 556 auto multiRet = func(params); 557 static foreach(i; 0..multiRet.ValueTuple.length) 558 lua.push(multiRet[i]); 559 return multiRet.ValueTuple.length; 560 } 561 else static if(is(RetT == LuaVariadic)) 562 { 563 auto multiRet = func(params); 564 foreach(value; multiRet) 565 lua.push(value); 566 return cast(int)multiRet.length; 567 } 568 else static if(isTuple!RetT) 569 { 570 auto multiRet = func(params); 571 static foreach(i; 0..multiRet.length) 572 lua.push(multiRet[i]); 573 return multiRet.length; 574 } 575 else 576 { 577 lua.push(func(params)); 578 return 1; 579 } 580 } 581 else 582 { 583 static if(is(RetT == void)) 584 { 585 Func(params); 586 return 0; 587 } 588 else static if(isInstanceOf!(LuaMultiReturn, RetT)) 589 { 590 auto multiRet = Func(params); 591 static foreach(i; 0..multiRet.ValueTuple.length) 592 lua.push(multiRet[i]); 593 return multiRet.ValueTuple.length; 594 } 595 else static if(is(RetT == LuaVariadic)) 596 { 597 auto multiRet = Func(params); 598 foreach(value; multiRet) 599 lua.push(value); 600 return cast(int)multiRet.length; 601 } 602 else static if(isTuple!RetT) 603 { 604 auto multiRet = Func(params); 605 static foreach(i; 0..multiRet.length) 606 lua.push(multiRet[i]); 607 return multiRet.length; 608 } 609 else 610 { 611 lua.push(Func(params)); 612 return 1; 613 } 614 } 615 } 616 617 /++ 618 + A function that wraps around other functions in order to provide runtime overloading support. 619 + 620 + Notes: 621 + Parameter binding logic is exactly the same as `luaCWrapperSmart`. 622 + 623 + From the 0th `Overload` to the last, this function will exhaustively call each function until one successfully 624 + has its arguments bound. 625 + 626 + If no overloads could succesfully be matched, then an exception will be thrown. 627 + 628 + All overloads must provide the same return type. I hope to make this more flexible in the future. 629 + 630 + To be more specific, a function fails to bind its arguments if it throws `LuaTypeException` or `LuaArgumentException`. 631 + So please be aware of this when writing overloads. 632 + ++/ 633 @LuaBasicFunction 634 int luaOverloads(Overloads...)(LuaState* state) 635 { 636 static foreach(Overload; Overloads) 637 {{ 638 bool compilerThinksThisIsUnreachable = true; 639 if(compilerThinksThisIsUnreachable) 640 { 641 // TODO: This needs a much better mechanism, this is super dodgy. 642 try return luaCWrapperSmartImpl!Overload(state); 643 catch(LuaTypeException) {} 644 catch(LuaArgumentException) {} 645 } 646 }} 647 648 throw new Exception("No overload matched the given arguments."); 649 } 650 651 unittest 652 { 653 auto l = LuaState(null); 654 l.doString("return function(...) return ... end"); 655 auto f = l.get!LuaFuncWeak(-1); 656 auto result = f.pcall!1("Henlo!"); 657 assert(result[0].textValue == "Henlo!"); 658 } 659 660 unittest 661 { 662 auto l = LuaState(null); 663 l.push(&luaCWrapperSmart!( 664 (string a, int[] b, bool[string] c) 665 { 666 assert(a == "Hello"); 667 assert(b == [4, 2, 0]); 668 assert(c["true"]); 669 return true; 670 } 671 )); 672 auto f = l.get!LuaFunc(-1); 673 auto result = f.pcall!1("Hello", [4, 2, 0], ["true": true]); 674 assert(result[0].booleanValue); 675 676 auto f2 = f.bind!(bool, string, int[], bool[string])(); 677 assert(f2("Hello", [4, 2, 0], ["true": true])); 678 679 alias F = bool delegate(string, int[], bool[string]); 680 F f3 = &f2.asDelegate; 681 assert(f3("Hello", [4, 2, 0], ["true": true])); 682 } 683 684 unittest 685 { 686 static string func(string a, int b) 687 { 688 assert(a == "bc"); 689 assert(b == 123); 690 return "doe ray me"; 691 } 692 693 auto l = LuaState(null); 694 l.push(&func); 695 auto f = LuaFuncWeak(&l, -1); 696 auto fb = f.bind!(string, string, int); 697 assert(fb("bc", 123) == "doe ray me"); 698 } 699 700 unittest 701 { 702 version(LDC) 703 { 704 pragma(msg, "WARNING: This unittest is currently broken under LDC"); 705 } 706 else 707 { 708 int closedValue; 709 void del(string a) 710 { 711 assert(a == "bc"); 712 closedValue = 123; 713 } 714 715 auto l = LuaState(null); 716 l.push(&del); 717 auto f = LuaFuncWeak(&l, -1); 718 f.pcall!0("bc"); 719 assert(closedValue == 123); 720 } 721 } 722 723 unittest 724 { 725 import std.exception : assertThrown, assertNotThrown; 726 727 auto l = LuaState(null); 728 l.register("test", &luaCWrapperSmart!((string s){ })); 729 730 auto f = l.globalTable.get!LuaFunc("test"); 731 f.pcall!0().assertThrown; 732 f.pcall!0("abc").assertNotThrown; 733 f.pcall!0("abc", "123").assertThrown; 734 } 735 736 unittest 737 { 738 static struct S 739 { 740 int i; 741 742 void test(int value) 743 { 744 assert(value == i); 745 } 746 747 void bind(LuaState* l) 748 { 749 l.register!("test", function (int value) => S(200).test(value))("api"); 750 } 751 } 752 753 auto lua = new LuaState(null); 754 S s; 755 s.bind(lua); 756 lua.doString("api.test(200)"); 757 } 758 759 unittest 760 { 761 auto lua = new LuaState(null); 762 lua.register("test", &luaCWrapperSmart!((int a, string b, LuaVariadic c){ 763 assert(a == 1); 764 assert(b == "2"); 765 assert(c.length == 3); 766 })); 767 lua.doString("test(1, '2', 3, true, {})"); 768 769 lua.register("test", &luaCWrapperSmart!((LuaState* l, int a, string b, LuaVariadic c){ 770 assert(a == 1); 771 assert(b == "2"); 772 assert(c.length == 3); 773 })); 774 lua.doString("test(1, '2', 3, true, {})"); 775 } 776 777 unittest 778 { 779 import std.typecons : tuple; 780 781 auto lua = new LuaState(null); 782 lua.register!( 783 luaOverloads!( 784 (int a) { assert(a == 1); return a; }, 785 (string a) { assert(a == "2"); return a; }, 786 (int a, string b) { assert(a == 1); assert(b == "2"); return tuple(a, b); } 787 ) 788 )("overloaded"); 789 lua.doString(` 790 assert(overloaded(1) == 1) 791 assert(overloaded("2") == "2") 792 a,b = overloaded(1, "2") 793 assert(a == 1) 794 assert(b == "2") 795 `); 796 } 797 798 unittest 799 { 800 import std.typecons : tuple; 801 802 static auto multiReturn() 803 { 804 return tuple(20, "40", true); 805 } 806 807 auto lua = new LuaState(null); 808 lua.register!multiReturn("multiReturn"); 809 lua.doString(` 810 local i, s, b = multiReturn() 811 assert(i == 20) 812 assert(s == "40") 813 assert(b) 814 `); 815 } 816 817 unittest 818 { 819 static LuaVariadic multiReturn() 820 { 821 return LuaVariadic([LuaValue(20), LuaValue("40"), LuaValue(true)]); 822 } 823 824 auto lua = new LuaState(null); 825 lua.register!multiReturn("multiReturn"); 826 lua.doString(` 827 local i, s, b = multiReturn() 828 assert(i == 20) 829 assert(s == "40") 830 assert(b) 831 `); 832 } 833 834 unittest 835 { 836 import std.typecons : Tuple; 837 static Tuple!(int, string, bool) multiReturn() 838 { 839 return typeof(return)(20, "40", true); 840 } 841 842 auto lua = new LuaState(null); 843 lua.register!multiReturn("multiReturn"); 844 lua.doString(` 845 local i, s, b = multiReturn() 846 assert(i == 20) 847 assert(s == "40") 848 assert(b) 849 `); 850 851 alias Employee = Tuple!(int, "ID", string, "Name", bool, "FullTime"); 852 853 lua.doString(` 854 function employee1() 855 return 15305, "Domain", true 856 end 857 858 function employee2() 859 return { ID = 15605, Name = "Range", FullTime = false } 860 end 861 `); 862 863 auto f = lua.globalTable.get!LuaFunc("employee1").bind!(Employee); 864 auto g = lua.globalTable.get!LuaFunc("employee2").bind!(Employee); 865 auto employee1 = f(); 866 auto employee2 = g(); 867 assert(employee1.ID == 15305); 868 assert(employee1.Name == "Domain"); 869 assert(employee1.FullTime); 870 assert(employee2.ID == 15605); 871 assert(employee2.Name == "Range"); 872 assert(!employee2.FullTime); 873 } 874 875 unittest 876 { 877 import std.exception : assertThrown; 878 import std.typecons : Tuple; 879 880 alias Employee = Tuple!(int, "ID", string, "Name", bool, "FullTime"); 881 882 auto lua = new LuaState(null); 883 lua.doString(` 884 function partialEmployee() 885 return 15305, "Domain" 886 end 887 `); 888 889 auto partialEmployee = lua.globalTable.get!LuaFunc("partialEmployee").bind!(Employee); 890 assertThrown!LuaTypeException(partialEmployee()); 891 } 892 893 unittest 894 { 895 import std.typecons : tuple; 896 897 auto lua = new LuaState(null); 898 lua.register!( 899 "normal", (){ return 1; }, 900 "overloaded", luaOverloads!( 901 (int a) { assert(a == 1); return a; }, 902 (string a) { assert(a == "2"); return a; }, 903 (int a, string b) { assert(a == 1); assert(b == "2"); return tuple(a, b); } 904 ) 905 )("lib"); 906 907 lua.doString(` 908 assert(lib.normal() == 1) 909 910 assert(lib.overloaded(1) == 1) 911 assert(lib.overloaded("2") == "2") 912 a,b = lib.overloaded(1, "2") 913 assert(a == 1) 914 assert(b == "2") 915 `); 916 } 917 918 unittest 919 { 920 auto lua = LuaState(null); 921 922 @LuaBasicFunction 923 static int basic(LuaState* lua) 924 { 925 return lua.top(); // lua.top will be the amount of parameters we have. So return all params. 926 } 927 lua.register!basic("basic"); 928 929 lua.doString(` 930 assert(basic(1) == 1) 931 932 a,b = basic(1, "2") 933 assert(a == 1 and b == "2") 934 `); 935 } 936 937 unittest 938 { 939 import std.algorithm : canFind; 940 import std.exception : collectExceptionMsg; 941 942 auto lua = LuaState(null); 943 944 static void err() 945 { 946 throw new Exception("err"); 947 } 948 lua.register!err("err"); 949 950 const msg = lua.doString(`err()`).collectExceptionMsg; 951 assert(msg.canFind("stack traceback:")); 952 } 953 954 unittest 955 { 956 auto lua = new LuaState(null); 957 lua.register!( 958 "defaultParams", (int a, int b = 1, int c = 2) { return a+b+c; }, 959 "defaultParams2", (LuaState*lua, int a, int b = 1, int c = 2) { return a+b+c; } 960 )("lib"); 961 962 lua.doString(` 963 assert(lib.defaultParams(1) == 4) 964 assert(lib.defaultParams(1, 2) == 5) 965 assert(lib.defaultParams(1, 3, 5) == 9) 966 assert(lib.defaultParams2(1) == 4) 967 assert(lib.defaultParams2(1, 2) == 5) 968 assert(lib.defaultParams2(1, 3, 5) == 9) 969 `); 970 } 971 972 unittest 973 { 974 auto lua = new LuaState(null); 975 lua.register!( 976 "p0", () { }, 977 "p1", (int a) { }, 978 "p1o1", (int a, int b = 0) { }, 979 "o1", (int a = 0) { }, 980 )("lib"); 981 982 lua.doString(` 983 assert(pcall(lib.p0)) 984 assert(not pcall(lib.p0, 1)) 985 986 assert(not pcall(lib.p1)) 987 assert(pcall(lib.p1, 1)) 988 assert(not pcall(lib.p1, 1, 2)) 989 990 assert(not pcall(lib.p1o1)) 991 assert(pcall(lib.p1o1, 1)) 992 assert(pcall(lib.p1o1, 1, 2)) 993 assert(not pcall(lib.p1o1, 1, 2, 3)) 994 995 assert(pcall(lib.o1)) 996 assert(pcall(lib.o1, 1)) 997 assert(not pcall(lib.o1, 1, 2)) 998 `); 999 } 1000 1001 unittest 1002 { 1003 class A{} 1004 auto lua = new LuaState(null); 1005 lua.register!( 1006 "array", (int[] a) { assert(a is null); return 1; }, 1007 "class", (A a) { assert(a is null); return 1; } 1008 )("test"); 1009 1010 lua.doString(` 1011 assert(test.array(nil) == 1) 1012 assert(test.class(nil) == 1) 1013 `); 1014 }