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 	 * Iterate over the values in this table.
213 	 */
214 	int opApply(T)(int delegate(ref T value) dg) @trusted
215 	{
216 		this.push();
217 		lua_pushnil(this.state);
218 		while(lua_next(this.state, -2) != 0)
219 		{
220 			auto value = popValue!T(this.state);
221 			int result = dg(value);
222 			if(result != 0)
223 			{
224 				lua_pop(this.state, 2);
225 				return result;
226 			}
227 		}
228 		lua_pop(this.state, 1);
229 		return 0;
230 	}
231 
232 	/**
233 	 * Iterate over the key-value pairs in this table.
234 	 */
235 	int opApply(T, U)(int delegate(ref U key, ref T value) dg) @trusted
236 	{
237 		this.push();
238 		lua_pushnil(this.state);
239 		while(lua_next(this.state, -2) != 0)
240 		{
241 			auto value = popValue!T(this.state);
242 			auto key = getValue!U(this.state, -1);
243 
244 			int result = dg(key, value);
245 			if(result != 0)
246 			{
247 				lua_pop(this.state, 2);
248 				return result;
249 			}
250 		}
251 		lua_pop(this.state, 1);
252 		return 0;
253 	}
254 }
255 
256 unittest
257 {
258 	lua_State* L = luaL_newstate();
259 	scope(success)
260 	{
261 		assert(lua_gettop(L) == 0);
262 		lua_close(L);
263 	}
264 
265 	lua_newtable(L);
266 	auto t = popValue!LuaTable(L);
267 
268 	assert(t.type == LuaType.Table);
269 
270 	t.set("foo", "bar");
271 	assert(t.get!string("foo") == "bar");
272 
273 	t.set("foo", nil);
274 	assert(t.get!LuaObject("foo").isNil);
275 
276 	t.set("foo", ["outer": ["inner": "hi!"]]);
277 	auto s = t.get!(string)("foo", "outer", "inner");
278 	assert(s == "hi!");
279 
280 	auto o = t["foo", "outer"];
281 	assert(o.type == LuaType.Table);
282 
283 	t["foo", "outer", "inner"] = "hello!";
284 	auto s2 = t.get!(string)("foo", "outer", "inner");
285 	assert(s2 == "hello!");
286 
287 	// readString
288 	t[2] = "two";
289 	bool success = t.readString(2, (in char[] str) {
290 		assert(str == "two");
291 	});
292 	assert(success);
293 
294 	t[2] = true;
295 	success = t.readString(2, (in char[] str) { assert(false); });
296 	assert(!success);
297 
298 	// metatable
299 	pushValue(L, ["__index": (LuaObject self, string key){
300 		return key;
301 	}]);
302 	auto meta = popValue!LuaTable(L);
303 
304 	lua_newtable(L);
305 	auto t2 = popValue!LuaTable(L);
306 
307 	t2.setMetaTable(meta);
308 
309 	auto test = t2.get!string("foobar");
310 	assert(test == "foobar");
311 
312 	assert(t2.getMetaTable() == meta);
313 
314 	// opApply
315 	auto input = [1, 2, 3];
316 	pushValue(L, input);
317 	auto applyTest = popValue!LuaTable(L);
318 
319 	int i = 0;
320 	foreach(int v; applyTest)
321 	{
322 		assert(input[i++] == v);
323 	}
324 
325 	auto inputWithKeys = ["one": 1, "two": 2, "three": 3];
326 	pushValue(L, inputWithKeys);
327 	auto applyTestKeys = popValue!LuaTable(L);
328 
329 	foreach(string key, int value; applyTestKeys)
330 	{
331 		assert(inputWithKeys[key] == value);
332 	}
333 }