1 module lumars.state; 2 3 import bindbc.lua, taggedalgebraic, lumars; 4 import taggedalgebraic : visit; 5 import std.typecons : Nullable; 6 7 /// Used to represent LUA's `nil`. 8 struct LuaNil {} 9 10 /// See `LuaValue` 11 union LuaValueUnion 12 { 13 /// LUA `nil` 14 LuaNil nil; 15 16 /// A lua number 17 lua_Number number; 18 19 /// A weak reference to some text. This text is managed by LUA and not D's GC so is unsafe to escape 20 const(char)[] textWeak; 21 22 /// GC-managed text 23 string text; 24 25 /// A bool 26 bool boolean; 27 28 /// A weak reference to a table currently on the LUA stack. 29 LuaTableWeak tableWeak; 30 31 /// A strong reference to a table which is in the LUA registry. 32 LuaTable table; 33 34 /// A weak reference to a function currently on the LUA stack. 35 LuaFuncWeak funcWeak; 36 37 /// A strong reference to a function which is in the LUA registry. 38 LuaFunc func; 39 40 void* userData; 41 } 42 43 /// An enumeration of various status codes LUA may return. 44 enum LuaStatus 45 { 46 ok = 0, 47 yield = LUA_YIELD, 48 errRun = LUA_ERRRUN, 49 errSyntax = LUA_ERRSYNTAX, 50 errMem = LUA_ERRMEM, 51 errErr = LUA_ERRERR, 52 } 53 54 /// A `TaggedUnion` of `LuaValueUnion` which is used to bridge the gap between D and Lua values. 55 alias LuaValue = TaggedUnion!LuaValueUnion; 56 alias LuaNumber = lua_Number; 57 alias LuaCFunc = lua_CFunction; 58 59 /++ 60 + A light wrapper around `lua_State` with some higher level functions for quality of life purposes. 61 + 62 + This struct cannot be copied, so put it on the heap or store it as a global. 63 + ++/ 64 struct LuaState 65 { 66 import std.string : toStringz; 67 import std.stdio : writefln, writeln, writef; 68 69 @disable this(this){} 70 71 private 72 { 73 lua_State* _handle; 74 LuaTablePseudo _G; 75 } 76 package bool _isWrapper; 77 78 /// Creates a wrapper around the given `lua_state`, or creates a new state if the given value is null. 79 @trusted 80 this(lua_State* wrapAround) 81 { 82 if(wrapAround) 83 { 84 this._handle = wrapAround; 85 this._isWrapper = true; 86 } 87 else 88 { 89 loadLuaIfNeeded(); 90 91 this._handle = luaL_newstate(); 92 luaL_openlibs(this.handle); 93 } 94 95 this._G = LuaTablePseudo(&this, LUA_GLOBALSINDEX); 96 } 97 98 /// For non-wrappers, destroy the lua state. 99 @trusted @nogc 100 ~this() nothrow 101 { 102 if(this._handle && !this._isWrapper) 103 lua_close(this._handle); 104 } 105 106 @nogc 107 LuaTablePseudo globalTable() nothrow 108 { 109 return this._G; 110 } 111 112 @nogc 113 lua_CFunction atPanic(lua_CFunction func) nothrow 114 { 115 return lua_atpanic(this.handle, func); 116 } 117 118 @nogc 119 void call(int nargs, int nresults) nothrow 120 { 121 lua_call(this.handle, nargs, nresults); 122 } 123 124 @nogc 125 bool checkStack(int amount) nothrow 126 { 127 return lua_checkstack(this.handle, amount) != 0; 128 } 129 130 @nogc 131 void concat(int nargs) nothrow 132 { 133 lua_concat(this.handle, nargs); 134 } 135 136 @nogc 137 bool equal(int index1, int index2) nothrow 138 { 139 return lua_equal(this.handle, index1, index2) != 0; 140 } 141 142 @nogc 143 void error() nothrow 144 { 145 lua_error(this.handle); 146 } 147 148 void error(const char[] msg) nothrow 149 { 150 luaL_error(this.handle, "%s", msg.toStringz); 151 } 152 153 LuaTableWeak pushMetatable(int ofIndex) 154 { 155 lua_getmetatable(this.handle, ofIndex); 156 return LuaTableWeak(&this, -1); 157 } 158 159 LuaTable getMetatable(int ofIndex) 160 { 161 lua_getmetatable(this.handle, ofIndex); 162 return LuaTable.makeRef(&this); 163 } 164 165 @nogc 166 bool lessThan(int index1, int index2) nothrow 167 { 168 return lua_lessthan(this.handle, index1, index2) != 0; 169 } 170 171 @nogc 172 bool rawEqual(int index1, int index2) nothrow 173 { 174 return lua_rawequal(this.handle, index1, index2) != 0; 175 } 176 177 @nogc 178 void pushTable(int tableIndex) nothrow 179 { 180 return lua_gettable(this.handle, tableIndex); 181 } 182 183 @nogc 184 void insert(int index) nothrow 185 { 186 return lua_insert(this.handle, index); 187 } 188 189 @nogc 190 size_t len(int index) nothrow 191 { 192 return lua_objlen(this.handle, index); 193 } 194 195 @nogc 196 LuaStatus pcall(int nargs, int nresults, int errFuncIndex) nothrow 197 { 198 return cast(LuaStatus)lua_pcall(this.handle, nargs, nresults, errFuncIndex); 199 } 200 201 @nogc 202 void copy(int index) nothrow 203 { 204 lua_pushvalue(this.handle, index); 205 } 206 207 @nogc 208 void rawGet(int tableIndex) nothrow 209 { 210 lua_rawget(this.handle, tableIndex); 211 } 212 213 @nogc 214 void rawGet(int tableIndex, int indexIntoTable) nothrow 215 { 216 lua_rawgeti(this.handle, tableIndex, indexIntoTable); 217 } 218 219 @nogc 220 void rawSet(int tableIndex) nothrow 221 { 222 lua_rawset(this.handle, tableIndex); 223 } 224 225 @nogc 226 void rawSet(int tableIndex, int indexIntoTable) nothrow 227 { 228 lua_rawseti(this.handle, tableIndex, indexIntoTable); 229 } 230 231 void getGlobal(const char[] name) 232 { 233 lua_getglobal(this.handle, name.toStringz); 234 } 235 236 void setGlobal(const char[] name) 237 { 238 lua_setglobal(this.handle, name.toStringz); 239 } 240 241 void register(const char[] name, LuaCFunc func) nothrow 242 { 243 lua_register(this.handle, name.toStringz, func); 244 } 245 246 /++ 247 + Registers the given D function into Lua, under a specific name within the global table. 248 + 249 + `Func` can be any normal D function. By default it is wrapped using `luaCWrapperSmart` 250 + to allow a (mostly) seamless way of interfacing D and Lua together. Please see `luaCWrapperSmart`'s 251 + documentation if you'd like to learn more. 252 + 253 + Params: 254 + Func = The function to wrap. 255 + name = The name to register the function under. 256 + ++/ 257 void register(alias Func)(const char[] name) 258 { 259 this.register(name, &luaCWrapperSmart!Func); 260 } 261 262 /++ 263 + Similar to the other register functions, except this one will register the functions 264 + into a single table, before registering the resulting table into the global table. 265 + 266 + In other words: If you want to make a "library" table then this is the function for you. 267 + 268 + `Args` must have an even number of elements, where each two elements are a pair. 269 + 270 + For each pair in `Args`, the first element is the name to register the function under, and 271 + the last element is the function itself to register. 272 + 273 + For example, if you did: `register!("a", (){}, "b", (){})("library")`, then the result is 274 + a global table called "library" with the functions "a" and "b" (e.g. `library.a()`). 275 + ++/ 276 void register(Args...)(const char[] libname) 277 if(Args.length % 2 == 0) 278 { 279 import std.traits : getUDAs; 280 luaL_Reg[(Args.length / 2) + 1] reg; 281 282 static foreach(i; 0..Args.length/2) 283 reg[i] = luaL_Reg(Args[i*2].ptr, &luaCWrapperSmart!(Args[i*2+1])); 284 285 luaL_register(this.handle, libname.toStringz, reg.ptr); 286 } 287 288 @nogc 289 void remove(int index) nothrow 290 { 291 lua_remove(this.handle, index); 292 } 293 294 @nogc 295 void replace(int index) nothrow 296 { 297 lua_replace(this.handle, index); 298 } 299 300 @nogc 301 void setMetatable(int ofIndex) nothrow 302 { 303 lua_setmetatable(this.handle, ofIndex); 304 } 305 306 void checkArg(bool condition, int argNum, const(char)[] extraMsg = null) nothrow 307 { 308 luaL_argcheck(this.handle, condition ? 1 : 0, argNum, extraMsg ? extraMsg.toStringz : null); 309 } 310 311 bool callMetamethod(int index, const char[] method) nothrow 312 { 313 return luaL_callmeta(this.handle, index, method.toStringz) != 0; 314 } 315 316 @nogc 317 void checkAny(int arg) nothrow 318 { 319 luaL_checkany(this.handle, arg); 320 } 321 322 @nogc 323 ptrdiff_t checkInt(int arg) nothrow 324 { 325 return luaL_checkinteger(this.handle, arg); 326 } 327 328 @nogc 329 const(char)[] checkStringWeak(int arg) nothrow 330 { 331 size_t len; 332 const ptr = luaL_checklstring(this.handle, arg, &len); 333 return ptr[0..len]; 334 } 335 336 string checkString(int arg) nothrow 337 { 338 return checkStringWeak(arg).idup; 339 } 340 341 @nogc 342 LuaNumber checkNumber(int arg) nothrow 343 { 344 return luaL_checknumber(this.handle, arg); 345 } 346 347 @nogc 348 void checkType(LuaValue.Kind type, int arg) nothrow 349 { 350 int t; 351 352 final switch(type) with(LuaValue.Kind) 353 { 354 case nil: t = LUA_TNIL; break; 355 case number: t = LUA_TNUMBER; break; 356 case textWeak: 357 case text: t = LUA_TSTRING; break; 358 case boolean: t = LUA_TBOOLEAN; break; 359 case tableWeak: 360 case table: t = LUA_TTABLE; break; 361 case funcWeak: 362 case func: t = LUA_TFUNCTION; break; 363 case userData: t = LUA_TLIGHTUSERDATA; break; 364 } 365 366 luaL_checktype(this.handle, arg, t); 367 } 368 369 @nogc 370 bool isType(T)(int index) nothrow 371 { 372 return this.type(index) == LuaState.luaValueKindFor!T; 373 } 374 375 @nogc 376 static LuaValue.Kind luaValueKindFor(T)() pure nothrow 377 { 378 import std.typecons : Nullable; 379 import std.traits : isNumeric, isDynamicArray, isAssociativeArray, 380 isPointer, KeyType, ValueType, 381 isInstanceOf, TemplateArgsOf; 382 383 static if(is(T == string)) 384 return LuaValue.Kind.text; 385 else static if(is(T == const(char)[])) 386 return LuaValue.Kind.text; 387 else static if(is(T : const(bool))) 388 return LuaValue.Kind.boolean; 389 else static if(isNumeric!T) 390 return LuaValue.Kind.number; 391 else static if(is(T == typeof(null)) || is(T == LuaNil)) 392 return LuaValue.Kind.nil; 393 else static if(is(T == LuaTableWeak)) 394 return LuaValue.Kind.table; 395 else static if(is(T == LuaTable)) 396 return LuaValue.Kind.table; 397 else static if(isDynamicArray!T) 398 return LuaValue.Kind.table; 399 else static if(isAssociativeArray!T) 400 return LuaValue.Kind.table; 401 else static if(is(T == LuaCFunc)) 402 return LuaValue.Kind.func; 403 else static if(is(T == LuaFuncWeak)) 404 return LuaValue.Kind.func; 405 else static if(is(T == LuaFunc)) 406 return LuaValue.Kind.func; 407 else static if(isInstanceOf!(Nullable, T)) 408 return luaValueKindFor!(TemplateArgsOf!T[0])(); 409 else static if(isPointer!T || is(T == class)) 410 return LuaValue.Kind.userData; 411 else static if(is(T == struct)) 412 return LuaValue.Kind.table; 413 else static assert(false, "Don't know how to convert any LUA values into type: "~T.stringof); 414 } 415 416 void doFile(const char[] file) 417 { 418 const status = luaL_dofile(this.handle, file.toStringz); 419 if(status != LuaStatus.ok) 420 { 421 const error = this.get!string(-1); 422 this.pop(1); 423 throw new LuaException(error); 424 } 425 } 426 427 void doFile(const char[] file, scope ref LuaTable table) 428 { 429 const loadStatus = luaL_loadfile(this.handle, file.toStringz); 430 if(loadStatus != LuaStatus.ok) 431 { 432 const error = this.get!string(-1); 433 this.pop(1); 434 throw new LuaException(error); 435 } 436 437 table.push(); 438 const fenvResult = lua_setfenv(this.handle, -2); 439 if(fenvResult == 0) 440 throw new LuaException("Failed to set function environment"); 441 442 const callStatus = lua_pcall(this.handle, 0, 0, 0); 443 if(callStatus != LuaStatus.ok) 444 { 445 const error = this.get!string(-1); 446 this.pop(1); 447 throw new LuaException(error); 448 } 449 } 450 451 void doString(const char[] str) 452 { 453 const status = luaL_dostring(this.handle, str.toStringz); 454 if(status != LuaStatus.ok) 455 { 456 const error = this.get!string(-1); 457 this.pop(1); 458 throw new LuaException(error); 459 } 460 } 461 462 void doString(const char[] str, scope ref LuaTable table) 463 { 464 const loadStatus = luaL_loadstring(this.handle, str.toStringz); 465 if(loadStatus != LuaStatus.ok) 466 { 467 const error = this.get!string(-1); 468 this.pop(1); 469 throw new LuaException(error); 470 } 471 472 table.push(); 473 const fenvResult = lua_setfenv(this.handle, -2); 474 if(fenvResult == 0) 475 throw new LuaException("Failed to set function environment"); 476 477 const callStatus = lua_pcall(this.handle, 0, 0, 0); 478 if(callStatus != LuaStatus.ok) 479 { 480 const error = this.get!string(-1); 481 this.pop(1); 482 throw new LuaException(error); 483 } 484 } 485 486 void loadFile(const char[] file) 487 { 488 const status = luaL_loadfile(this.handle, file.toStringz); 489 if(status != LuaStatus.ok) 490 { 491 const error = this.get!string(-1); 492 this.pop(1); 493 throw new LuaException(error); 494 } 495 } 496 497 void loadString(const char[] str) 498 { 499 const status = luaL_loadstring(this.handle, str.toStringz); 500 if(status != LuaStatus.ok) 501 { 502 const error = this.get!string(-1); 503 this.pop(1); 504 throw new LuaException(error); 505 } 506 } 507 508 @nogc 509 ptrdiff_t optInt(int arg, ptrdiff_t default_) nothrow 510 { 511 return luaL_optinteger(this.handle, arg, default_); 512 } 513 514 @nogc 515 LuaNumber optNumber(int arg, LuaNumber default_) nothrow 516 { 517 return luaL_optnumber(this.handle, arg, default_); 518 } 519 520 void printStack() 521 { 522 writeln("[LUA STACK]"); 523 foreach(i; 0..this.top) 524 { 525 const type = lua_type(this.handle, i+1); 526 writef("\t[%s] \t", i+1); 527 528 switch(type) 529 { 530 case LUA_TBOOLEAN: writefln("%s\t%s", "BOOL", this.get!bool(i+1)); break; 531 case LUA_TFUNCTION: writefln("%s\t%s", "FUNC", lua_tocfunction(this.handle, i+1)); break; 532 case LUA_TLIGHTUSERDATA: writefln("%s\t%s", "LIGHT", lua_touserdata(this.handle, i+1)); break; 533 case LUA_TNIL: writefln("%s", "NIL"); break; 534 case LUA_TNUMBER: writefln("%s\t%s", "NUM", this.get!lua_Number(i+1)); break; 535 case LUA_TSTRING: writefln("%s\t%s", "STR", this.get!(const(char)[])(i+1)); break; 536 case LUA_TTABLE: writefln("%s", "TABL"); break; 537 case LUA_TTHREAD: writefln("%s\t%s", "THRD", lua_tothread(this.handle, i+1)); break; 538 case LUA_TUSERDATA: writefln("%s\t%s", "USER", lua_touserdata(this.handle, i+1)); break; 539 default: writefln("%s\t%s", "UNKN", type); break; 540 } 541 } 542 } 543 544 void push(T)(T value) 545 { 546 import std.conv : to; 547 import std.traits : isNumeric, isDynamicArray, isAssociativeArray, isDelegate, isPointer, isFunction, 548 PointerTarget, KeyType, ValueType, FieldNameTuple, TemplateOf, TemplateArgsOf; 549 550 static if(is(T == typeof(null)) || is(T == LuaNil)) 551 lua_pushnil(this.handle); 552 else static if(is(T : const(char)[])) 553 lua_pushlstring(this.handle, value.ptr, value.length); 554 else static if(isNumeric!T) 555 lua_pushnumber(this.handle, value.to!lua_Number); 556 else static if(is(T : const(bool))) 557 lua_pushboolean(this.handle, value ? 1 : 0); 558 else static if(isDynamicArray!T) 559 { 560 alias ValueT = typeof(value[0]); 561 562 lua_createtable(this.handle, 0, value.length.to!int); 563 foreach(i, v; value) 564 { 565 this.push(v); 566 lua_rawseti(this.handle, -2, cast(int)i+1); 567 } 568 } 569 else static if(isAssociativeArray!T) 570 { 571 alias KeyT = KeyType!T; 572 alias ValueT = ValueType!T; 573 574 lua_createtable(this.handle, 0, value.length.to!int); 575 foreach(k, v; value) 576 { 577 this.push(k); 578 this.push(v); 579 lua_rawset(this.handle, -3); 580 } 581 } 582 else static if(is(T == LuaTable) || is(T == LuaFunc)) 583 value.push(); 584 else static if(is(T == LuaTableWeak) || is(T == LuaFuncWeak)) 585 this.copy(value.push()); 586 else static if(is(T : lua_CFunction)) 587 lua_pushcfunction(this.handle, value); 588 else static if(isDelegate!T) 589 { 590 lua_pushlightuserdata(this.handle, value.ptr); 591 lua_pushlightuserdata(this.handle, value.funcptr); 592 lua_pushcclosure(this.handle, &luaCWrapperSmart!(T, LuaFuncWrapperType.isDelegate), 2); 593 } 594 else static if(isPointer!T && isFunction!(PointerTarget!T)) 595 { 596 lua_pushlightuserdata(this.handle, value); 597 lua_pushcclosure(this.handle, &luaCWrapperSmart!(T, LuaFuncWrapperType.isFunction), 1); 598 } 599 else static if(__traits(isSame, TemplateOf!T, Nullable)) 600 { 601 if (value.isNull) 602 lua_pushnil(this.handle); 603 else 604 push!(TemplateArgsOf!T)(value.get()); 605 } 606 else static if(isPointer!T) 607 lua_pushlightuserdata(this.handle, value); 608 else static if(is(T == class)) 609 lua_pushlightuserdata(this.handle, cast(void*)value); 610 else static if(is(T == struct)) 611 { 612 lua_newtable(this.handle); 613 614 static foreach(member; FieldNameTuple!T) 615 { 616 this.push(member); 617 this.push(mixin("value."~member)); 618 lua_settable(this.handle, -3); 619 } 620 } 621 else static assert(false, "Don't know how to push type: "~T.stringof); 622 } 623 624 void push(LuaValue value) 625 { 626 value.visit!( 627 (_){ this.push(_); } 628 ); 629 } 630 631 @nogc 632 int top() nothrow 633 { 634 return lua_gettop(this.handle); 635 } 636 637 @nogc 638 void pop(int amount) nothrow 639 { 640 lua_pop(this.handle, amount); 641 } 642 643 T get(T)(int index) 644 { 645 import std.conv : to; 646 import std.format : format; 647 import std.traits : isNumeric, isDynamicArray, isAssociativeArray, isPointer, KeyType, ValueType, TemplateOf, TemplateArgsOf; 648 import std.typecons : isTuple; 649 650 static if(is(T == string)) 651 { 652 if(this.isType!LuaNil(index)) 653 return null; 654 655 this.enforceType(LuaValue.Kind.text, index); 656 size_t len; 657 auto ptr = lua_tolstring(this.handle, index, &len); 658 return ptr[0..len].idup; 659 } 660 else static if(is(T == const(char)[])) 661 { 662 if(this.isType!LuaNil(index)) 663 return null; 664 665 this.enforceType(LuaValue.Kind.text, index); 666 size_t len; 667 auto ptr = lua_tolstring(this.handle, index, &len); 668 return ptr[0..len]; 669 } 670 else static if(is(T : const(bool))) 671 { 672 this.enforceType(LuaValue.Kind.boolean, index); 673 return lua_toboolean(this.handle, index) != 0; 674 } 675 else static if(isNumeric!T) 676 { 677 this.enforceType(LuaValue.Kind.number, index); 678 return lua_tonumber(this.handle, index).to!T; 679 } 680 else static if(is(T == typeof(null)) || is(T == LuaNil)) 681 { 682 this.enforceType(LuaValue.Kind.nil, index); 683 return LuaNil(); 684 } 685 else static if(is(T == LuaTableWeak)) 686 { 687 this.enforceType(LuaValue.Kind.table, index); 688 return T(&this, index); 689 } 690 else static if(is(T == LuaTable)) 691 { 692 this.enforceType(LuaValue.Kind.table, index); 693 this.copy(index); 694 return T.makeRef(&this); 695 } 696 else static if(isDynamicArray!T) 697 { 698 if(this.isType!LuaNil(index)) 699 return null; 700 701 this.enforceType(LuaValue.Kind.table, index); 702 T ret; 703 ret.length = lua_objlen(this.handle, index); 704 705 this.push(null); 706 const tableIndex = index < 0 ? index - 1 : index; 707 while(this.next(tableIndex)) 708 { 709 ret[this.get!size_t(-2) - 1] = this.get!(typeof(ret[0]))(-1); 710 this.pop(1); 711 } 712 713 return ret; 714 } 715 else static if(isAssociativeArray!T) 716 { 717 if(this.isType!LuaNil(index)) 718 return null; 719 720 this.enforceType(LuaValue.Kind.table, index); 721 T ret; 722 723 this.push(null); 724 const tableIndex = index < 0 ? index - 1 : index; 725 while(this.next(tableIndex)) 726 { 727 ret[this.get!(KeyType!T)(-2)] = this.get!(ValueType!T)(-1); 728 this.pop(1); 729 } 730 731 return ret; 732 } 733 else static if(is(T == LuaCFunc)) 734 { 735 this.enforceType(LuaValue.Kind.func, index); 736 return lua_tocfunction(this.handle, index); 737 } 738 else static if(is(T == LuaFuncWeak)) 739 { 740 this.enforceType(LuaValue.Kind.func, index); 741 return LuaFuncWeak(&this, index); 742 } 743 else static if(is(T == LuaFunc)) 744 { 745 this.enforceType(LuaValue.Kind.func, index); 746 this.copy(index); 747 return T.makeRef(&this); 748 } 749 else static if(isPointer!T || is(T == class)) 750 { 751 if(this.isType!LuaNil(index)) 752 return null; 753 754 this.enforceType(LuaValue.Kind.userData, index); 755 return cast(T)lua_touserdata(this.handle, index); 756 } 757 else static if(is(T == LuaValue)) 758 { 759 switch(this.type(index)) 760 { 761 case LuaValue.Kind.text: return LuaValue(this.get!string(index)); 762 case LuaValue.Kind.number: return LuaValue(this.get!lua_Number(index)); 763 case LuaValue.Kind.boolean: return LuaValue(this.get!bool(index)); 764 case LuaValue.Kind.nil: return LuaValue(this.get!LuaNil(index)); 765 case LuaValue.Kind.table: return LuaValue(this.get!LuaTable(index)); 766 case LuaValue.Kind.func: return LuaValue(this.get!LuaFunc(index)); 767 case LuaValue.Kind.userData: return LuaValue(this.get!(void*)(index)); 768 default: throw new LuaException("Don't know how to convert type into a LuaValue: "~this.type(index).to!string); 769 } 770 } 771 else static if(__traits(isSame, TemplateOf!(T), Nullable)) 772 { 773 if(this.isType!LuaNil(index)) 774 return T.init; 775 return cast(T)get!(TemplateArgsOf!(T)[0])(index); 776 } 777 else static if (isTuple!T) 778 { 779 T ret; 780 alias params = T.Types; 781 int idx = 0; 782 783 static foreach (i, p; params) 784 { 785 idx = cast(int)(i - params.length); 786 if (this.isType!(params[i])(idx)) 787 { 788 ret[i] = this.get!(p)(idx); 789 } 790 else 791 { 792 const firstArgIndex = cast(int)(-params.length); 793 static if(i == 0) 794 { 795 if(this.type(firstArgIndex) == LuaValue.Kind.table) 796 return getAsStruct!(T, T.fieldNames)(firstArgIndex); 797 798 throw new LuaTypeException( 799 format!( 800 "When parsing tuple %s, expected value %s to be %s, but it was %s. " 801 ~" Attempted to parse 0th value as a struct instead, but 0th value is not a table." 802 )( 803 T.stringof, 804 idx, 805 LuaState.luaValueKindFor!(params[i]), 806 this.type(firstArgIndex), 807 ) 808 ); 809 } 810 else 811 { 812 throw new LuaTypeException( 813 format!"When parsing tuple %s, expected value %s to be %s, but it was %s."( 814 T.stringof, 815 idx, 816 LuaState.luaValueKindFor!(params[i]), 817 this.type(firstArgIndex) 818 ) 819 ); 820 } 821 } 822 } 823 824 return ret; 825 } 826 else static if(is(T == struct)) 827 { 828 return getAsStruct!(T, __traits(allMembers, T))(index); 829 } 830 else static assert(false, "Don't know how to convert any LUA values into type: "~T.stringof); 831 } 832 833 T getAsStruct(T, Names ...)(int index) 834 { 835 this.enforceType(LuaValue.Kind.table, index); 836 T ret; 837 838 this.push(null); 839 const tableIndex = index < 0 ? index - 1 : index; 840 While: while(this.next(tableIndex)) 841 { 842 const field = this.get!(const(char)[])(-2); 843 844 static foreach(member; Names) 845 { 846 if(field == member) 847 { 848 mixin("ret."~member~"= this.get!(typeof(ret."~member~"))(-1);"); 849 this.pop(1); 850 continue While; 851 } 852 } 853 854 this.pop(1); 855 } 856 return ret; 857 } 858 859 @nogc 860 bool next(int index) nothrow 861 { 862 this.assertIndex(index); 863 return lua_next(this.handle, index) != 0; 864 } 865 866 void enforceType(LuaValue.Kind expected, int index) 867 { 868 import std.exception : enforce; 869 import std.format : format; 870 const type = this.type(index); 871 enforce!LuaTypeException(type == expected, "Expected value at stack index %s to be of type %s but it is %s".format( 872 index, expected, type 873 )); 874 } 875 876 @nogc 877 LuaValue.Kind type(int index) nothrow 878 { 879 assert(this.top > 0, "Stack is empty."); 880 this.assertIndex(index); 881 const type = lua_type(this.handle, index); 882 883 switch(type) 884 { 885 case LUA_TBOOLEAN: return LuaValue.Kind.boolean; 886 case LUA_TNIL: return LuaValue.Kind.nil; 887 case LUA_TNUMBER: return LuaValue.Kind.number; 888 case LUA_TSTRING: return LuaValue.Kind.text; 889 case LUA_TTABLE: return LuaValue.Kind.table; 890 case LUA_TFUNCTION: return LuaValue.Kind.func; 891 case LUA_TLIGHTUSERDATA: return LuaValue.Kind.userData; 892 893 default: 894 return LuaValue.Kind.nil; 895 } 896 } 897 898 /++ 899 + Attempts to call `debug.traceback`. A string is expected to be on top of the stack, 900 + otherwise the function will abort the attempt. 901 + 902 + Otherwise, the string on top of the stack is consumed and a new string will be pushed containing 903 + the traceback (if possible). 904 + 905 + Args: 906 + level = Same as the `level` parameter for `debug.traceback`. Essentially, how many functions to not include in the traceback 907 + ++/ 908 void traceback(int level = 2) 909 { 910 if(!this.isType!string(-1)) 911 return; 912 913 const str = this.get!string(-1); 914 this.pop(1); 915 916 bool found; 917 auto debugTable = this.globalTable.tryGet!LuaTable("debug", found); 918 if(!found) 919 { 920 this.push(str~"\n"~"[Could not produce traceback: `debug` does not exist in _G table]"); 921 return; 922 } 923 924 auto tracebackFunc = debugTable.tryGet!LuaFunc("traceback", found); 925 if(!found) 926 { 927 this.push(str~"\n"~"[Could not produce traceback: `traceback` does not exist in `debug` table]"); 928 return; 929 } 930 931 this.push(tracebackFunc); 932 this.push(str); 933 this.push(level); 934 this.call(2, 1); 935 } 936 937 @property @safe @nogc 938 inout(lua_State*) handle() nothrow pure inout 939 { 940 return this._handle; 941 } 942 943 @nogc 944 private void assertIndex(int index) nothrow 945 { 946 if(index > 0) 947 assert(this.top >= index, "Index out of bounds"); 948 else 949 assert(this.top + index >= 0, "Index out of bounds"); 950 } 951 } 952 953 private void loadLuaIfNeeded() 954 { 955 version(BindLua_Static){} 956 else 957 { 958 const ret = loadLua(); 959 if(ret != luaSupport) { 960 if(ret == LuaSupport.noLibrary) 961 throw new LuaException("Lua library not found."); 962 else if(ret == LuaSupport.badLibrary) 963 throw new LuaException("Lua library is corrupt or for a different platform."); 964 else 965 throw new LuaException("Lua library is the wrong version, or some unknown error occured."); 966 } 967 } 968 } 969 970 import std.exception : basicExceptionCtors; 971 class LuaException : Exception 972 { 973 mixin basicExceptionCtors; 974 } 975 class LuaTypeException : LuaException 976 { 977 mixin basicExceptionCtors; 978 } 979 class LuaArgumentException : LuaException 980 { 981 mixin basicExceptionCtors; 982 } 983 class LuaStackException : LuaException 984 { 985 mixin basicExceptionCtors; 986 } 987 988 unittest 989 { 990 auto l = LuaState(null); 991 l.push(null); 992 assert(l.type(-1) == LuaValue.Kind.nil); 993 assert(l.get!LuaValue(-1).kind == LuaValue.Kind.nil); 994 assert(l.get!string(-1) == null); 995 assert(l.get!(const(char)[])(-1) == null); 996 l.pop(1); 997 998 l.push(LuaNil()); 999 assert(l.type(-1) == LuaValue.Kind.nil); 1000 assert(l.get!LuaValue(-1).kind == LuaValue.Kind.nil); 1001 assert(l.get!string(-1) == null); 1002 assert(l.get!(const(char)[])(-1) == null); 1003 l.pop(1); 1004 1005 l.push(false); 1006 assert(l.get!LuaValue(-1).kind == LuaValue.Kind.boolean); 1007 assert(!l.get!bool(-1)); 1008 l.pop(1); 1009 1010 l.push(20); 1011 assert(l.get!LuaValue(-1).kind == LuaValue.Kind.number); 1012 assert(l.get!int(-1) == 20); 1013 l.pop(1); 1014 1015 l.push("abc"); 1016 assert(l.get!LuaValue(-1).kind == LuaValue.Kind.text); 1017 assert(l.get!string(-1) == "abc"); 1018 assert(l.get!(const(char)[])(-1) == "abc"); 1019 l.pop(1); 1020 1021 l.push(["abc", "one"]); 1022 assert(l.get!(string[])(-1) == ["abc", "one"]); 1023 l.pop(1); 1024 1025 l.push([LuaValue(200), LuaValue("abc")]); 1026 assert(l.get!(LuaValue[])(-1) == [LuaValue(200), LuaValue("abc")]); 1027 l.pop(1); 1028 1029 l.push(LuaNil()); 1030 assert(l.get!(Nullable!bool)(-1) == Nullable!(bool).init); 1031 l.pop(1); 1032 1033 Nullable!bool nb; 1034 l.push(nb); 1035 assert(l.get!(Nullable!bool)(-1) == Nullable!(bool).init); 1036 l.pop(1); 1037 1038 l.push(123); 1039 assert(l.get!(Nullable!int)(-1) == 123); 1040 l.pop(1); 1041 1042 Nullable!int ni = 234; 1043 l.push(ni); 1044 assert(l.get!(Nullable!int)(-1) == 234); 1045 l.pop(1); 1046 } 1047 1048 unittest 1049 { 1050 auto l = LuaState(null); 1051 l.register!(() => 123)("abc"); 1052 l.doString("assert(abc() == 123)"); 1053 } 1054 1055 unittest 1056 { 1057 auto l = LuaState(null); 1058 l.register!( 1059 "funcA", () => "a", 1060 "funcB", () => "b" 1061 )("lib"); 1062 l.doString("assert(lib.funcA() == 'a') assert(lib.funcB() == 'b')"); 1063 } 1064 1065 unittest 1066 { 1067 auto l = LuaState(null); 1068 l.doString("abba = 'chicken tikka'"); 1069 assert(l.globalTable.get!string("abba") == "chicken tikka"); 1070 l.globalTable["baab"] = "tikka chicken"; 1071 assert(l.globalTable.get!string("baab") == "tikka chicken"); 1072 } 1073 1074 unittest 1075 { 1076 static struct B 1077 { 1078 string a; 1079 } 1080 1081 static struct C 1082 { 1083 string a; 1084 } 1085 1086 static struct A 1087 { 1088 string a; 1089 B[] b; 1090 C[string] c; 1091 } 1092 1093 auto a = A( 1094 "bc", 1095 [B("c")], 1096 ["c": C("123")] 1097 ); 1098 1099 auto l = LuaState(null); 1100 l.push(a); 1101 1102 auto luaa = l.get!A(-1); 1103 assert(luaa.a == "bc"); 1104 assert(luaa.b.length == 1); 1105 assert(luaa.b == [B("c")]); 1106 assert(luaa.c.length == 1); 1107 assert(luaa.c["c"] == C("123")); 1108 } 1109 1110 unittest 1111 { 1112 auto state = LuaState(null); 1113 auto print = state._G.get!LuaFunc("print"); 1114 auto _G1 = LuaTable.makeNew(&state); 1115 auto _G2 = LuaTable.makeNew(&state); 1116 _G1["abc"] = 123; 1117 _G1["print"] = print; 1118 _G2["abc"] = 321; 1119 _G2["print"] = print; 1120 1121 const code = "print(abc)"; 1122 1123 state.doString(code, _G1); 1124 state.doString(code, _G2); 1125 }