1 module luad.state; 2 3 import std.array, std.range; 4 5 import std.string : toStringz; 6 import std.typecons : isTuple; 7 8 import luad.c.all; 9 import luad.stack; 10 import luad.conversions.classes; 11 12 import luad.base, luad.table, luad.lfunction, luad.dynamic, luad.error; 13 14 /// Specify error handling scheme for $(MREF LuaState.doString) and $(MREF LuaState.doFile). 15 enum LuaErrorHandler 16 { 17 None, /// No extra error handler. 18 Traceback /// Append a stack traceback to the error message. 19 } 20 21 /** 22 * Represents a Lua state instance. 23 */ 24 class LuaState 25 { 26 private: 27 lua_State* L; // underlying state 28 LuaTable _G, _R; // global and registry tables 29 LuaFunction traceback; // debug.traceback, set in openLibs() 30 bool owner = false; // whether or not to close the underlying state in the finalizer 31 32 public: 33 /** 34 * Create a new, empty Lua state. The standard library is not loaded. 35 * 36 * If an uncaught error for any operation on this state 37 * causes a Lua panic for the underlying state, 38 * an exception of type $(DPREF error, LuaErrorException) is thrown. 39 * 40 * See_Also: $(MREF LuaState.openLibs) 41 */ 42 this() 43 { 44 lua_State* L = luaL_newstate(); 45 owner = true; 46 47 extern(C) static int panic(lua_State* L) 48 { 49 size_t len; 50 const(char)* cMessage = lua_tolstring(L, -1, &len); 51 string message = cMessage[0 .. len].idup; 52 53 lua_pop(L, 1); 54 55 throw new LuaErrorException(message); 56 } 57 58 lua_atpanic(L, &panic); 59 60 this(L); 61 } 62 63 /** 64 * Create a D wrapper for an existing Lua state. 65 * 66 * The new $(D LuaState) object does not assume ownership of the state. 67 * Params: 68 * L = state to wrap 69 * Note: 70 * The panic function is not changed - a Lua panic will not throw a D exception! 71 * See_Also: 72 $(MREF LuaState.setPanicHandler) 73 */ 74 this(lua_State* L) 75 { 76 this.L = L; 77 _G = LuaTable(L, LUA_GLOBALSINDEX); 78 _R = LuaTable(L, LUA_REGISTRYINDEX); 79 80 lua_pushlightuserdata(L, cast(void*)this); 81 lua_setfield(L, LUA_REGISTRYINDEX, "__dstate"); 82 } 83 84 ~this() 85 { 86 if(owner) 87 { 88 _R.release(); 89 _G.release(); 90 traceback.release(); 91 lua_close(L); 92 } 93 else // Unregister state 94 { 95 lua_pushnil(L); 96 lua_setfield(L, LUA_REGISTRYINDEX, "__dstate"); 97 } 98 } 99 100 /// The underlying $(D lua_State) pointer for interfacing with C. 101 @property lua_State* state() nothrow pure @safe 102 { 103 return L; 104 } 105 106 /** 107 * Get the $(D LuaState) instance for a Lua state. 108 * Params: 109 * L = Lua state 110 * Returns: 111 * $(D LuaState) for the given $(D lua_State*), or $(D null) if a $(D LuaState) is not currently attached to the state 112 */ 113 static LuaState fromPointer(lua_State* L) @trusted 114 { 115 lua_getfield(L, LUA_REGISTRYINDEX, "__dstate"); 116 auto lua = cast(LuaState)lua_touserdata(L, -1); 117 lua_pop(L, 1); 118 return lua; 119 } 120 121 /// Open the standard library. 122 void openLibs() @trusted 123 { 124 luaL_openlibs(L); 125 traceback = _G.get!LuaFunction("debug", "traceback"); 126 } 127 128 /// The global table for this instance. 129 @property LuaTable globals() @trusted 130 { 131 return _G; 132 } 133 134 /// The _registry table for this instance. 135 @property LuaTable registry() @trusted 136 { 137 return _R; 138 } 139 140 /** 141 * Set a new panic handler. 142 * Params: 143 * onPanic = new panic handler 144 * Examples: 145 * ---------------------- 146 auto L = luaL_newstate(); // found in luad.c.all 147 auto lua = new LuaState(L); 148 149 static void panic(LuaState lua, in char[] error) 150 { 151 throw new LuaErrorException(error.idup); 152 } 153 154 lua.setPanicHandler(&panic); 155 * ---------------------- 156 */ 157 void setPanicHandler(void function(LuaState, in char[]) onPanic) @trusted 158 { 159 extern(C) static int panic(lua_State* L) 160 { 161 size_t len; 162 const(char)* message = lua_tolstring(L, -1, &len); 163 auto error = message[0 .. len]; 164 165 lua_getfield(L, LUA_REGISTRYINDEX, "__dpanic"); 166 auto callback = cast(void function(LuaState, in char[]))lua_touserdata(L, -1); 167 assert(callback); 168 169 scope(exit) lua_pop(L, 2); 170 171 callback(LuaState.fromPointer(L), error); 172 return 0; 173 } 174 175 lua_pushlightuserdata(L, onPanic); 176 lua_setfield(L, LUA_REGISTRYINDEX, "__dpanic"); 177 178 lua_atpanic(L, &panic); 179 } 180 181 /* 182 * push debug.traceback error handler to the stack 183 */ 184 private void pushErrorHandler() 185 { 186 if(traceback.isNil) 187 throw new Exception("LuaErrorHandler.Traceback requires openLibs()"); 188 traceback.push(); 189 } 190 191 /* 192 * a variant of luaL_do(string|file) with advanced error handling 193 */ 194 private void doChunk(alias loader)(in char[] s, LuaErrorHandler handler) 195 { 196 if(handler == LuaErrorHandler.Traceback) 197 pushErrorHandler(); 198 199 if(loader(L, toStringz(s)) || lua_pcall(L, 0, LUA_MULTRET, handler == LuaErrorHandler.Traceback? -2 : 0)) 200 lua_error(L); 201 202 if(handler == LuaErrorHandler.Traceback) 203 lua_remove(L, 1); 204 } 205 206 /** 207 * Compile a string of Lua _code. 208 * Params: 209 * code = _code to compile 210 * Returns: 211 * Loaded _code as a function. 212 */ 213 LuaFunction loadString(in char[] code) @trusted 214 { 215 if(luaL_loadstring(L, toStringz(code)) != 0) 216 lua_error(L); 217 218 return popValue!LuaFunction(L); 219 } 220 221 /** 222 * Compile a file of Lua code. 223 * Params: 224 * path = _path to file 225 * Returns: 226 * Loaded code as a function. 227 */ 228 LuaFunction loadFile(in char[] path) @trusted 229 { 230 if(luaL_loadfile(L, toStringz(path)) != 0) 231 lua_error(L); 232 233 return popValue!LuaFunction(L); 234 } 235 236 /** 237 * Execute a string of Lua _code. 238 * Params: 239 * code = _code to run 240 * handler = error handling scheme 241 * Returns: 242 * Any _code return values 243 * See_Also: 244 * $(MREF LuaErrorHandler) 245 */ 246 LuaObject[] doString(in char[] code, LuaErrorHandler handler = LuaErrorHandler.None) @trusted 247 { 248 auto top = lua_gettop(L); 249 250 doChunk!(luaL_loadstring)(code, handler); 251 252 auto nret = lua_gettop(L) - top; 253 254 return popStack(L, nret); 255 } 256 257 /** 258 * Execute a file of Lua code. 259 * Params: 260 * path = _path to file 261 * handler = error handling scheme 262 * Returns: 263 * Any script return values 264 * See_Also: 265 * $(MREF LuaErrorHandler) 266 */ 267 LuaObject[] doFile(in char[] path, LuaErrorHandler handler = LuaErrorHandler.None) @trusted 268 { 269 auto top = lua_gettop(L); 270 271 doChunk!(luaL_loadfile)(path, handler); 272 273 auto nret = lua_gettop(L) - top; 274 275 return popStack(L, nret); 276 } 277 278 /** 279 * Create a new, empty table. 280 * Returns: 281 * The new table 282 */ 283 LuaTable newTable()() @trusted 284 { 285 return newTable(0, 0); 286 } 287 288 /** 289 * Create a new, empty table with pre-allocated space for members. 290 * Params: 291 * narr = number of pre-allocated array slots 292 * nrec = number of pre-allocated non-array slots 293 * Returns: 294 * The new table 295 */ 296 LuaTable newTable()(uint narr, uint nrec) @trusted 297 { 298 lua_createtable(L, narr, nrec); 299 return popValue!LuaTable(L); 300 } 301 302 /** 303 * Create a new table from an $(D InputRange). 304 * If the element type of the range is $(D Tuple!(T, U)), 305 * then each element makes up a key-value pair, where 306 * $(D T) is the key and $(D U) is the value of the pair. 307 * For any other element type $(D T), a table with sequential numeric 308 * keys is created (an array). 309 * Params: 310 * range = $(D InputRange) of key-value pairs or elements 311 * Returns: 312 * The new table 313 */ 314 LuaTable newTable(Range)(Range range) @trusted if(isInputRange!Range) 315 { 316 alias ElementType!Range Elem; 317 318 static if(hasLength!Range) 319 { 320 immutable numElements = range.length; 321 assert(numElements < int.max, "lua_createtable only supports int.max many elements"); 322 } 323 else 324 { 325 immutable numElements = 0; 326 } 327 328 static if(isTuple!Elem) // Key-value pairs 329 { 330 static assert(range.front.length == 2, "key-value tuple must have exactly 2 values."); 331 332 lua_createtable(L, 0, cast(int)numElements); 333 334 foreach(pair; range) 335 { 336 pushValue(L, pair[0]); 337 pushValue(L, pair[1]); 338 lua_rawset(L, -3); 339 } 340 } 341 else // Sequential table 342 { 343 lua_createtable(L, cast(int)numElements, 0); 344 345 int i = 1; 346 347 foreach(value; range) 348 { 349 pushValue(L, value); 350 lua_rawseti(L, -2, i); 351 ++i; 352 } 353 } 354 355 return popValue!LuaTable(L); 356 } 357 358 /** 359 * Wrap a D value in a Lua reference. 360 * 361 * Note that using this method is only necessary in certain situations, 362 * such as when you want to act on the reference before fully exposing it to Lua. 363 * Params: 364 * T = type of reference. Must be $(D LuaObject), $(D LuaTable), $(D LuaFunction) or $(D LuaDynamic). 365 * Defaults to $(D LuaObject). 366 * value = D value to _wrap 367 * Returns: 368 * A Lua reference to value 369 */ 370 T wrap(T = LuaObject, U)(U value) @trusted if(is(T : LuaObject) || is(T == LuaDynamic)) 371 { 372 pushValue(L, value); 373 return popValue!T(L); 374 } 375 376 /** 377 * Register a D class or struct with Lua. 378 * 379 * This method exposes a type's constructors and static interface to Lua. 380 * Params: 381 * T = class or struct to register 382 * Returns: 383 * Reference to the registered type in Lua 384 */ 385 LuaObject registerType(T)() @trusted 386 { 387 pushStaticTypeInterface!T(L); 388 return popValue!LuaObject(L); 389 } 390 391 /** 392 * Same as calling $(D globals._get) with the same arguments. 393 * See_Also: 394 * $(DPREF table, LuaTable._get) 395 */ 396 T get(T, U...)(U args) 397 { 398 return globals.get!T(args); 399 } 400 401 /** 402 * Same as calling $(D globals.get!LuaObject) with the same arguments. 403 * See_Also: 404 * $(DPREF table, LuaTable._opIndex) 405 */ 406 LuaObject opIndex(T...)(T args) 407 { 408 return globals.get!LuaObject(args); 409 } 410 411 /** 412 * Same as calling $(D globals._set) with the same arguments. 413 * See_Also: 414 * $(DPREF table, LuaTable._set) 415 */ 416 void set(T, U)(T key, U value) 417 { 418 globals.set(key, value); 419 } 420 421 /** 422 * Same as calling $(D globals._opIndexAssign) with the same arguments. 423 * See_Also: 424 * $(DPREF table, LuaTable._opIndexAssign) 425 */ 426 void opIndexAssign(T, U...)(T value, U args) 427 { 428 globals()[args] = value; 429 } 430 } 431 432 version(unittest) 433 { 434 import luad.testing; 435 import std.string : splitLines; 436 private LuaState lua; 437 } 438 439 unittest 440 { 441 lua = new LuaState; 442 assert(LuaState.fromPointer(lua.state) == lua); 443 444 lua.openLibs(); 445 446 //default panic handler 447 try 448 { 449 lua.doString(`error("Hello, D!")`, LuaErrorHandler.Traceback); 450 assert(false); 451 } 452 catch(LuaErrorException e) 453 { 454 auto lines = splitLines(e.msg); 455 assert(lines.length > 1); 456 assert(lines[0] == `[string "error("Hello, D!")"]:1: Hello, D!`); 457 } 458 459 lua.set("success", false); 460 assert(!lua.get!bool("success")); 461 462 lua.doString(`success = true`); 463 assert(lua.get!bool("success")); 464 465 auto foo = lua.wrap!LuaTable([1, 2, 3]); 466 foo[4] = "test"; // Lua tables start at 1 467 lua["foo"] = foo; 468 unittest_lua(lua.state, ` 469 for i = 1, 3 do 470 assert(foo[i] == i) 471 end 472 assert(foo[4] == "test") 473 `); 474 475 LuaFunction multipleReturns = lua.loadString(`return 1, "two", 3`); 476 LuaObject[] results = multipleReturns(); 477 478 assert(results.length == 3); 479 assert(results[0].type == LuaType.Number); 480 assert(results[1].type == LuaType.String); 481 assert(results[2].type == LuaType.Number); 482 } 483 484 unittest // LuaState.newTable(range) 485 { 486 import std.algorithm; 487 488 auto input = [1, 2, 3]; 489 490 lua["tab"] = lua.newTable(input); 491 492 unittest_lua(lua.state, ` 493 assert(#tab == 3) 494 for i = 1, 3 do 495 assert(tab[i] == i) 496 end 497 `); 498 499 lua["tab"] = lua.newTable(filter!(i => i == 2)(input)); 500 501 unittest_lua(lua.state, ` 502 assert(#tab == 1) 503 assert(tab[1] == 2) 504 `); 505 506 auto keys = iota(7, 14); 507 auto values = repeat(42); 508 509 lua["tab"] = lua.newTable(zip(keys, values)); 510 511 unittest_lua(lua.state, ` 512 assert(not tab[1]) 513 assert(not tab[6]) 514 for i = 7, 13 do 515 assert(tab[i] == 42) 516 end 517 assert(not tab[14]) 518 `); 519 } 520 521 unittest 522 { 523 static class Test 524 { 525 private: 526 /+ Not working as of 2.062 527 static int priv; 528 static void priv_fun() {} 529 +/ 530 531 public: 532 static int pub = 123; 533 534 static string foo() { return "bar"; } 535 536 this(int i) 537 { 538 _bar = i; 539 } 540 541 int bar(){ return _bar; } 542 int _bar; 543 } 544 545 lua["Test"] = lua.registerType!Test(); 546 547 unittest_lua(lua.state, ` 548 assert(type(Test) == "table") 549 -- TODO: private members are currently pushed too... 550 --assert(Test.priv == nil) 551 --assert(Test.priv_fun == nil) 552 assert(Test._foo == nil) 553 assert(Test._bar == nil) 554 555 local test = Test(42) 556 assert(test:bar() == 42) 557 558 assert(Test.pub == 123) 559 assert(Test.foo() == "bar") 560 `); 561 } 562 563 version(Windows) unittest // Enable carefully, see issue #35 564 { 565 // setPanicHandler, keep this test last 566 static void panic(LuaState lua, in char[] error) 567 { 568 throw new Exception("hijacked error!"); 569 } 570 571 lua.setPanicHandler(&panic); 572 573 try 574 { 575 lua.doString(`error("test")`); 576 } 577 catch(Exception e) 578 { 579 assert(e.msg == "hijacked error!"); 580 } 581 }