1 /** 2 Internal module for pushing and getting _functions and delegates. 3 4 LuaD allows for pushing of all D function or delegate types with return type and parameter types compatible with LuaD (see $(DPMODULE stack)). 5 6 For a fixed number of multiple return values, return a $(STDREF typecons,Tuple) or a static array. For a variable number of return values, return $(MREF LuaVariableReturn). 7 8 As a special case for $(D const(char)[]) parameter types in _functions pushed to Lua, no copy of the string is made when called; take care not to escape such references, they are effectively $(D scope) parameters. 9 When a copy is desired, use $(D char[]) or $(D string), or $(D dup) or $(D idup) the string manually. 10 11 If a function with the $(D lua_CFunction) signature is encountered, it is pushed directly with no inserted conversions or overhead. 12 13 Typesafe varargs is supported when pushing _functions to Lua, but as of DMD 2.054, compiler bugs prevent getting delegates with varargs from Lua (use $(DPREF lfunction,LuaFunction) instead). 14 */ 15 module luad.conversions.functions; 16 17 import core.memory; 18 import std.range; 19 import std..string : toStringz; 20 import std.traits; 21 import std.typetuple; 22 23 import luad.c.all; 24 25 import luad.stack; 26 27 private void argsError(lua_State* L, int nargs, ptrdiff_t expected) 28 { 29 lua_Debug debugInfo; 30 lua_getstack(L, 0, &debugInfo); 31 lua_getinfo(L, "n", &debugInfo); 32 luaL_error(L, "call to %s '%s': got %d arguments, expected %d", 33 debugInfo.namewhat, debugInfo.name, nargs, expected); 34 } 35 36 template StripHeadQual(T : const(T*)) 37 { 38 alias const(T)* StripHeadQual; 39 } 40 41 template StripHeadQual(T : const(T[])) 42 { 43 alias const(T)[] StripHeadQual; 44 } 45 46 template StripHeadQual(T : immutable(T*)) 47 { 48 alias immutable(T)* StripHeadQual; 49 } 50 51 template StripHeadQual(T : immutable(T[])) 52 { 53 alias immutable(T)[] StripHeadQual; 54 } 55 56 template StripHeadQual(T : T[]) 57 { 58 alias T[] StripHeadQual; 59 } 60 61 template StripHeadQual(T : T*) 62 { 63 alias T* StripHeadQual; 64 } 65 66 template StripHeadQual(T : T[N], size_t N) 67 { 68 alias T[N] StripHeadQual; 69 } 70 71 template StripHeadQual(T) 72 { 73 alias T StripHeadQual; 74 } 75 76 template FillableParameterTypeTuple(T) 77 { 78 alias staticMap!(StripHeadQual, ParameterTypeTuple!T) FillableParameterTypeTuple; 79 } 80 81 template BindableReturnType(T) 82 { 83 alias StripHeadQual!(ReturnType!T) BindableReturnType; 84 } 85 86 //Call with or without return value, propagating Exceptions as Lua errors. 87 //This should rather be throwing a userdata with __tostring and a reference to 88 //the thrown exception, as it is now, everything but the error type and message is lost. 89 int callFunction(T)(lua_State* L, T func, ParameterTypeTuple!T args) 90 if(!is(BindableReturnType!T == const) && 91 !is(BindableReturnType!T == immutable)) 92 { 93 alias BindableReturnType!T RetType; 94 enum hasReturnValue = !is(RetType == void); 95 96 static if(hasReturnValue) 97 RetType ret; 98 99 try 100 { 101 static if(hasReturnValue) 102 ret = func(args); 103 else 104 func(args); 105 } 106 catch(Exception e) 107 { 108 luaL_error(L, "%s", toStringz(e.toString())); 109 } 110 111 static if(hasReturnValue) 112 return pushReturnValues(L, ret); 113 else 114 return 0; 115 } 116 117 // Ditto, but wrap the try-catch in a nested function because the return value's 118 // declaration and initialization cannot be separated. 119 int callFunction(T)(lua_State* L, T func, ParameterTypeTuple!T args) 120 if(is(BindableReturnType!T == const) || 121 is(BindableReturnType!T == immutable)) 122 { 123 auto ref call() 124 { 125 try 126 return func(args); 127 catch(Exception e) 128 luaL_error(L, "%s", e.toString().toStringz()); 129 } 130 131 return pushReturnValues(L, call()); 132 } 133 134 private: 135 136 // TODO: right now, virtual functions on specialized classes can be called with base classes as 'self', not safe! 137 extern(C) int methodWrapper(T, Class, bool virtual)(lua_State* L) 138 { 139 alias ParameterTypeTuple!T Args; 140 141 static assert ((variadicFunctionStyle!T != Variadic.d && variadicFunctionStyle!T != Variadic.c), 142 "Non-typesafe variadic functions are not supported."); 143 144 //Check arguments 145 int top = lua_gettop(L); 146 147 static if (variadicFunctionStyle!T == Variadic.typesafe) 148 enum requiredArgs = Args.length; 149 else 150 enum requiredArgs = Args.length + 1; 151 152 if(top < requiredArgs) 153 argsError(L, top, requiredArgs); 154 155 Class self = *cast(Class*)luaL_checkudata(L, 1, toStringz(Class.mangleof)); 156 157 static if(virtual) 158 { 159 alias ReturnType!T function(Class, Args) VirtualWrapper; 160 VirtualWrapper func = cast(VirtualWrapper)lua_touserdata(L, lua_upvalueindex(1)); 161 } 162 else 163 { 164 T func; 165 func.ptr = cast(void*)self; 166 func.funcptr = cast(typeof(func.funcptr))lua_touserdata(L, lua_upvalueindex(1)); 167 } 168 169 //Assemble arguments 170 static if(virtual) 171 { 172 ParameterTypeTuple!VirtualWrapper allArgs; 173 allArgs[0] = self; 174 alias allArgs[1..$] args; 175 } 176 else 177 { 178 Args allArgs; 179 alias allArgs args; 180 } 181 182 foreach(i, Arg; Args) 183 args[i] = getArgument!(T, i)(L, i + 2); 184 185 return callFunction!(typeof(func))(L, func, allArgs); 186 } 187 188 extern(C) int functionWrapper(T)(lua_State* L) 189 { 190 alias FillableParameterTypeTuple!T Args; 191 192 static assert ((variadicFunctionStyle!T != Variadic.d && variadicFunctionStyle!T != Variadic.c), 193 "Non-typesafe variadic functions are not supported."); 194 195 //Check arguments 196 int top = lua_gettop(L); 197 198 static if (variadicFunctionStyle!T == Variadic.typesafe) 199 enum requiredArgs = Args.length - 1; 200 else 201 enum requiredArgs = Args.length; 202 203 if(top < requiredArgs) 204 argsError(L, top, requiredArgs); 205 206 //Get function 207 static if(isFunctionPointer!T) 208 T func = cast(T)lua_touserdata(L, lua_upvalueindex(1)); 209 else 210 T func = *cast(T*)lua_touserdata(L, lua_upvalueindex(1)); 211 212 //Assemble arguments 213 Args args; 214 foreach(i, Arg; Args) 215 args[i] = getArgument!(T, i)(L, i + 1); 216 217 return callFunction!T(L, func, args); 218 } 219 220 extern(C) int functionCleaner(lua_State* L) 221 { 222 GC.removeRoot(lua_touserdata(L, 1)); 223 return 0; 224 } 225 226 public: 227 228 void pushFunction(T)(lua_State* L, T func) if (isSomeFunction!T) 229 { 230 static if(isFunctionPointer!T) 231 lua_pushlightuserdata(L, func); 232 else 233 { 234 T* udata = cast(T*)lua_newuserdata(L, T.sizeof); 235 *udata = func; 236 237 GC.addRoot(udata); 238 239 if(luaL_newmetatable(L, "__dcall") == 1) 240 { 241 lua_pushcfunction(L, &functionCleaner); 242 lua_setfield(L, -2, "__gc"); 243 } 244 245 lua_setmetatable(L, -2); 246 } 247 248 lua_pushcclosure(L, &functionWrapper!T, 1); 249 } 250 251 // TODO: optimize for non-virtual functions 252 void pushMethod(Class, string member)(lua_State* L) if (isSomeFunction!(__traits(getMember, Class, member))) 253 { 254 alias typeof(mixin("&Class.init." ~ member)) T; 255 256 // Delay vtable lookup until the right time 257 static ReturnType!T virtualWrapper(Class self, ParameterTypeTuple!T args) 258 { 259 return mixin("self." ~ member)(args); 260 } 261 262 lua_pushlightuserdata(L, &virtualWrapper); 263 lua_pushcclosure(L, &methodWrapper!(T, Class, true), 1); 264 } 265 266 /** 267 * Currently this function allocates a reference in the registry that is never deleted, 268 * one for each call... see code comments 269 */ 270 T getFunction(T)(lua_State* L, int idx) if (is(T == delegate)) 271 { 272 auto func = new class 273 { 274 int lref; 275 this() 276 { 277 lua_pushvalue(L, idx); 278 lref = luaL_ref(L, LUA_REGISTRYINDEX); 279 } 280 281 //Alright... how to fix this? 282 //The problem is that this object tends to be finalized after L is freed (by LuaState's destructor or otherwise). 283 //If you have a good solution to the problem of dangling references to a lua_State, 284 //please contact me :) 285 286 /+~this() 287 { 288 luaL_unref(L, LUA_REGISTRYINDEX, lref); 289 }+/ 290 291 void push() 292 { 293 lua_rawgeti(L, LUA_REGISTRYINDEX, lref); 294 } 295 }; 296 297 alias ReturnType!T RetType; 298 alias ParameterTypeTuple!T Args; 299 300 return delegate RetType(Args args) 301 { 302 func.push(); 303 foreach(arg; args) 304 pushValue(L, arg); 305 306 return callWithRet!RetType(L, args.length); 307 }; 308 } 309 310 /** 311 * Type for efficiently returning a variable number of return values 312 * from a function. 313 * 314 * Use $(D variableReturn) to instantiate it. 315 * Params: 316 * Range = any input range 317 */ 318 struct LuaVariableReturn(Range) if(isInputRange!Range) 319 { 320 alias WrappedType = Range; /// The type of the wrapped input range. 321 Range returnValues; /// The wrapped input range. 322 } 323 324 /** 325 * Create a LuaVariableReturn object for efficiently returning 326 * a variable number of values from a function. 327 * Params: 328 * returnValues = any input range 329 * Example: 330 ----------------------------- 331 LuaVariableReturn!(uint[]) makeList(uint n) 332 { 333 uint[] list; 334 335 foreach(i; 1 .. n + 1) 336 { 337 list ~= i; 338 } 339 340 return variableReturn(list); 341 } 342 343 lua["makeList"] = &makeList; 344 345 lua.doString(` 346 local one, two, three, four = makeList(4) 347 assert(one == 1) 348 assert(two == 2) 349 assert(three == 3) 350 assert(four == 4) 351 `); 352 ----------------------------- 353 */ 354 LuaVariableReturn!Range variableReturn(Range)(Range returnValues) 355 if(isInputRange!Range) 356 { 357 return typeof(return)(returnValues); 358 } 359 360 version(unittest) 361 { 362 import luad.testing; 363 import std.typecons; 364 private lua_State* L; 365 } 366 367 unittest 368 { 369 L = luaL_newstate(); 370 luaL_openlibs(L); 371 372 //functions 373 static const(char)[] func(const(char)[] s) 374 { 375 return "Hello, " ~ s; 376 } 377 378 pushValue(L, &func); 379 assert(lua_isfunction(L, -1)); 380 lua_setglobal(L, "sayHello"); 381 382 unittest_lua(L, ` 383 local ret = sayHello("foo") 384 local expect = "Hello, foo" 385 assert(ret == expect, 386 ("sayHello return type - got '%s', expected '%s'"):format(ret, expect) 387 ) 388 `); 389 390 static uint countSpaces(const(char)[] s) 391 { 392 uint n = 0; 393 foreach(dchar c; s) 394 if(c == ' ') 395 ++n; 396 397 return n; 398 } 399 400 pushValue(L, &countSpaces); 401 assert(lua_isfunction(L, -1)); 402 lua_setglobal(L, "countSpaces"); 403 404 unittest_lua(L, ` 405 assert(countSpaces("Hello there, world!") == 2) 406 `); 407 408 //delegates 409 double curry = 3.14 * 2; 410 double closure(double x) 411 { 412 return curry * x; 413 } 414 415 pushValue(L, &closure); 416 assert(lua_isfunction(L, -1)); 417 lua_setglobal(L, "circle"); 418 419 unittest_lua(L, ` 420 assert(circle(2) == 3.14 * 4, "closure return type mismatch!") 421 `); 422 423 // Const parameters 424 static bool isEmpty(const(char[]) str) { return str.length == 0; } 425 static bool isEmpty2(in char[] str) { return str.length == 0; } 426 427 pushValue(L, &isEmpty); 428 lua_setglobal(L, "isEmpty"); 429 430 pushValue(L, &isEmpty2); 431 lua_setglobal(L, "isEmpty2"); 432 433 unittest_lua(L, ` 434 assert(isEmpty("")) 435 assert(isEmpty2("")) 436 assert(not isEmpty("a")) 437 assert(not isEmpty2("a")) 438 `); 439 440 // Immutable parameters 441 static immutable(char[]) returnArg(immutable(char[]) str) { return str; } 442 443 pushValue(L, &returnArg); 444 lua_setglobal(L, "returnArg"); 445 446 unittest_lua(L, `assert(returnArg("foo") == "foo")`); 447 } 448 449 version(unittest) import luad.base; 450 451 // multiple return values 452 unittest 453 { 454 // tuple returns 455 auto nameInfo = ["foo"]; 456 auto ageInfo = [42]; 457 458 alias Tuple!(string, "name", uint, "age") GetInfoResult; 459 GetInfoResult getInfo(int idx) 460 { 461 GetInfoResult result; 462 result.name = nameInfo[idx]; 463 result.age = ageInfo[idx]; 464 return result; 465 } 466 467 pushValue(L, &getInfo); 468 lua_setglobal(L, "getInfo"); 469 470 unittest_lua(L, ` 471 local name, age = getInfo(0) 472 assert(name == "foo") 473 assert(age == 42) 474 `); 475 476 // static array returns 477 static string[2] getName() 478 { 479 string[2] ret; 480 ret[0] = "Foo"; 481 ret[1] = "Bar"; 482 return ret; 483 } 484 485 pushValue(L, &getName); 486 lua_setglobal(L, "getName"); 487 488 unittest_lua(L, ` 489 local first, last = getName() 490 assert(first == "Foo") 491 assert(last == "Bar") 492 `); 493 494 // variable length returns 495 LuaVariableReturn!(uint[]) makeList(uint n) 496 { 497 uint[] list; 498 499 foreach(i; 1 .. n + 1) 500 { 501 list ~= i; 502 } 503 504 return variableReturn(list); 505 } 506 507 auto makeList2(uint n) 508 { 509 return variableReturn(iota(1, n + 1)); 510 } 511 512 pushValue(L, &makeList); 513 lua_setglobal(L, "makeList"); 514 pushValue(L, &makeList2); 515 lua_setglobal(L, "makeList2"); 516 517 unittest_lua(L, ` 518 for i, f in pairs{makeList, makeList2} do 519 local one, two, three, four = f(4) 520 assert(one == 1) 521 assert(two == 2) 522 assert(three == 3) 523 assert(four == 4) 524 end 525 `); 526 } 527 528 // Variadic function arguments 529 unittest 530 { 531 static string concat(const(char)[][] pieces...) 532 { 533 string result; 534 foreach(piece; pieces) 535 result ~= piece; 536 return result; 537 } 538 539 pushValue(L, &concat); 540 lua_setglobal(L, "concat"); 541 542 unittest_lua(L, ` 543 local whole = concat("he", "llo", ", ", "world!") 544 assert(whole == "hello, world!") 545 `); 546 547 //Test with zero parameters. 548 unittest_lua(L, ` 549 local blank = concat() 550 assert (string.len(blank) == 0) 551 `); 552 553 static const(char)[] concat2(char separator, const(char)[][] pieces...) 554 { 555 if(pieces.length == 0) 556 return ""; 557 558 string result; 559 foreach(piece; pieces[0..$-1]) 560 result ~= piece ~ separator; 561 562 return result ~ pieces[$-1]; 563 } 564 565 pushValue(L, &concat2); 566 lua_setglobal(L, "concat2"); 567 568 unittest_lua(L, ` 569 local whole = concat2(",", "one", "two", "three", "four") 570 assert(whole == "one,two,three,four") 571 `); 572 573 //C- and D-style variadic versions of concat for 574 //future use if/when these are supported. 575 576 //C varargs require at least one fixed argument. 577 import core.vararg; 578 // C-style varargs broken on Linux for 2.066.1? 579 version(none) extern(C) static string concat_cvar (size_t count, ...) 580 { 581 string result; 582 583 va_list args; 584 585 va_start(args, count); 586 587 foreach(immutable i; 0 .. count) 588 { 589 auto arg = va_arg!LuaObject(args); 590 result ~= arg.toString(); 591 } 592 593 va_end(args); 594 595 return result; 596 } 597 598 //D-style variadics have an _arguments array that specifies 599 //the type of each passed argument. 600 static string concat_dvar (...) { 601 string result; 602 603 foreach (argtype; _arguments) { 604 assert (argtype == typeid(LuaObject)); 605 auto arg = va_arg!(LuaObject)(_argptr); 606 607 result ~= arg.toString(); 608 } 609 610 return result; 611 } 612 } 613 614 // get delegates from Lua 615 unittest 616 { 617 lua_getglobal(L, "string"); 618 lua_getfield(L, -1, "match"); 619 auto match = popValue!(string delegate(string, string))(L); 620 lua_pop(L, 1); 621 622 auto result = match("foobar@example.com", "([^@]+)@example.com"); 623 assert(result == "foobar"); 624 625 // multiple return values 626 luaL_dostring(L, `function multRet(a) return "foo", a end`); 627 lua_getglobal(L, "multRet"); 628 auto multRet = popValue!(Tuple!(string, int) delegate(int))(L); 629 630 auto results = multRet(42); 631 assert(results[0] == "foo"); 632 assert(results[1] == 42); 633 } 634 635 // Nested call stack testing 636 unittest 637 { 638 alias string delegate(string) MyFun; 639 640 MyFun[string] funcs; 641 642 pushValue(L, (string name, MyFun fun) { 643 funcs[name] = fun; 644 }); 645 lua_setglobal(L, "addFun"); 646 647 pushValue(L, (string name, string arg) { 648 auto top = lua_gettop(L); 649 auto result = funcs[name](arg); 650 assert(lua_gettop(L) == top); 651 return result; 652 }); 653 lua_setglobal(L, "callFun"); 654 655 auto top = lua_gettop(L); 656 657 luaL_dostring(L, q{ 658 addFun("echo", function(s) return s end) 659 local result = callFun("echo", "test") 660 assert(result == "test") 661 }); 662 663 assert(lua_gettop(L) == top); 664 } 665 666 unittest 667 { 668 assert(lua_gettop(L) == 0); 669 lua_close(L); 670 }