1 module luad.table;
2 
3 import luad.c.all;
4 
5 import luad.base;
6 import luad.stack;
7 import luad.conversions.structs;
8 
9 /// Represents a Lua table.
10 struct LuaTable
11 {
12 	/// LuaTable sub-types $(DPREF base, LuaObject) through this reference.
13 	LuaObject object;
14 
15 	alias object this;
16 
17 	package this(lua_State* L, int idx)
18 	{
19 		LuaObject.checkType(L, idx, LUA_TTABLE, "LuaTable");
20 		object = LuaObject(L, idx);
21 	}
22 
23 	/**
24 	 * Lookup a value in this table or in a sub-table of this table.
25 	 * Params:
26 	 *	 T = type of value
27 	 *	 args = list of keys, where all keys but the last one should result in a table
28 	 * Returns:
29 	 *	 $(D t[k]) where $(D t) is the table for the second-to-last parameter, and $(D k) is the last parameter
30 	 *
31 	 * Examples:
32 	 * ----------------------
33 	auto execute = lua.get!LuaFunction("os", "execute");
34 	execute(`echo hello, world!`);
35 	 * ----------------------
36 	 */
37 	T get(T, U...)(U args) @trusted
38 	{
39 		this.push();
40 
41 		foreach(key; args)
42 		{
43 			pushValue(this.state, key);
44 			lua_gettable(this.state, -2);
45 		}
46 
47 		auto ret = getValue!T(this.state, -1);
48 		lua_pop(this.state, args.length + 1);
49 		return ret;
50 	}
51 
52 	/**
53 	 * Read a string value in this table without making a copy of the string.
54 	 * The read string is passed to $(D dg), and should not be escaped.
55 	 * If the value for $(D key) is not a string, $(D dg) is not called.
56 	 * Params:
57 	 *    key = lookup _key
58 	 *    dg = delegate to receive string
59 	 * Returns:
60 	 *    $(D true) if the value for $(D key) was a string and passed to $(D dg), $(D false) otherwise
61 	 * Examples:
62 	 --------------------
63 	t[2] = "two";
64 	t.readString(2, str => assert(str == "two"));
65 	 --------------------
66 	 */
67 	bool readString(T)(T key, scope void delegate(in char[] str) dg) @trusted
68 	{
69 		this.push();
70 		scope(exit) lua_pop(this.state, 1);
71 
72 		pushValue(this.state, key);
73 
74 		lua_gettable(this.state, -2);
75 		scope(exit) lua_pop(this.state, 1);
76 
77 		if(lua_isstring(this.state, -1) == 0)
78 			return false;
79 
80 		size_t len;
81 		const(char)* cstr = lua_tolstring(this.state, -1, &len);
82 
83 		dg(cstr[0 .. len]);
84 		return true;
85 	}
86 
87 	/**
88 	 * Same as calling $(D get!LuaObject) with the same arguments.
89 	 * Examples:
90 	 * ---------------------
91 	auto luapath = lua["package", "path"];
92 	writefln("LUA_PATH:\n%s", luapath);
93 	 * ---------------------
94 	 * See_Also:
95 	 *   $(MREF LuaTable.get)
96 	 */
97 	LuaObject opIndex(T...)(T args)
98 	{
99 		return get!LuaObject(args);
100 	}
101 
102 	/**
103 	 * Set a key-value pair in this table.
104 	 * Params:
105 	 *	 key = key to _set
106 	 *	 value = value for $(D key)
107 	 */
108 	void set(T, U)(T key, U value) @trusted
109 	{
110 		this.push();
111 		scope(success) lua_pop(this.state, 1);
112 
113 		pushValue(this.state, key);
114 		pushValue(this.state, value);
115 		lua_settable(this.state, -3);
116 	}
117 
118 	/**
119 	 * Set a key-value pair this table or in a sub-table of this table.
120 	 * Params:
121 	 *	 value = value to set
122 	 *	 args = list of keys, where all keys but the last one should result in a table
123 	 * Returns:
124 	 *	 $(D t[k] = value), where $(D t) is the table for the second-to-last parameter in args,
125 	 *	 and $(D k) is the last parameter in args
126 	 *
127 	 * Examples:
128 	 * ----------------------
129 	lua["string", "empty"] = (in char[] s){ return s.length == 0; };
130 	lua.doString(`assert(string.empty(""))`);
131 	 * ----------------------
132 	 */
133 	void opIndexAssign(T, U...)(T value, U args) @trusted
134 	{
135 		this.push();
136 		scope(success) lua_pop(this.state, 1);
137 
138 		foreach(i, arg; args)
139 		{
140 			static if(i != args.length - 1)
141 			{
142 				pushValue(this.state, arg);
143 				lua_gettable(this.state, -2);
144 			}
145 		}
146 
147 		pushValue(this.state, args[$-1]);
148 		pushValue(this.state, value);
149 		lua_settable(this.state, -3);
150 
151 		lua_pop(this.state, args.length - 1);
152 	}
153 
154 	/**
155 	 * Create struct of type $(D T) and fill its members with fields from this table.
156 	 *
157 	 * Struct fields that are not present in this table are left at their default value.
158 	 *
159 	 * Params:
160 	 *	 T = any struct type
161 	 *
162 	 * Returns:
163 	 *	 Newly created struct
164 	 */
165 	T toStruct(T)() @trusted if (is(T == struct))
166 	{
167 		push();
168 		return popValue!T(this.state);
169 	}
170 
171 	/**
172 	 * Fill a struct's members with fields from this table.
173 	 * Params:
174 	 *	 s = struct to fill
175 	 */
176 	void copyTo(T)(ref T s) @trusted if (is(T == struct))
177 	{
178 		push();
179 		fillStruct(this.state, -1, s);
180 		lua_pop(L, 1);
181 	}
182 
183 	/**
184 	 * Set the metatable for this table.
185 	 * Params:
186 	 *	 meta = new metatable
187  	 */
188 	void setMetaTable(ref LuaTable meta) @trusted
189 	in{ assert(this.state == meta.state); }
190 	body
191 	{
192 		this.push();
193 		meta.push();
194 		lua_setmetatable(this.state, -2);
195 		lua_pop(this.state, 1);
196 	}
197 
198 	/**
199 	 * Get the metatable for this table.
200 	 * Returns:
201 	 *	 A reference to the metatable for this table. The reference is nil if this table has no metatable.
202 	 */
203 	LuaTable getMetaTable() @trusted
204 	{
205 		this.push();
206 		scope(success) lua_pop(this.state, 1);
207 
208 		return lua_getmetatable(this.state, -1) == 0? LuaTable() : popValue!LuaTable(this.state);
209 	}
210 
211 	/**
212 	* Get the array length of the table.
213 	*/
214 	size_t length() @trusted
215 	{
216 		this.push();
217 		size_t len = lua_objlen(this.state, -1);
218 		lua_pop(this.state, 1);
219 		return len;
220 	}
221 
222 	/**
223 	 * Iterate over the values in this table.
224 	 */
225 	int opApply(T)(int delegate(ref T value) dg) @trusted
226 	{
227 		this.push();
228 		lua_pushnil(this.state);
229 		while(lua_next(this.state, -2) != 0)
230 		{
231 			auto value = popValue!T(this.state);
232 			int result = dg(value);
233 			if(result != 0)
234 			{
235 				lua_pop(this.state, 2);
236 				return result;
237 			}
238 		}
239 		lua_pop(this.state, 1);
240 		return 0;
241 	}
242 
243 	/**
244 	 * Iterate over the key-value pairs in this table.
245 	 */
246 	int opApply(T, U)(int delegate(ref U key, ref T value) dg) @trusted
247 	{
248 		this.push();
249 		lua_pushnil(this.state);
250 		while(lua_next(this.state, -2) != 0)
251 		{
252 			auto value = popValue!T(this.state);
253 			auto key = getValue!U(this.state, -1);
254 
255 			int result = dg(key, value);
256 			if(result != 0)
257 			{
258 				lua_pop(this.state, 2);
259 				return result;
260 			}
261 		}
262 		lua_pop(this.state, 1);
263 		return 0;
264 	}
265 }
266 
267 unittest
268 {
269 	lua_State* L = luaL_newstate();
270 	scope(success)
271 	{
272 		assert(lua_gettop(L) == 0);
273 		lua_close(L);
274 	}
275 
276 	lua_newtable(L);
277 	auto t = popValue!LuaTable(L);
278 
279 	assert(t.type == LuaType.Table);
280 
281 	t.set("foo", "bar");
282 	assert(t.get!string("foo") == "bar");
283 
284 	t.set("foo", nil);
285 	assert(t.get!LuaObject("foo").isNil);
286 
287 	t.set("foo", ["outer": ["inner": "hi!"]]);
288 	auto s = t.get!(string)("foo", "outer", "inner");
289 	assert(s == "hi!");
290 
291 	auto o = t["foo", "outer"];
292 	assert(o.type == LuaType.Table);
293 
294 	t["foo", "outer", "inner"] = "hello!";
295 	auto s2 = t.get!(string)("foo", "outer", "inner");
296 	assert(s2 == "hello!");
297 
298 	// length
299 	t.set("array", ["one", "two"]);
300 	auto a = t.get!LuaTable("array");
301 	assert(a.length == 2);
302 
303 	// readString
304 	t[2] = "two";
305 	bool success = t.readString(2, (in char[] str) {
306 		assert(str == "two");
307 	});
308 	assert(success);
309 
310 	t[2] = true;
311 	success = t.readString(2, (in char[] str) { assert(false); });
312 	assert(!success);
313 
314 	// metatable
315 	pushValue(L, ["__index": (LuaObject self, string key){
316 		return key;
317 	}]);
318 	auto meta = popValue!LuaTable(L);
319 
320 	lua_newtable(L);
321 	auto t2 = popValue!LuaTable(L);
322 
323 	t2.setMetaTable(meta);
324 
325 	auto test = t2.get!string("foobar");
326 	assert(test == "foobar");
327 
328 	assert(t2.getMetaTable() == meta);
329 
330 	// opApply
331 	auto input = [1, 2, 3];
332 	pushValue(L, input);
333 	auto applyTest = popValue!LuaTable(L);
334 
335 	int i = 0;
336 	foreach(int v; applyTest)
337 	{
338 		assert(input[i++] == v);
339 	}
340 
341 	auto inputWithKeys = ["one": 1, "two": 2, "three": 3];
342 	pushValue(L, inputWithKeys);
343 	auto applyTestKeys = popValue!LuaTable(L);
344 
345 	foreach(string key, int value; applyTestKeys)
346 	{
347 		assert(inputWithKeys[key] == value);
348 	}
349 }