1 module lumars.table; 2 3 import bindbc.lua, lumars; 4 import std.traits : isNumeric; 5 6 enum LuaIterateOption 7 { 8 none = 0, 9 dontDupeStrings = 1 << 0, 10 } 11 12 template ipairs(alias Func, LuaIterateOption Options = LuaIterateOption.none) 13 { 14 void ipairs(LuaTableT)(LuaTableT table) 15 { 16 import std.format : format; 17 18 const index = table.push(); 19 scope(exit) table.pop(); 20 21 const expectedTop = table.lua.top + 1; 22 foreach(i; 1..int.max) 23 { 24 lua_rawgeti(table.lua.handle, index, i); 25 scope(failure) table.lua.pop(1); 26 if(lua_isnil(table.lua.handle, -1)) 27 { 28 table.lua.pop(1); 29 break; 30 } 31 32 LuaValue value; 33 const valueType = table.lua.type(-1); 34 if(valueType == LuaValue.Kind.text) 35 { 36 static if((Options & LuaIterateOption.dontDupeStrings) > 0) 37 value = LuaValue(table.lua.get!(const(char)[])(-1)); 38 else 39 value = LuaValue(table.lua.get!string(-1)); 40 } 41 else 42 value = table.lua.get!LuaValue(-1); 43 44 Func(i, value); 45 if(table.lua.top != expectedTop) 46 { 47 table.lua.printStack(); 48 assert(false, 49 "Expected stack top to be %s after call to user function, but it is %s." 50 .format(expectedTop, table.lua.top) 51 ); 52 } 53 54 table.lua.pop(1); 55 } 56 } 57 } 58 59 template ipairs(ValueT, alias Func) 60 { 61 void ipairs(LuaTableT)(LuaTableT table) 62 { 63 table.ipairs!((k, _) 64 { 65 Func(k, table.lua.get!ValueT(-1)); 66 }); 67 } 68 } 69 70 template pairs(alias Func, LuaIterateOption Options = LuaIterateOption.none) 71 { 72 void pairs(LuaTableT)(LuaTableT table) 73 { 74 auto index = table.push(); 75 scope(exit) table.pop(); 76 77 index = index < 0 ? index - 1 : index; 78 79 table.lua.push(null); 80 while(table.lua.next(index)) 81 { 82 scope(failure) table.lua.pop(2); 83 84 LuaValue key; 85 const keyType = table.lua.type(-2); 86 if(keyType == LuaValue.Kind.text) 87 { 88 static if((Options & LuaIterateOption.dontDupeStrings) > 0) 89 key = LuaValue(table.lua.get!(const(char)[])(-2)); 90 else 91 key = LuaValue(table.lua.get!string(-2)); 92 } 93 else 94 key = table.lua.get!LuaValue(-2); 95 96 LuaValue value; 97 const valueType = table.lua.type(-1); 98 if(valueType == LuaValue.Kind.text) 99 { 100 static if((Options & LuaIterateOption.dontDupeStrings) > 0) 101 value = LuaValue(table.lua.get!(const(char)[])(-1)); 102 else 103 value = LuaValue(table.lua.get!string(-1)); 104 } 105 else 106 value = table.lua.get!LuaValue(-1); 107 108 Func(key, value); 109 table.lua.pop(1); 110 } 111 } 112 } 113 114 template pairs(KeyT, ValueT, alias Func) 115 { 116 void pairs(LuaTableT)(LuaTableT table) 117 { 118 table.pairs!((k, v) 119 { 120 static if(is(KeyT == LuaValue) && is(ValueT == LuaValue)) 121 Func(k, v); 122 else static if(is(KeyT == LuaValue)) 123 Func(k, table.lua.get!ValueT(-1)); 124 else static if(is(ValueT == LuaValue)) 125 Func(table.lua.get!KeyT(-2), v); 126 else 127 Func(table.lua.get!KeyT(-2), table.lua.get!ValueT(-1)); 128 }); 129 } 130 } 131 132 private mixin template LuaTableFuncs(bool isPseudo) 133 { 134 T tryGet(T, IndexT)(IndexT index, out bool result) 135 if(isNumeric!IndexT || is(IndexT == string)) 136 { 137 static assert( 138 !is(IndexT == LuaTableWeak) 139 && !is(IndexT == LuaFuncWeak) 140 && !is(IndexT == const(char)[]), 141 "Can't use weak references with `get` as this function does not keep the value alive on the stack." 142 ); 143 144 static if(!isPseudo) 145 { 146 const meIndex = this.push(); 147 scope(exit) this.pop(); 148 const tableIndex = meIndex < 0 ? meIndex - 1 : meIndex; 149 } 150 else 151 const tableIndex = this._index; 152 153 this.lua.push(index); 154 lua_gettable(this.lua.handle, tableIndex); 155 result = this.lua.isType!T(-1); 156 157 if(!result) 158 { 159 this.lua.pop(1); 160 return T.init; 161 } 162 else 163 { 164 auto value = this.lua.get!T(-1); 165 this.lua.pop(1); 166 return value; 167 } 168 } 169 170 T get(T, IndexT)(IndexT index) 171 if(isNumeric!IndexT || is(IndexT == string)) 172 { 173 static assert( 174 !is(IndexT == LuaTableWeak) 175 && !is(IndexT == LuaFuncWeak) 176 && !is(IndexT == const(char)[]), 177 "Can't use weak references with `get` as this function does not keep the value alive on the stack." 178 ); 179 180 static if(!isPseudo) 181 { 182 const meIndex = this.push(); 183 scope(exit) this.pop(); 184 const tableIndex = meIndex < 0 ? meIndex - 1 : meIndex; 185 } 186 else 187 const tableIndex = this._index; 188 189 this.lua.push(index); 190 lua_gettable(this.lua.handle, tableIndex); 191 auto value = this.lua.get!T(-1); 192 this.lua.pop(1); 193 return value; 194 } 195 196 void set(T, IndexT)(IndexT index, T value) 197 if(isNumeric!IndexT || is(IndexT == string)) 198 { 199 static if(!isPseudo) 200 { 201 const meIndex = this.push(); 202 scope(exit) this.pop(); 203 const tableIndex = meIndex < 0 ? meIndex - 2 : meIndex; 204 } 205 else 206 const tableIndex = this._index; 207 208 this.lua.push(index); 209 this.lua.push(value); 210 lua_settable(this.lua.handle, tableIndex); 211 } 212 213 void setMetatable(LuaTable metatable) 214 { 215 static if(!isPseudo) 216 { 217 const meIndex = this.push(); 218 scope(exit) this.pop(); 219 const tableIndex = meIndex < 0 ? meIndex - 1 : meIndex; 220 } 221 else 222 const tableIndex = this._index; 223 224 metatable.push(); 225 lua_setmetatable(this.lua.handle, tableIndex); 226 } 227 228 void opIndexAssign(T, IndexT)(T value, IndexT index) 229 { 230 this.set(index, value); 231 } 232 233 size_t length() 234 { 235 static if(!isPseudo) 236 { 237 const index = this.push(); 238 scope(exit) this.pop(); 239 } 240 else 241 const index = this._index; 242 243 return lua_objlen(this.lua.handle, index); 244 } 245 } 246 247 struct LuaTablePseudo 248 { 249 mixin LuaTableFuncs!true; 250 251 private 252 { 253 LuaState* _lua; 254 int _index; 255 } 256 257 @safe @nogc 258 this(LuaState* lua, int index) nothrow 259 { 260 this._index = index; 261 this._lua = lua; 262 } 263 264 void pushElement(IndexT)(IndexT index) 265 if(isNumeric!IndexT || is(IndexT == string)) 266 { 267 this.lua.push(index); 268 lua_gettable(this.lua.handle, this._index); 269 } 270 271 @property @safe @nogc 272 LuaState* lua() nothrow pure 273 { 274 return this._lua; 275 } 276 } 277 278 struct LuaTableWeak 279 { 280 mixin LuaTableFuncs!false; 281 282 private 283 { 284 LuaState* _lua; 285 int _index; 286 } 287 288 this(LuaState* lua, int index) 289 { 290 lua.enforceType(LuaValue.Kind.table, index); 291 this._index = index; 292 this._lua = lua; 293 } 294 295 void pushElement(IndexT)(IndexT index) 296 if(isNumeric!IndexT || is(IndexT == string)) 297 { 298 this.lua.push(index); 299 lua_gettable(this.lua.handle, this._index < 0 ? this._index - 1 : this._index); 300 } 301 302 @safe @nogc 303 int push() nothrow pure const 304 { 305 return this._index; 306 } 307 308 void pop() 309 { 310 this.lua.enforceType(LuaValue.Kind.table, this._index); 311 } 312 313 @property @safe @nogc 314 LuaState* lua() nothrow pure 315 { 316 return this._lua; 317 } 318 } 319 320 struct LuaTable 321 { 322 import std.range : isInputRange; 323 import std.traits : isAssociativeArray; 324 import std.typecons : RefCounted; 325 mixin LuaTableFuncs!false; 326 327 private 328 { 329 static struct State 330 { 331 LuaState* lua; 332 int ref_; 333 bool isWrapper; 334 335 ~this() 336 { 337 if(this.lua && !this.isWrapper) 338 luaL_unref(this.lua.handle, LUA_REGISTRYINDEX, this.ref_); 339 } 340 } 341 RefCounted!State _state; 342 } 343 344 void pushElement(IndexT)(IndexT index) 345 if(isNumeric!IndexT || is(IndexT == string)) 346 { 347 this.push(); 348 scope(exit) this.lua.remove(-2); 349 350 this.lua.push(index); 351 lua_gettable(this.lua.handle, -2); 352 } 353 354 static LuaTable makeRef(LuaState* lua) 355 { 356 lua.enforceType(LuaValue.Kind.table, -1); 357 RefCounted!State state; 358 state.lua = lua; 359 state.ref_ = luaL_ref(lua.handle, LUA_REGISTRYINDEX); 360 state.isWrapper = lua._isWrapper; 361 362 return LuaTable(state); 363 } 364 365 static LuaTable makeNew(LuaState* lua, int arrayCapacity = 0, int recordCapacity = 0) 366 { 367 lua_createtable(lua.handle, arrayCapacity, recordCapacity); 368 return LuaTable.makeRef(lua); 369 } 370 371 static LuaTable makeNew(Range)(LuaState* lua, Range range) 372 if(isInputRange!Range) 373 { 374 import std.range : ElementType; 375 alias Element = ElementType!Range; 376 377 lua_newtable(lua.handle); 378 static if(is(Element == struct) && __traits(hasMember, Element, "key") && __traits(hasMember, Element, "value")) 379 { 380 foreach(kvp; range) 381 { 382 lua.push(kvp.key); 383 lua.push(kvp.value); 384 lua.rawSet(-3); 385 } 386 } 387 else 388 { 389 int i = 1; 390 foreach(v; range) 391 { 392 lua.push(v); 393 lua.rawSet(-2, i++); 394 } 395 } 396 397 return LuaTable.makeRef(lua); 398 } 399 400 static LuaTable makeNew(AA)(LuaState* lua, AA aa) 401 if(isAssociativeArray!AA) 402 { 403 return makeNew(lua, aa.byKeyValue); 404 } 405 406 @nogc 407 int push() nothrow 408 { 409 lua_rawgeti(this._state.lua.handle, LUA_REGISTRYINDEX, this._state.ref_); 410 return this._state.lua.top; 411 } 412 413 void pop() 414 { 415 this._state.lua.enforceType(LuaValue.Kind.table, -1); 416 this._state.lua.pop(1); 417 } 418 419 LuaState* lua() 420 { 421 return this._state.lua; 422 } 423 } 424 425 unittest 426 { 427 import std; 428 auto l = LuaState(null); 429 430 l.push(["Henlo, ", "Warld."]); 431 auto t = l.get!LuaTableWeak(-1); 432 int i = 0; 433 t.ipairs!((k, v) 434 { 435 i++; 436 assert( 437 (k == 1 && v.textValue == "Henlo, ") 438 || (k == 2 && v.textValue == "Warld."), 439 format("%s, %s", k, v) 440 ); 441 }); 442 assert(t.get!string(1) == "Henlo, "); 443 assert(t.get!string(2) == "Warld."); 444 assert(i == 2); 445 t.ipairs!(string, (k, v) 446 { 447 if(k == 1) 448 assert(v == "Henlo, "); 449 }); 450 l.pop(1); 451 } 452 453 unittest 454 { 455 import std; 456 auto l = LuaState(null); 457 458 l.push( 459 [ 460 "a": "bc", 461 "1": "23" 462 ] 463 ); 464 auto t = l.get!LuaTable(-1); 465 int i = 0; 466 t.pairs!((k, v) 467 { 468 i++; 469 assert( 470 (k.textValue == "a" && v.textValue == "bc") 471 || (k.textValue == "1" && v.textValue == "23"), 472 format("%s, %s", k, v) 473 ); 474 }); 475 assert(t.get!string("a") == "bc"); 476 assert(t.get!string("1") == "23"); 477 assert(i == 2); 478 l.pop(1); 479 } 480 481 unittest 482 { 483 auto l = LuaState(null); 484 auto t = LuaTable.makeNew(&l); 485 486 t["test"] = "icles"; 487 t[4] = 20; 488 assert(t.get!string("test") == "icles"); 489 assert(t.get!LuaNumber(4) == 20); 490 } 491 492 unittest 493 { 494 import std; 495 auto l = LuaState(null); 496 auto t = LuaTable.makeNew(&l, iota(1, 11)); 497 assert(t.length == 10); 498 t.ipairs!(LuaNumber, (i, v) 499 { 500 assert(i == v); 501 }); 502 } 503 504 unittest 505 { 506 auto l = LuaState(null); 507 auto t = LuaTable.makeNew(&l, ["a": "bc", "1": "23"]); 508 509 int count; 510 t.pairs!(string, string, (k, v) 511 { 512 if(k == "a") 513 assert(v == "bc"); 514 else if(k == "1") 515 assert(v == "23"); 516 else 517 assert(false); 518 count++; 519 }); 520 assert(count == 2); 521 } 522 523 unittest 524 { 525 auto l = LuaState(null); 526 auto t = LuaTable.makeNew(&l); 527 t["__call"] = &luaCWrapperSmart!((LuaValue _) => 2); 528 auto t2 = LuaTable.makeNew(&l); 529 t2.setMetatable(t); 530 531 l.globalTable["test"] = t2; 532 l.doString("assert(test() == 2)"); 533 } 534 535 unittest 536 { 537 auto l = LuaState(null); 538 auto t = LuaTable.makeNew(&l); 539 t["hello"] = () => "world"; 540 auto f = t.get!LuaFunc("hello").bind!(string); 541 assert(f() == "world"); 542 }