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