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 }