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 }