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() 133 { 134 T get(T, IndexT)(IndexT index) 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 const meIndex = this.push(); 145 scope(exit) this.pop(); 146 147 this.lua.push(index); 148 lua_gettable(this.lua.handle, meIndex < 0 ? meIndex - 1 : meIndex); 149 auto value = this.lua.get!T(-1); 150 this.lua.pop(1); 151 return value; 152 } 153 154 void set(T, IndexT)(IndexT index, T value) 155 if(isNumeric!IndexT || is(IndexT == string)) 156 { 157 const meIndex = this.push(); 158 scope(exit) this.pop(); 159 160 this.lua.push(index); 161 this.lua.push(value); 162 lua_settable(this.lua.handle, meIndex < 0 ? meIndex - 2 : meIndex); 163 } 164 165 void setMetatable(LuaTable metatable) 166 { 167 const meIndex = this.push(); 168 scope(exit) this.pop(); 169 170 metatable.push(); 171 lua_setmetatable(this.lua.handle, meIndex); 172 } 173 174 void opIndexAssign(T, IndexT)(T value, IndexT index) 175 { 176 this.set(index, value); 177 } 178 179 size_t length() 180 { 181 const index = this.push(); 182 scope(exit) this.pop(); 183 184 return lua_objlen(this.lua.handle, index); 185 } 186 } 187 188 struct LuaTablePseudo 189 { 190 private 191 { 192 LuaState* _lua; 193 int _index; 194 } 195 196 @safe @nogc 197 this(LuaState* lua, int index) nothrow 198 { 199 this._index = index; 200 this._lua = lua; 201 } 202 203 void pushElement(IndexT)(IndexT index) 204 if(isNumeric!IndexT || is(IndexT == string)) 205 { 206 this.lua.push(index); 207 lua_gettable(this.lua.handle, this._index); 208 } 209 210 T get(T, IndexT)(IndexT index) 211 if(isNumeric!IndexT || is(IndexT == string)) 212 { 213 static assert( 214 !is(IndexT == LuaTableWeak) 215 && !is(IndexT == LuaFuncWeak) 216 && !is(IndexT == const(char)[]), 217 "Can't use weak references with `get` as this function does not keep the value alive on the stack." 218 ); 219 220 this.lua.push(index); 221 lua_gettable(this.lua.handle, this._index); 222 auto value = this.lua.get!T(-1); 223 this.lua.pop(1); 224 return value; 225 } 226 227 void set(T, IndexT)(IndexT index, T value) 228 if(isNumeric!IndexT || is(IndexT == string)) 229 { 230 this.lua.push(index); 231 this.lua.push(value); 232 lua_settable(this.lua.handle, this._index); 233 } 234 235 void opIndexAssign(T, IndexT)(T value, IndexT index) 236 { 237 this.set(index, value); 238 } 239 240 @property @safe @nogc 241 LuaState* lua() nothrow pure 242 { 243 return this._lua; 244 } 245 } 246 247 struct LuaTableWeak 248 { 249 mixin LuaTableFuncs; 250 251 private 252 { 253 LuaState* _lua; 254 int _index; 255 } 256 257 this(LuaState* lua, int index) 258 { 259 lua.enforceType(LuaValue.Kind.table, index); 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 < 0 ? this._index - 1 : this._index); 269 } 270 271 @safe @nogc 272 int push() nothrow pure const 273 { 274 return this._index; 275 } 276 277 void pop() 278 { 279 this.lua.enforceType(LuaValue.Kind.table, this._index); 280 } 281 282 @property @safe @nogc 283 LuaState* lua() nothrow pure 284 { 285 return this._lua; 286 } 287 } 288 289 struct LuaTable 290 { 291 import std.range : isInputRange; 292 import std.traits : isAssociativeArray; 293 import std.typecons : RefCounted; 294 mixin LuaTableFuncs; 295 296 private 297 { 298 static struct State 299 { 300 LuaState* lua; 301 int ref_; 302 bool isWrapper; 303 304 ~this() 305 { 306 if(this.lua && !this.isWrapper) 307 luaL_unref(this.lua.handle, LUA_REGISTRYINDEX, this.ref_); 308 } 309 } 310 RefCounted!State _state; 311 } 312 313 void pushElement(IndexT)(IndexT index) 314 if(isNumeric!IndexT || is(IndexT == string)) 315 { 316 this.push(); 317 scope(exit) this.lua.remove(-2); 318 319 this.lua.push(index); 320 lua_gettable(this.lua.handle, -2); 321 } 322 323 static LuaTable makeRef(LuaState* lua) 324 { 325 lua.enforceType(LuaValue.Kind.table, -1); 326 RefCounted!State state; 327 state.lua = lua; 328 state.ref_ = luaL_ref(lua.handle, LUA_REGISTRYINDEX); 329 state.isWrapper = lua._isWrapper; 330 331 return LuaTable(state); 332 } 333 334 static LuaTable makeNew(LuaState* lua, int arrayCapacity = 0, int recordCapacity = 0) 335 { 336 lua_createtable(lua.handle, arrayCapacity, recordCapacity); 337 return LuaTable.makeRef(lua); 338 } 339 340 static LuaTable makeNew(Range)(LuaState* lua, Range range) 341 if(isInputRange!Range) 342 { 343 import std.range : ElementType; 344 alias Element = ElementType!Range; 345 346 lua_newtable(lua.handle); 347 static if(is(Element == struct) && __traits(hasMember, Element, "key") && __traits(hasMember, Element, "value")) 348 { 349 foreach(kvp; range) 350 { 351 lua.push(kvp.key); 352 lua.push(kvp.value); 353 lua.rawSet(-3); 354 } 355 } 356 else 357 { 358 int i = 1; 359 foreach(v; range) 360 { 361 lua.push(v); 362 lua.rawSet(-2, i++); 363 } 364 } 365 366 return LuaTable.makeRef(lua); 367 } 368 369 static LuaTable makeNew(AA)(LuaState* lua, AA aa) 370 if(isAssociativeArray!AA) 371 { 372 return makeNew(lua, aa.byKeyValue); 373 } 374 375 @nogc 376 int push() nothrow 377 { 378 lua_rawgeti(this._state.lua.handle, LUA_REGISTRYINDEX, this._state.ref_); 379 return this._state.lua.top; 380 } 381 382 void pop() 383 { 384 this._state.lua.enforceType(LuaValue.Kind.table, -1); 385 this._state.lua.pop(1); 386 } 387 388 LuaState* lua() 389 { 390 return this._state.lua; 391 } 392 } 393 394 unittest 395 { 396 import std; 397 auto l = LuaState(null); 398 399 l.push(["Henlo, ", "Warld."]); 400 auto t = l.get!LuaTableWeak(-1); 401 int i = 0; 402 t.ipairs!((k, v) 403 { 404 i++; 405 assert( 406 (k == 1 && v.textValue == "Henlo, ") 407 || (k == 2 && v.textValue == "Warld."), 408 format("%s, %s", k, v) 409 ); 410 }); 411 assert(t.get!string(1) == "Henlo, "); 412 assert(t.get!string(2) == "Warld."); 413 assert(i == 2); 414 t.ipairs!(string, (k, v) 415 { 416 if(k == 1) 417 assert(v == "Henlo, "); 418 }); 419 l.pop(1); 420 } 421 422 unittest 423 { 424 import std; 425 auto l = LuaState(null); 426 427 l.push( 428 [ 429 "a": "bc", 430 "1": "23" 431 ] 432 ); 433 auto t = l.get!LuaTable(-1); 434 int i = 0; 435 t.pairs!((k, v) 436 { 437 i++; 438 assert( 439 (k.textValue == "a" && v.textValue == "bc") 440 || (k.textValue == "1" && v.textValue == "23"), 441 format("%s, %s", k, v) 442 ); 443 }); 444 assert(t.get!string("a") == "bc"); 445 assert(t.get!string("1") == "23"); 446 assert(i == 2); 447 l.pop(1); 448 } 449 450 unittest 451 { 452 auto l = LuaState(null); 453 auto t = LuaTable.makeNew(&l); 454 455 t["test"] = "icles"; 456 t[4] = 20; 457 assert(t.get!string("test") == "icles"); 458 assert(t.get!LuaNumber(4) == 20); 459 } 460 461 unittest 462 { 463 import std; 464 auto l = LuaState(null); 465 auto t = LuaTable.makeNew(&l, iota(1, 11)); 466 assert(t.length == 10); 467 t.ipairs!(LuaNumber, (i, v) 468 { 469 assert(i == v); 470 }); 471 } 472 473 unittest 474 { 475 auto l = LuaState(null); 476 auto t = LuaTable.makeNew(&l, ["a": "bc", "1": "23"]); 477 478 int count; 479 t.pairs!(string, string, (k, v) 480 { 481 if(k == "a") 482 assert(v == "bc"); 483 else if(k == "1") 484 assert(v == "23"); 485 else 486 assert(false); 487 count++; 488 }); 489 assert(count == 2); 490 } 491 492 unittest 493 { 494 auto l = LuaState(null); 495 auto t = LuaTable.makeNew(&l); 496 t["__call"] = &luaCWrapperSmart!((LuaValue _) => 2); 497 auto t2 = LuaTable.makeNew(&l); 498 t2.setMetatable(t); 499 500 l.globalTable["test"] = t2; 501 l.doString("assert(test() == 2)"); 502 } 503 504 unittest 505 { 506 auto l = LuaState(null); 507 auto t = LuaTable.makeNew(&l); 508 t["hello"] = () => "world"; 509 auto f = t.get!LuaFunc("hello").bind!(string); 510 assert(f() == "world"); 511 }