1 // Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
2 // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
3 
4 #ifndef _LUATABLE_H
5 #define _LUATABLE_H
6 
7 #include <cassert>
8 #include <iterator>
9 
10 #include <lua.hpp>
11 
12 #include "LuaPushPull.h"
13 #include "LuaRef.h"
14 #include "LuaUtils.h"
15 
16 /*
17  * The LuaTable class is a wrapper around a table present on the stack. There
18  * are three ways to instantiate a LuaTable object:
19  *
20  * > lua_State *l;
21  * > int i; // the stack index of a table
22  * > LuaTable(l); // This will allocate a new table on top of the stack
23  * > LuaTable(l, array_s, hash_s); // Same as previous but it uses "lua_createtable",
24  * >                               // so it preallocate array part and hash part on LVM
25  * > LuaTable(l, i); // This will wrap the object around an existing table
26  *
27  * Note that the LuaTable object never removes the wrapped table from the stack.
28  * Also, there is no integrity check except at the object creation, which means
29  * that if you fiddle with the stack below the wrapped table you will get
30  * unexpected results (most likely a crash).
31  *
32  * Get/Set:
33  *
34  * The Get and Set operators use the pi_lua_generic_{push pull} functions
35  * to fetch a value of any type from the table. It is possible to add support
36  * for extra types by overloading the aforementioned functions for the new
37  * type. The Get function takes an optional default value, which will be returned
38  * if the table does not contain the requested key. If no default is given, and
39  * the key is not in the table then a Lua error is generated. If the key is present
40  * but the value has an incompatible type, then a Lua error is generated (even if
41  * a default value is passed to the Get method).
42  *
43  * These operations are designed to restore the state of the stack, thus making
44  * it impossible to Get a LuaTable, as the latter would need a place on the
45  * stack. For this reason, the Sub() method is used to get a subtable,
46  * placing the "returned" table on the top of the stack.
47  *
48  * Example:
49  *
50  * > lua_State *l; // stack size: X
51  * > LuaTable t = LuaTable(l+1); // stack size: X+1, t = {}
52  * > t.Set("foo", 1); // stack size: X+1, t = {foo:1}
53  * > int foo = t.Get<int>("foo");
54  * > //int bar = t.Get<int>("bar"); // WOULD CRASH!
55  * > int bar = t.Get("bar", 0);
56  * > {
57  * >     LuaTable t2(l); // stack size: X+2
58  * >     t.Set("baz", t2); // t = {foo:1, baz:<t2>}
59  * > } // t2 isn't a valid name, we can now safely pop the table out.
60  * > lua_pop(l, 1); // stack size: X+1
61  * > LuaTable t2_bis = t.Sub("baz"); // stack size: X+2
62  *
63  * STL loaders:
64  *
65  * If you want to load a whole vector or map into a LuaTable, just do
66  *
67  * > std::vector v; // Or std::list, std::set, whatever as long as it has iterators
68  * > std::map m;
69  * > LuaTable t;
70  * > t.LoadMap(m.begin(), m.end());
71  * > t.LoadVector(v.begin(), v.end());
72  *
73  * Note that LoadVector doesn't overwrite the content of the table, it appends
74  * to its array-like part. Unless you have numerical index beyond its length,
75  * which you shouldn't do anyway.
76  *
77  * LoadMap happily overwrites any data if necessary.
78  *
79  * VecIter:
80  *
81  * It is possible to get STL-like iterators on the array part of the table.
82  * The use cases are typically in loops or to use in the STL algorithms or as
83  * inputs for containers.
84  *
85  * The two methods are LuaTable::Begin<Value>() and LuaTable::End<Value>()
86  *
87  * As usual, since C++ is static typed, the iterators will fail in a mixed-typed table
88  * (generating a Lua error when you attempt to access an element with the wrong type).
89  *
90  * ScopedTable:
91  *
92  * The ScopedTable class is a LuaTable derivative that comes with two constructors:
93  *   * New table constructor: ScopedTable(l);
94  *   * LuaRef contructor: ScopedTable(my_lua_ref_object);
95  * Both constructors will push a new table onto the stack, and when the C++
96  * ScopedTable objects are destroyed, this new table is removed and everything
97  * above it on the stack gets shifted down.
98  */
99 class LuaTable {
100 public:
101 	// For now, every lua_State * can only be NULL or Pi::LuaManager->GetLuaState();
LuaTable(const LuaTable & ref)102 	LuaTable(const LuaTable &ref) :
103 		m_lua(ref.m_lua),
104 		m_index(ref.m_index) {} // Copy constructor.
LuaTable(lua_State * l,int index)105 	LuaTable(lua_State *l, int index) :
106 		m_lua(l),
107 		m_index(lua_absindex(l, index)) { assert(lua_istable(m_lua, m_index)); }
LuaTable(lua_State * l)108 	explicit LuaTable(lua_State *l) :
109 		m_lua(l)
110 	{
111 		lua_newtable(m_lua);
112 		m_index = lua_gettop(l);
113 	}
114 
LuaTable(lua_State * l,int array_s,int hash_s)115 	explicit LuaTable(lua_State *l, int array_s, int hash_s) :
116 		m_lua(l)
117 	{
118 		lua_createtable(m_lua, array_s, hash_s);
119 		m_index = lua_gettop(l);
120 	}
121 
~LuaTable()122 	~LuaTable() {}
123 
124 	const LuaTable &operator=(const LuaTable &ref)
125 	{
126 		m_lua = ref.m_lua;
127 		m_index = ref.m_index;
128 		return *this;
129 	}
130 	template <class Key>
131 	LuaTable PushValueToStack(const Key &key) const;
132 	template <class Value, class Key>
133 	Value Get(const Key &key) const;
134 	template <class Key>
135 	LuaTable Sub(const Key &key) const; // Does not clean up the stack.
136 	template <class Value, class Key>
137 	Value Get(const Key &key, Value default_value) const;
138 	template <class Value, class Key>
139 	LuaTable Set(const Key &key, const Value &value) const;
140 
141 	template <class Ret, class Key, class... Args>
142 	Ret Call(const Key &key, const Args &... args) const;
143 	template <class Key, class... Args>
Call(const Key & key,const Args &...args)144 	void Call(const Key &key, const Args &... args) const
145 	{
146 		Call<bool>(key, args...);
147 	}
148 	template <class Ret1, class Ret2, class... Ret, class Key, class... Args>
149 	std::tuple<Ret1, Ret2, Ret...> Call(const Key &key, const Args &... args) const;
150 
151 	template <class Key, class... Args>
CallMethod(const Key & key,const Args &...args)152 	void CallMethod(const Key &key, const Args &... args) const
153 	{
154 		Call<bool>(key, *this, args...);
155 	}
156 	template <class Ret, class Key, class... Args>
CallMethod(const Key & key,const Args &...args)157 	Ret CallMethod(const Key &key, const Args &... args) const
158 	{
159 		return Call<Ret>(key, *this, args...);
160 	}
161 	template <class Ret1, class Ret2, class... Ret, class Key, class... Args>
CallMethod(const Key & key,const Args &...args)162 	std::tuple<Ret1, Ret2, Ret...> CallMethod(const Key &key, const Args &... args) const
163 	{
164 		return Call<Ret1, Ret2, Ret...>(key, *this, args...);
165 	}
166 
167 	template <class PairIterator>
168 	LuaTable LoadMap(PairIterator beg, PairIterator end) const;
169 	template <class ValueIterator>
170 	LuaTable LoadVector(ValueIterator beg, ValueIterator end) const;
171 
172 	template <class Key, class Value>
173 	std::map<Key, Value> GetMap() const;
174 
GetLua()175 	lua_State *GetLua() const { return m_lua; }
GetIndex()176 	int GetIndex() const { return m_index; }
Size()177 	size_t Size() const { return lua_rawlen(m_lua, m_index); }
178 
179 	/* VecIter, as in VectorIterator (only shorter to type :-)
180 	 *
181 	 * Careful, its LuaTable specialization isn't stable WRT the stack, and using its value
182 	 * will push a table onto the stack, and will only clean it up when the iterator
183 	 * gets destroyed or inc/decremented.
184 	 *
185 	 * For all other values, occasional operations on the stack may occur but it should
186 	 * not leak anything.
187 	 */
188 	template <class Value>
189 	class VecIter : public std::iterator<std::input_iterator_tag, Value> {
190 	public:
VecIter()191 		VecIter() :
192 			m_table(0),
193 			m_currentIndex(0),
194 			m_cache(),
195 			m_dirtyCache(true) {}
~VecIter()196 		~VecIter() {}
VecIter(LuaTable * t,int currentIndex)197 		VecIter(LuaTable *t, int currentIndex) :
198 			m_table(t),
199 			m_currentIndex(currentIndex),
200 			m_cache(),
201 			m_dirtyCache(true) {}
VecIter(const VecIter & copy)202 		VecIter(const VecIter &copy) :
203 			m_table(copy.m_table),
204 			m_currentIndex(copy.m_currentIndex),
205 			m_cache(),
206 			m_dirtyCache(true) {}
207 		void operator=(const VecIter &copy)
208 		{
209 			CleanCache();
210 			m_table = copy.m_table;
211 			m_currentIndex = copy.m_currentIndex;
212 		}
213 
214 		VecIter operator++()
215 		{
216 			if (m_table) {
217 				CleanCache();
218 				++m_currentIndex;
219 			}
220 			return *this;
221 		}
222 		VecIter operator++(int)
223 		{
224 			VecIter copy(*this);
225 			if (m_table) {
226 				CleanCache();
227 				++m_currentIndex;
228 			}
229 			return copy;
230 		}
231 		VecIter operator--()
232 		{
233 			if (m_table) --m_currentIndex;
234 			return *this;
235 		}
236 		VecIter operator--(int)
237 		{
238 			VecIter copy(*this);
239 			if (m_table) --m_currentIndex;
240 			return copy;
241 		}
242 
243 		bool operator==(const VecIter &other) const { return (m_table == other.m_table && m_currentIndex == other.m_currentIndex); }
244 		bool operator!=(const VecIter &other) const { return (m_table != other.m_table || m_currentIndex != other.m_currentIndex); }
245 		Value operator*()
246 		{
247 			LoadCache();
248 			return m_cache;
249 		}
250 		const Value *operator->()
251 		{
252 			LoadCache();
253 			return &m_cache;
254 		}
255 
256 	private:
CleanCache()257 		void CleanCache() { m_dirtyCache = true; }
LoadCache()258 		void LoadCache()
259 		{
260 			if (m_dirtyCache) {
261 				m_cache = m_table->Get<Value>(m_currentIndex);
262 				m_dirtyCache = false;
263 			}
264 		}
265 		LuaTable *m_table;
266 		int m_currentIndex;
267 		Value m_cache;
268 		bool m_dirtyCache;
269 	};
270 
271 	template <class Value>
Begin()272 	VecIter<Value> Begin() { return VecIter<Value>(this, 1); }
273 	template <class Value>
End()274 	VecIter<Value> End() { return VecIter<Value>(this, Size() + 1); }
275 
276 protected:
LuaTable()277 	LuaTable() :
278 		m_lua(0),
279 		m_index(0) {} //Protected : invalid tables shouldn't be out there.
280 	lua_State *m_lua;
281 	int m_index;
282 };
283 
284 class ScopedTable : public LuaTable {
285 public:
ScopedTable(const LuaTable & t)286 	ScopedTable(const LuaTable &t) :
287 		LuaTable(t)
288 	{
289 		if (m_lua) {
290 			lua_pushvalue(m_lua, m_index);
291 			m_index = lua_gettop(m_lua);
292 		}
293 	}
ScopedTable(lua_State * l)294 	ScopedTable(lua_State *l) :
295 		LuaTable(l) {}
ScopedTable(const LuaRef & r)296 	ScopedTable(const LuaRef &r) :
297 		LuaTable()
298 	{
299 		r.PushCopyToStack();
300 		m_lua = r.GetLua();
301 		m_index = lua_gettop(m_lua);
302 	}
~ScopedTable()303 	~ScopedTable()
304 	{
305 		if (m_lua && !lua_isnone(m_lua, m_index) && lua_istable(m_lua, m_index))
306 			lua_remove(m_lua, m_index);
307 	}
308 };
309 
310 template <class Key>
PushValueToStack(const Key & key)311 LuaTable LuaTable::PushValueToStack(const Key &key) const
312 {
313 	pi_lua_generic_push(m_lua, key);
314 	lua_gettable(m_lua, m_index);
315 	return *this;
316 }
317 
318 template <class Key>
Sub(const Key & key)319 LuaTable LuaTable::Sub(const Key &key) const
320 {
321 	PushValueToStack(key);
322 	return (lua_istable(m_lua, -1)) ? LuaTable(m_lua, -1) : LuaTable();
323 }
324 
325 template <class Value, class Key>
Get(const Key & key)326 Value LuaTable::Get(const Key &key) const
327 {
328 	Value return_value;
329 	PushValueToStack(key);
330 	pi_lua_generic_pull(m_lua, -1, return_value);
331 	lua_pop(m_lua, 1);
332 	return return_value;
333 }
334 
335 template <class Value, class Key>
Get(const Key & key,Value default_value)336 Value LuaTable::Get(const Key &key, Value default_value) const
337 {
338 	PushValueToStack(key);
339 	if (!(lua_isnil(m_lua, -1)))
340 		pi_lua_generic_pull(m_lua, -1, default_value);
341 	lua_pop(m_lua, 1);
342 	return default_value;
343 }
344 
345 template <class Value, class Key>
Set(const Key & key,const Value & value)346 LuaTable LuaTable::Set(const Key &key, const Value &value) const
347 {
348 	pi_lua_generic_push(m_lua, key);
349 	pi_lua_generic_push(m_lua, value);
350 	lua_settable(m_lua, m_index);
351 	return *this;
352 }
353 
354 template <class Key, class Value>
GetMap()355 std::map<Key, Value> LuaTable::GetMap() const
356 {
357 	LUA_DEBUG_START(m_lua);
358 	std::map<Key, Value> ret;
359 	lua_pushnil(m_lua);
360 	while (lua_next(m_lua, m_index)) {
361 		Key k;
362 		Value v;
363 		if (pi_lua_strict_pull(m_lua, -2, k) && pi_lua_strict_pull(m_lua, -1, v)) {
364 			ret[k] = v;
365 		} else {
366 			// XXX we should probably emit some kind of warning here somehow
367 		}
368 		lua_pop(m_lua, 1);
369 	}
370 	LUA_DEBUG_END(m_lua, 0);
371 	return ret;
372 }
373 
374 template <class PairIterator>
LoadMap(PairIterator beg,PairIterator end)375 LuaTable LuaTable::LoadMap(PairIterator beg, PairIterator end) const
376 {
377 	for (PairIterator it = beg; it != end; ++it)
378 		Set(it->first, it->second);
379 	return *this;
380 }
381 
382 template <class ValueIterator>
LoadVector(ValueIterator beg,ValueIterator end)383 LuaTable LuaTable::LoadVector(ValueIterator beg, ValueIterator end) const
384 {
385 	lua_len(m_lua, m_index);
386 	int i = lua_tointeger(m_lua, -1) + 1;
387 	lua_pop(m_lua, 1);
388 	for (ValueIterator it = beg; it != end; ++it, ++i)
389 		Set(i, *it);
390 	return *this;
391 }
392 
393 template <class Ret, class Key, class... Args>
Call(const Key & key,const Args &...args)394 Ret LuaTable::Call(const Key &key, const Args &... args) const
395 {
396 	LUA_DEBUG_START(m_lua);
397 	Ret return_value;
398 
399 	lua_checkstack(m_lua, sizeof...(args) + 3);
400 	PushValueToStack(key);
401 	pi_lua_multiple_push(m_lua, args...);
402 	pi_lua_protected_call(m_lua, sizeof...(args), 1);
403 	pi_lua_generic_pull(m_lua, -1, return_value);
404 	lua_pop(m_lua, 1);
405 	LUA_DEBUG_END(m_lua, 0);
406 	return return_value;
407 }
408 
409 template <class Ret1, class Ret2, class... Ret, class Key, class... Args>
Call(const Key & key,const Args &...args)410 std::tuple<Ret1, Ret2, Ret...> LuaTable::Call(const Key &key, const Args &... args) const
411 {
412 	LUA_DEBUG_START(m_lua);
413 	lua_checkstack(m_lua, sizeof...(args) + 3);
414 	PushValueToStack(key);
415 	pi_lua_multiple_push(m_lua, args...);
416 	pi_lua_protected_call(m_lua, sizeof...(args), sizeof...(Ret) + 2);
417 	auto return_values = pi_lua_multiple_pull<Ret1, Ret2, Ret...>(m_lua, -static_cast<int>(sizeof...(Ret)) - 2);
418 	lua_pop(m_lua, static_cast<int>(sizeof...(Ret)) + 2);
419 	LUA_DEBUG_END(m_lua, 0);
420 	return return_values;
421 }
422 
423 template <>
LoadCache()424 inline void LuaTable::VecIter<LuaTable>::LoadCache()
425 {
426 	if (m_dirtyCache) {
427 		m_cache = m_table->Sub(m_currentIndex);
428 		m_dirtyCache = false;
429 	}
430 }
431 template <>
CleanCache()432 inline void LuaTable::VecIter<LuaTable>::CleanCache()
433 {
434 	if (!m_dirtyCache && m_cache.GetLua()) {
435 		lua_remove(m_cache.GetLua(), m_cache.GetIndex());
436 	}
437 	m_dirtyCache = true;
438 }
439 
pi_lua_generic_push(lua_State * l,const LuaTable & value)440 inline void pi_lua_generic_push(lua_State *l, const LuaTable &value)
441 {
442 	lua_pushvalue(l, value.GetIndex());
443 }
444 #endif
445