1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 //#include "System/Platform/Win/win32.h"
4 
5 #include <boost/cstdint.hpp>
6 #include <string.h>
7 #include <map>
8 
9 #include "LuaUtils.h"
10 
11 #include "System/Log/ILog.h"
12 #include "System/Util.h"
13 #include "LuaConfig.h"
14 #include <boost/thread/recursive_mutex.hpp>
15 
16 #if !defined UNITSYNC && !defined DEDICATED && !defined BUILDING_AI
17 	#include "System/TimeProfiler.h"
18 #else
19 	#define SCOPED_TIMER(x)
20 #endif
21 
22 
23 static const int maxDepth = 16;
24 int LuaUtils::exportedDataSize = 0;
25 
26 
27 /******************************************************************************/
28 /******************************************************************************/
29 
30 
31 static bool CopyPushData(lua_State* dst, lua_State* src, int index, int depth, std::map<const void*, int>& alreadyCopied);
32 static bool CopyPushTable(lua_State* dst, lua_State* src, int index, int depth, std::map<const void*, int>& alreadyCopied);
33 
34 
PosAbsLuaIndex(lua_State * src,int index)35 static inline int PosAbsLuaIndex(lua_State* src, int index)
36 {
37 	if (index > 0) {
38 		return index;
39 	} else {
40 		return (lua_gettop(src) + index + 1);
41 	}
42 }
43 
44 
CopyPushData(lua_State * dst,lua_State * src,int index,int depth,std::map<const void *,int> & alreadyCopied)45 static bool CopyPushData(lua_State* dst, lua_State* src, int index, int depth, std::map<const void*, int>& alreadyCopied)
46 {
47 	const int type = lua_type(src, index);
48 	switch (type) {
49 		case LUA_TBOOLEAN: {
50 			lua_pushboolean(dst, lua_toboolean(src, index));
51 			break;
52 		}
53 		case LUA_TNUMBER: {
54 			lua_pushnumber(dst, lua_tonumber(src, index));
55 			break;
56 		}
57 		case LUA_TSTRING: {
58 			// get string (pointer)
59 			size_t len;
60 			const char* data = lua_tolstring(src, index, &len);
61 
62 			// check cache
63 			auto it = alreadyCopied.find(data);
64 			if (it != alreadyCopied.end()) {
65 				lua_rawgeti(dst, LUA_REGISTRYINDEX, it->second);
66 				break;
67 			}
68 
69 			// copy string
70 			lua_pushlstring(dst, data, len);
71 
72 			// cache it
73 			lua_pushvalue(dst, -1);
74 			const int dstRef = luaL_ref(dst, LUA_REGISTRYINDEX);
75 			alreadyCopied[data] = dstRef;
76 			break;
77 		}
78 		case LUA_TTABLE: {
79 			CopyPushTable(dst, src, index, depth, alreadyCopied);
80 			break;
81 		}
82 		default: {
83 			lua_pushnil(dst); // unhandled type
84 			return false;
85 		}
86 	}
87 	return true;
88 }
89 
90 
CopyPushTable(lua_State * dst,lua_State * src,int index,int depth,std::map<const void *,int> & alreadyCopied)91 static bool CopyPushTable(lua_State* dst, lua_State* src, int index, int depth, std::map<const void*, int>& alreadyCopied)
92 {
93 	const int table = PosAbsLuaIndex(src, index);
94 
95 	// check cache
96 	const void* p = lua_topointer(src, table);
97 	auto it = alreadyCopied.find(p);
98 	if (it != alreadyCopied.end()) {
99 		lua_rawgeti(dst, LUA_REGISTRYINDEX, it->second);
100 		return true;
101 	}
102 
103 	// check table depth
104 	if (depth++ > maxDepth) {
105 		LOG("CopyTable: reached max table depth '%i'", depth);
106 		lua_pushnil(dst); // push something
107 		return false;
108 	}
109 
110 	// create new table
111 	const auto array_len = lua_objlen(src, table);
112 	lua_createtable(dst, array_len, 5);
113 
114 	// cache it
115 	lua_pushvalue(dst, -1);
116 	const int dstRef = luaL_ref(dst, LUA_REGISTRYINDEX);
117 	alreadyCopied[p] = dstRef;
118 
119 	// copy table entries
120 	for (lua_pushnil(src); lua_next(src, table) != 0; lua_pop(src, 1)) {
121 		CopyPushData(dst, src, -2, depth, alreadyCopied); // copy the key
122 		CopyPushData(dst, src, -1, depth, alreadyCopied); // copy the value
123 		lua_rawset(dst, -3);
124 	}
125 
126 	return true;
127 }
128 
129 
CopyData(lua_State * dst,lua_State * src,int count)130 int LuaUtils::CopyData(lua_State* dst, lua_State* src, int count)
131 {
132 	SCOPED_TIMER("::CopyData");
133 
134 	const int srcTop = lua_gettop(src);
135 	const int dstTop = lua_gettop(dst);
136 	if (srcTop < count) {
137 		LOG_L(L_ERROR, "LuaUtils::CopyData: tried to copy more data than there is");
138 		return 0;
139 	}
140 	lua_checkstack(dst, count + 3); // +3 needed for table copying
141 	lua_lock(src); // we need to be sure tables aren't changed while we iterate them
142 
143 	// hold a map of all already copied tables in the lua's registry table
144 	// needed for recursive tables, i.e. "local t = {}; t[t] = t"
145 	std::map<const void*, int> alreadyCopied;
146 
147 	const int startIndex = (srcTop - count + 1);
148 	const int endIndex   = srcTop;
149 	for (int i = startIndex; i <= endIndex; i++) {
150 		CopyPushData(dst, src, i, 0, alreadyCopied);
151 	}
152 
153 	// clear map
154 	for (auto& pair: alreadyCopied) {
155 		luaL_unref(dst, LUA_REGISTRYINDEX, pair.second);
156 	}
157 
158 	const int curSrcTop = lua_gettop(src);
159 	assert(srcTop == curSrcTop);
160 	lua_settop(dst, dstTop + count);
161 	lua_unlock(src);
162 	return count;
163 }
164 
165 /******************************************************************************/
166 /******************************************************************************/
167 
168 static bool BackupData(LuaUtils::DataDump &d, lua_State* src, int index, int depth);
169 static bool RestoreData(const LuaUtils::DataDump &d, lua_State* dst, int depth);
170 static bool BackupTable(LuaUtils::DataDump &d, lua_State* src, int index, int depth);
171 static bool RestoreTable(const LuaUtils::DataDump &d, lua_State* dst, int depth);
172 
173 
BackupData(LuaUtils::DataDump & d,lua_State * src,int index,int depth)174 static bool BackupData(LuaUtils::DataDump &d, lua_State* src, int index, int depth) {
175 	++LuaUtils::exportedDataSize;
176 	const int type = lua_type(src, index);
177 	d.type = type;
178 	switch (type) {
179 		case LUA_TBOOLEAN: {
180 			d.bol = lua_toboolean(src, index);
181 			break;
182 		}
183 		case LUA_TNUMBER: {
184 			d.num = lua_tonumber(src, index);
185 			break;
186 		}
187 		case LUA_TSTRING: {
188 			size_t len = 0;
189 			const char* data = lua_tolstring(src, index, &len);
190 			if (len > 0) {
191 				d.str.resize(len);
192 				memcpy(&d.str[0], data, len);
193 			}
194 			break;
195 		}
196 		case LUA_TTABLE: {
197 			if(!BackupTable(d, src, index, depth))
198 				d.type = LUA_TNIL;
199 			break;
200 		}
201 		default: {
202 			d.type = LUA_TNIL;
203 			break;
204 		}
205 	}
206 	return true;
207 }
208 
RestoreData(const LuaUtils::DataDump & d,lua_State * dst,int depth)209 static bool RestoreData(const LuaUtils::DataDump &d, lua_State* dst, int depth) {
210 	--LuaUtils::exportedDataSize;
211 	const int type = d.type;
212 	switch (type) {
213 		case LUA_TBOOLEAN: {
214 			lua_pushboolean(dst, d.bol);
215 			break;
216 		}
217 		case LUA_TNUMBER: {
218 			lua_pushnumber(dst, d.num);
219 			break;
220 		}
221 		case LUA_TSTRING: {
222 			lua_pushlstring(dst, d.str.c_str(), d.str.size());
223 			break;
224 		}
225 		case LUA_TTABLE: {
226 			RestoreTable(d, dst, depth);
227 			break;
228 		}
229 		default: {
230 			lua_pushnil(dst);
231 			break;
232 		}
233 	}
234 	return true;
235 }
236 
BackupTable(LuaUtils::DataDump & d,lua_State * src,int index,int depth)237 static bool BackupTable(LuaUtils::DataDump &d, lua_State* src, int index, int depth) {
238 	if (depth++ > maxDepth)
239 		return false;
240 
241 	const int table = PosAbsLuaIndex(src, index);
242 	for (lua_pushnil(src); lua_next(src, table) != 0; lua_pop(src, 1)) {
243 		LuaUtils::DataDump dk, dv;
244 		BackupData(dk, src, -2, depth);
245 		BackupData(dv, src, -1, depth);
246 		d.table.push_back(std::pair<LuaUtils::DataDump, LuaUtils::DataDump>(dk ,dv));
247 	}
248 
249 	return true;
250 }
251 
RestoreTable(const LuaUtils::DataDump & d,lua_State * dst,int depth)252 static bool RestoreTable(const LuaUtils::DataDump &d, lua_State* dst, int depth) {
253 	if (depth++ > maxDepth) {
254 		lua_pushnil(dst);
255 		return false;
256 	}
257 
258 	lua_newtable(dst);
259 	for (std::vector<std::pair<LuaUtils::DataDump, LuaUtils::DataDump> >::const_iterator i = d.table.begin(); i != d.table.end(); ++i) {
260 		RestoreData((*i).first, dst, depth);
261 		RestoreData((*i).second, dst, depth);
262 		lua_rawset(dst, -3);
263 	}
264 
265 	return true;
266 }
267 
268 
Backup(std::vector<LuaUtils::DataDump> & backup,lua_State * src,int count)269 int LuaUtils::Backup(std::vector<LuaUtils::DataDump> &backup, lua_State* src, int count) {
270 	const int srcTop = lua_gettop(src);
271 	if (srcTop < count)
272 		return 0;
273 
274 	const int startIndex = (srcTop - count + 1);
275 	const int endIndex   = srcTop;
276 	for (int i = startIndex; i <= endIndex; i++) {
277 		backup.push_back(DataDump());
278 		BackupData(backup.back(), src, i, 0);
279 	}
280 
281 	return count;
282 }
283 
284 
Restore(const std::vector<LuaUtils::DataDump> & backup,lua_State * dst)285 int LuaUtils::Restore(const std::vector<LuaUtils::DataDump> &backup, lua_State* dst) {
286 	const int dstTop = lua_gettop(dst);
287 	int count = backup.size();
288 	lua_checkstack(dst, count + 3);
289 
290 	for (std::vector<DataDump>::const_iterator i = backup.begin(); i != backup.end(); ++i) {
291 		RestoreData(*i, dst, 0);
292 	}
293 	lua_settop(dst, dstTop + count);
294 
295 	return count;
296 }
297 
298 
299 /******************************************************************************/
300 /******************************************************************************/
301 
PushCurrentFunc(lua_State * L,const char * caller)302 static void PushCurrentFunc(lua_State* L, const char* caller)
303 {
304 	// get the current function
305 	lua_Debug ar;
306 	if (lua_getstack(L, 1, &ar) == 0) {
307 		luaL_error(L, "%s() lua_getstack() error", caller);
308 	}
309 	if (lua_getinfo(L, "f", &ar) == 0) {
310 		luaL_error(L, "%s() lua_getinfo() error", caller);
311 	}
312 	if (!lua_isfunction(L, -1)) {
313 		luaL_error(L, "%s() invalid current function", caller);
314 	}
315 }
316 
317 
PushFunctionEnv(lua_State * L,const char * caller,int funcIndex)318 static void PushFunctionEnv(lua_State* L, const char* caller, int funcIndex)
319 {
320 	lua_getfenv(L, funcIndex);
321 	lua_pushliteral(L, "__fenv");
322 	lua_rawget(L, -2);
323 	if (lua_isnil(L, -1)) {
324 		lua_pop(L, 1); // there is no fenv proxy
325 	} else {
326 		lua_remove(L, -2); // remove the orig table, leave the proxy
327 	}
328 
329 	if (!lua_istable(L, -1)) {
330 		luaL_error(L, "%s() invalid fenv", caller);
331 	}
332 }
333 
334 
PushCurrentFuncEnv(lua_State * L,const char * caller)335 void LuaUtils::PushCurrentFuncEnv(lua_State* L, const char* caller)
336 {
337 	PushCurrentFunc(L, caller);
338 	PushFunctionEnv(L, caller, -1);
339 	lua_remove(L, -2); // remove the function
340 }
341 
342 /******************************************************************************/
343 /******************************************************************************/
344 
345 
LowerKeysCheck(lua_State * L,int table,int alreadyCheckTable)346 static bool LowerKeysCheck(lua_State* L, int table, int alreadyCheckTable)
347 {
348 	bool checked = true;
349 	lua_pushvalue(L, table);
350 	lua_rawget(L, alreadyCheckTable);
351 	if (lua_isnil(L, -1)) {
352 		checked = false;
353 		lua_pushvalue(L, table);
354 		lua_pushboolean(L, true);
355 		lua_rawset(L, alreadyCheckTable);
356 	}
357 	lua_pop(L, 1);
358 	return checked;
359 }
360 
361 
LowerKeysReal(lua_State * L,int alreadyCheckTable)362 static bool LowerKeysReal(lua_State* L, int alreadyCheckTable)
363 {
364 	luaL_checkstack(L, 8, __FUNCTION__);
365 	const int table = lua_gettop(L);
366 	if (LowerKeysCheck(L, table, alreadyCheckTable)) {
367 		return true;
368 	}
369 
370 	// a new table for changed values
371 	const int changed = table + 1;
372 	lua_newtable(L);
373 
374 	for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) {
375 		if (lua_istable(L, -1)) {
376 			LowerKeysReal(L, alreadyCheckTable);
377 		}
378 		if (lua_israwstring(L, -2)) {
379 			const string rawKey = lua_tostring(L, -2);
380 			const string lowerKey = StringToLower(rawKey);
381 			if (rawKey != lowerKey) {
382 				// removed the mixed case entry
383 				lua_pushvalue(L, -2); // the key
384 				lua_pushnil(L);
385 				lua_rawset(L, table);
386 				// does the lower case key alread exist in the table?
387 				lua_pushsstring(L, lowerKey);
388 				lua_rawget(L, table);
389 				if (lua_isnil(L, -1)) {
390 					// lower case does not exist, add it to the changed table
391 					lua_pushsstring(L, lowerKey);
392 					lua_pushvalue(L, -3); // the value
393 					lua_rawset(L, changed);
394 				}
395 				lua_pop(L, 1);
396 			}
397 		}
398 	}
399 
400 	// copy the changed values into the table
401 	for (lua_pushnil(L); lua_next(L, changed) != 0; lua_pop(L, 1)) {
402 		lua_pushvalue(L, -2); // copy the key to the top
403 		lua_pushvalue(L, -2); // copy the value to the top
404 		lua_rawset(L, table);
405 	}
406 
407 	lua_pop(L, 1); // pop the changed table
408 	return true;
409 }
410 
411 
LowerKeys(lua_State * L,int table)412 bool LuaUtils::LowerKeys(lua_State* L, int table)
413 {
414 	if (!lua_istable(L, table)) {
415 		return false;
416 	}
417 
418 	// table of processed tables
419 	luaL_checkstack(L, 2, __FUNCTION__);
420 	lua_newtable(L);
421 	const int checkedTableIdx = lua_gettop(L);
422 
423 	lua_pushvalue(L, table); // push the table onto the top of the stack
424 	LowerKeysReal(L, checkedTableIdx);
425 
426 	lua_pop(L, 2); // the lowered table, and the check table
427 	return true;
428 }
429 
430 
CheckForNaNsReal(lua_State * L,const std::string & path)431 static bool CheckForNaNsReal(lua_State* L, const std::string& path)
432 {
433 	luaL_checkstack(L, 3, __FUNCTION__);
434 	const int table = lua_gettop(L);
435 	bool foundNaNs = false;
436 
437 	for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) {
438 		if (lua_istable(L, -1)) {
439 			// We can't work on -2 directly cause lua_tostring would replace the value in -2,
440 			// so we need to make a copy and convert that to a string.
441 			lua_pushvalue(L, -2);
442 			const char* key = lua_tostring(L, -1);
443 			const std::string subpath = path + key + ".";
444 			lua_pop(L, 1);
445 			foundNaNs |= CheckForNaNsReal(L, subpath);
446 		} else
447 		if (lua_isnumber(L, -1)) {
448 			// Check for NaN
449 			const float value = lua_tonumber(L, -1);
450 			if (math::isinf(value) || math::isnan(value)) {
451 				// We can't work on -2 directly cause lua_tostring would replace the value in -2,
452 				// so we need to make a copy and convert that to a string.
453 				lua_pushvalue(L, -2);
454 				const char* key = lua_tostring(L, -1);
455 				LOG_L(L_WARNING, "%s%s: Got Invalid NaN/Inf!", path.c_str(), key);
456 				lua_pop(L, 1);
457 				foundNaNs = true;
458 			}
459 		}
460 	}
461 
462 	return foundNaNs;
463 }
464 
465 
CheckTableForNaNs(lua_State * L,int table,const std::string & name)466 bool LuaUtils::CheckTableForNaNs(lua_State* L, int table, const std::string& name)
467 {
468 	if (!lua_istable(L, table)) {
469 		return false;
470 	}
471 
472 	luaL_checkstack(L, 2, __FUNCTION__);
473 
474 	// table of processed tables
475 	lua_newtable(L);
476 	// push the table onto the top of the stack
477 	lua_pushvalue(L, table);
478 
479 	const bool foundNaNs = CheckForNaNsReal(L, name + ": ");
480 
481 	lua_pop(L, 2); // the lowered table, and the check table
482 
483 	return foundNaNs;
484 }
485 
486 
487 /******************************************************************************/
488 /******************************************************************************/
489 
490 // copied from lua/src/lauxlib.cpp:luaL_checkudata()
GetUserData(lua_State * L,int index,const string & type)491 void* LuaUtils::GetUserData(lua_State* L, int index, const string& type)
492 {
493 	const char* tname = type.c_str();
494 	void *p = lua_touserdata(L, index);
495 	if (p != NULL) {                               // value is a userdata?
496 		if (lua_getmetatable(L, index)) {            // does it have a metatable?
497 			lua_getfield(L, LUA_REGISTRYINDEX, tname); // get correct metatable
498 			if (lua_rawequal(L, -1, -2)) {             // the correct mt?
499 				lua_pop(L, 2);                           // remove both metatables
500 				return p;
501 			}
502 		}
503 	}
504 	return NULL;
505 }
506 
507 
508 /******************************************************************************/
509 /******************************************************************************/
510 
PrintStack(lua_State * L)511 void LuaUtils::PrintStack(lua_State* L)
512 {
513 	const int top = lua_gettop(L);
514 	for (int i = 1; i <= top; i++) {
515 		LOG_L(L_ERROR, "  %i: type = %s (%p)", i, luaL_typename(L, i), lua_topointer(L, i));
516 		const int type = lua_type(L, i);
517 		switch(type) {
518 			case LUA_TSTRING:
519 				LOG_L(L_ERROR, "\t\t%s", lua_tostring(L, i));
520 				break;
521 			case LUA_TNUMBER:
522 				LOG_L(L_ERROR, "\t\t%f", lua_tonumber(L, i));
523 				break;
524 			case LUA_TBOOLEAN:
525 				LOG_L(L_ERROR, "\t\t%s", lua_toboolean(L, i) ? "true" : "false");
526 				break;
527 			default: {}
528 		}
529 	}
530 }
531 
532 
533 /******************************************************************************/
534 /******************************************************************************/
535 
536 // from LuaShaders.cpp
ParseIntArray(lua_State * L,int index,int * array,int size)537 int LuaUtils::ParseIntArray(lua_State* L, int index, int* array, int size)
538 {
539 	if (!lua_istable(L, -1)) {
540 		return -1;
541 	}
542 	const int table = lua_gettop(L);
543 	for (int i = 0; i < size; i++) {
544 		lua_rawgeti(L, table, (i + 1));
545 		if (lua_isnumber(L, -1)) {
546 			array[i] = lua_toint(L, -1);
547 			lua_pop(L, 1);
548 		} else {
549 			lua_pop(L, 1);
550 			return i;
551 		}
552 	}
553 	return size;
554 }
555 
556 
ParseFloatArray(lua_State * L,int index,float * array,int size)557 int LuaUtils::ParseFloatArray(lua_State* L, int index, float* array, int size)
558 {
559 	if (!lua_istable(L, index)) {
560 		return -1;
561 	}
562 	const int table = (index > 0) ? index : (lua_gettop(L) + index + 1);
563 	for (int i = 0; i < size; i++) {
564 		lua_rawgeti(L, table, (i + 1));
565 		if (lua_isnumber(L, -1)) {
566 			array[i] = lua_tofloat(L, -1);
567 			lua_pop(L, 1);
568 		} else {
569 			lua_pop(L, 1);
570 			return i;
571 		}
572 	}
573 	return size;
574 }
575 
576 
577 
PushCommandParamsTable(lua_State * L,const Command & cmd,bool subtable)578 void LuaUtils::PushCommandParamsTable(lua_State* L, const Command& cmd, bool subtable)
579 {
580 	if (subtable) {
581 		HSTR_PUSH(L, "params");
582 	}
583 
584 	lua_createtable(L, cmd.params.size(), 0);
585 
586 	for (unsigned int p = 0; p < cmd.params.size(); p++) {
587 		lua_pushnumber(L, cmd.params[p]);
588 		lua_rawseti(L, -2, p + 1);
589 	}
590 
591 	if (subtable) {
592 		lua_rawset(L, -3);
593 	}
594 }
595 
PushCommandOptionsTable(lua_State * L,const Command & cmd,bool subtable)596 void LuaUtils::PushCommandOptionsTable(lua_State* L, const Command& cmd, bool subtable)
597 {
598 	if (subtable) {
599 		HSTR_PUSH(L, "options");
600 	}
601 
602 	lua_createtable(L, 0, 7);
603 	HSTR_PUSH_NUMBER(L, "coded", cmd.options);
604 	HSTR_PUSH_BOOL(L, "alt",      !!(cmd.options & ALT_KEY        ));
605 	HSTR_PUSH_BOOL(L, "ctrl",     !!(cmd.options & CONTROL_KEY    ));
606 	HSTR_PUSH_BOOL(L, "shift",    !!(cmd.options & SHIFT_KEY      ));
607 	HSTR_PUSH_BOOL(L, "right",    !!(cmd.options & RIGHT_MOUSE_KEY));
608 	HSTR_PUSH_BOOL(L, "meta",     !!(cmd.options & META_KEY       ));
609 	HSTR_PUSH_BOOL(L, "internal", !!(cmd.options & INTERNAL_ORDER ));
610 
611 	if (subtable) {
612 		lua_rawset(L, -3);
613 	}
614 }
615 
ParseCommandOptions(lua_State * L,Command & cmd,const char * caller,const int idx)616 void LuaUtils::ParseCommandOptions(
617 	lua_State* L,
618 	Command& cmd,
619 	const char* caller,
620 	const int idx
621 ) {
622 	if (lua_isnumber(L, idx)) {
623 		cmd.options = (unsigned char)lua_tonumber(L, idx);
624 	} else if (lua_istable(L, idx)) {
625 		for (lua_pushnil(L); lua_next(L, idx) != 0; lua_pop(L, 1)) {
626 			// "key" = value (table format of CommandNotify)
627 			if (lua_israwstring(L, -2)) {
628 				const std::string key = lua_tostring(L, -2);
629 
630 				// we do not care about the "coded" key (not a boolean value)
631 				if (!lua_isboolean(L, -1))
632 					continue;
633 
634 				const bool value = lua_toboolean(L, -1);
635 
636 				if (key == "right") {
637 					cmd.options |= (RIGHT_MOUSE_KEY * value);
638 				} else if (key == "alt") {
639 					cmd.options |= (ALT_KEY * value);
640 				} else if (key == "ctrl") {
641 					cmd.options |= (CONTROL_KEY * value);
642 				} else if (key == "shift") {
643 					cmd.options |= (SHIFT_KEY * value);
644 				} else if (key == "meta") {
645 					cmd.options |= (META_KEY * value);
646 				}
647 
648 				continue;
649 			}
650 
651 			// [idx] = "value", avoid 'n'
652 			if (lua_israwnumber(L, -2)) {
653 				//const int idx = lua_tonumber(L, -2);
654 
655 				if (!lua_isstring(L, -1))
656 					continue;
657 
658 				const std::string value = lua_tostring(L, -1);
659 
660 				if (value == "right") {
661 					cmd.options |= RIGHT_MOUSE_KEY;
662 				} else if (value == "alt") {
663 					cmd.options |= ALT_KEY;
664 				} else if (value == "ctrl") {
665 					cmd.options |= CONTROL_KEY;
666 				} else if (value == "shift") {
667 					cmd.options |= SHIFT_KEY;
668 				} else if (value == "meta") {
669 					cmd.options |= META_KEY;
670 				}
671 			}
672 		}
673 	} else {
674 		luaL_error(L, "%s(): bad options-argument type", caller);
675 	}
676 }
677 
678 
ParseCommand(lua_State * L,const char * caller,int idIndex)679 Command LuaUtils::ParseCommand(lua_State* L, const char* caller, int idIndex)
680 {
681 	// cmdID
682 	if (!lua_isnumber(L, idIndex)) {
683 		luaL_error(L, "%s(): bad command ID", caller);
684 	}
685 
686 	Command cmd(lua_toint(L, idIndex));
687 
688 	// params
689 	const int paramTableIdx = (idIndex + 1);
690 
691 	if (!lua_istable(L, paramTableIdx)) {
692 		luaL_error(L, "%s(): bad param table", caller);
693 	}
694 
695 	for (lua_pushnil(L); lua_next(L, paramTableIdx) != 0; lua_pop(L, 1)) {
696 		if (lua_israwnumber(L, -2)) { // avoid 'n'
697 			if (!lua_isnumber(L, -1)) {
698 				luaL_error(L, "%s(): expected <number idx=%d, number value> in params-table", caller, lua_tonumber(L, -2));
699 			}
700 
701 			cmd.PushParam(lua_tofloat(L, -1));
702 		}
703 	}
704 
705 	// options
706 	ParseCommandOptions(L, cmd, caller, (idIndex + 2));
707 
708 	// XXX should do some sanity checking?
709 	return cmd;
710 }
711 
712 
ParseCommandTable(lua_State * L,const char * caller,int table)713 Command LuaUtils::ParseCommandTable(lua_State* L, const char* caller, int table)
714 {
715 	// cmdID
716 	lua_rawgeti(L, table, 1);
717 	if (!lua_isnumber(L, -1)) {
718 		luaL_error(L, "%s(): bad command ID", caller);
719 	}
720 	const int id = lua_toint(L, -1);
721 	Command cmd(id);
722 	lua_pop(L, 1);
723 
724 	// params
725 	lua_rawgeti(L, table, 2);
726 	if (!lua_istable(L, -1)) {
727 		luaL_error(L, "%s(): bad param table", caller);
728 	}
729 	const int paramTable = lua_gettop(L);
730 	for (lua_pushnil(L); lua_next(L, paramTable) != 0; lua_pop(L, 1)) {
731 		if (lua_israwnumber(L, -2)) { // avoid 'n'
732 			if (!lua_isnumber(L, -1)) {
733 				luaL_error(L, "%s(): bad param table entry", caller);
734 			}
735 			const float value = lua_tofloat(L, -1);
736 			cmd.PushParam(value);
737 		}
738 	}
739 	lua_pop(L, 1);
740 
741 	// options
742 	lua_rawgeti(L, table, 3);
743 	ParseCommandOptions(L, cmd, caller, lua_gettop(L));
744 	lua_pop(L, 1);
745 
746 	// XXX should do some sanity checking?
747 
748 	return cmd;
749 }
750 
751 
ParseCommandArray(lua_State * L,const char * caller,int table,vector<Command> & commands)752 void LuaUtils::ParseCommandArray(lua_State* L, const char* caller,
753                                  int table, vector<Command>& commands)
754 {
755 	if (!lua_istable(L, table)) {
756 		luaL_error(L, "%s(): error parsing command array", caller);
757 	}
758 	for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) {
759 		if (!lua_istable(L, -1)) {
760 			continue;
761 		}
762 		Command cmd = ParseCommandTable(L, caller, lua_gettop(L));
763 		commands.push_back(cmd);
764 	}
765 }
766 
767 
ParseFacing(lua_State * L,const char * caller,int index)768 int LuaUtils::ParseFacing(lua_State* L, const char* caller, int index)
769 {
770 	if (lua_israwnumber(L, index)) {
771 		return std::max(0, std::min(3, lua_toint(L, index)));
772 	}
773 	else if (lua_israwstring(L, index)) {
774 		const string dir = StringToLower(lua_tostring(L, index));
775 		if (dir == "s") { return 0; }
776 		if (dir == "e") { return 1; }
777 		if (dir == "n") { return 2; }
778 		if (dir == "w") { return 3; }
779 		if (dir == "south") { return 0; }
780 		if (dir == "east")  { return 1; }
781 		if (dir == "north") { return 2; }
782 		if (dir == "west")  { return 3; }
783 		luaL_error(L, "%s(): bad facing string", caller);
784 	}
785 	luaL_error(L, "%s(): bad facing parameter", caller);
786 	return 0;
787 }
788 
789 
790 /******************************************************************************/
791 /******************************************************************************/
792 
793 
Next(const ParamMap & paramMap,lua_State * L)794 int LuaUtils::Next(const ParamMap& paramMap, lua_State* L)
795 {
796 	luaL_checktype(L, 1, LUA_TTABLE);
797 	lua_settop(L, 2); // create a 2nd argument if there isn't one
798 
799 	// internal parameters first
800 	if (lua_isnoneornil(L, 2)) {
801 		const string& nextKey = paramMap.begin()->first;
802 		lua_pushsstring(L, nextKey); // push the key
803 		lua_pushvalue(L, 3);         // copy the key
804 		lua_gettable(L, 1);          // get the value
805 		return 2;
806 	}
807 
808 	// all internal parameters use strings as keys
809 	if (lua_isstring(L, 2)) {
810 		const char* key = lua_tostring(L, 2);
811 		ParamMap::const_iterator it = paramMap.find(key);
812 		if ((it != paramMap.end()) && (it->second.type != READONLY_TYPE)) {
813 			// last key was an internal parameter
814 			++it;
815 			while ((it != paramMap.end()) && (it->second.type == READONLY_TYPE)) {
816 				++it; // skip read-only parameters
817 			}
818 			if ((it != paramMap.end()) && (it->second.type != READONLY_TYPE)) {
819 				// next key is an internal parameter
820 				const string& nextKey = it->first;
821 				lua_pushsstring(L, nextKey); // push the key
822 				lua_pushvalue(L, 3);         // copy the key
823 				lua_gettable(L, 1);          // get the value (proxied)
824 				return 2;
825 			}
826 			// start the user parameters,
827 			// remove the internal key and push a nil
828 			lua_settop(L, 1);
829 			lua_pushnil(L);
830 		}
831 	}
832 
833 	// user parameter
834 	if (lua_next(L, 1)) {
835 		return 2;
836 	}
837 
838 	// end of the line
839 	lua_pushnil(L);
840 	return 1;
841 }
842 
843 
844 /******************************************************************************/
845 /******************************************************************************/
846 
getprintf_msg(lua_State * L,int index)847 static std::string getprintf_msg(lua_State* L, int index)
848 {
849 	// copied from lua/src/lib/lbaselib.c
850 	string msg = "";
851 	const int args = lua_gettop(L); // number of arguments
852 
853 	lua_getglobal(L, "tostring");
854 
855 	for (int i = index; i <= args; i++) {
856 		const char* s;
857 		lua_pushvalue(L, -1);     // function to be called
858 		lua_pushvalue(L, i);      // value to print
859 		lua_call(L, 1, 1);
860 		s = lua_tostring(L, -1);  // get result
861 		if (i > index) {
862 			msg += ", ";
863 		}
864 		msg += s;
865 		lua_pop(L, 1);            // pop result
866 	}
867 
868 	if ((args != index) || !lua_istable(L, index)) {
869 		return msg;
870 	}
871 
872 	// print solo tables (array style)
873 	msg = "TABLE: ";
874 	bool first = true;
875 	for (lua_pushnil(L); lua_next(L, index) != 0; lua_pop(L, 1)) {
876 		if (lua_israwnumber(L, -2)) {  // only numeric keys
877 			const char *s;
878 			lua_pushvalue(L, -3);    // function to be called
879 			lua_pushvalue(L, -2);    // value to print
880 			lua_call(L, 1, 1);
881 			s = lua_tostring(L, -1);  // get result
882 			if (!first) {
883 				msg += ", ";
884 			}
885 			msg += s;
886 			first = false;
887 			lua_pop(L, 1);            // pop result
888 		}
889 	}
890 
891 	return msg;
892 }
893 
894 
Echo(lua_State * L)895 int LuaUtils::Echo(lua_State* L)
896 {
897 	const std::string msg = getprintf_msg(L, 1);
898 	LOG("%s", msg.c_str());
899 	return 0;
900 }
901 
902 
PushLogEntries(lua_State * L)903 bool LuaUtils::PushLogEntries(lua_State* L)
904 {
905 #define PUSH_LOG_LEVEL(cmd) LuaPushNamedNumber(L, #cmd, LOG_LEVEL_ ## cmd)
906 	PUSH_LOG_LEVEL(DEBUG);
907 	PUSH_LOG_LEVEL(INFO);
908 	PUSH_LOG_LEVEL(NOTICE);
909 	PUSH_LOG_LEVEL(WARNING);
910 	PUSH_LOG_LEVEL(ERROR);
911 	PUSH_LOG_LEVEL(FATAL);
912 	return true;
913 }
914 
915 
916 /*-
917 	Logs a msg to the logfile / console
918 	@param loglevel loglevel that will be used for the message
919 	@param msg string to be logged
920 	@fn Spring.Log(string logsection, int loglevel, ...)
921 	@fn Spring.Log(string logsection, string loglevel, ...)
922 */
Log(lua_State * L)923 int LuaUtils::Log(lua_State* L)
924 {
925 	const int args = lua_gettop(L); // number of arguments
926 	if (args < 3)
927 		return luaL_error(L, "Incorrect arguments to Spring.Log(logsection, loglevel, ...)");
928 
929 	const char* section = luaL_checkstring(L, 1);
930 
931 	int loglevel = 0;
932 	if (lua_israwnumber(L, 2)) {
933 		loglevel = lua_tonumber(L, 2);
934 	}
935 	else if (lua_israwstring(L, 2)) {
936 		std::string loglvlstr = lua_tostring(L, 2);
937 		StringToLowerInPlace(loglvlstr);
938 		if (loglvlstr == "debug") {
939 			loglevel = LOG_LEVEL_DEBUG;
940 		}
941 		else if (loglvlstr == "info") {
942 			loglevel = LOG_LEVEL_INFO;
943 		}
944 		else if (loglvlstr == "notice") {
945 			loglevel = LOG_LEVEL_INFO;
946 		}
947 		else if (loglvlstr == "warning") {
948 			loglevel = LOG_LEVEL_WARNING;
949 		}
950 		else if (loglvlstr == "error") {
951 			loglevel = LOG_LEVEL_ERROR;
952 		}
953 		else if (loglvlstr == "fatal") {
954 			loglevel = LOG_LEVEL_FATAL;
955 		}
956 		else {
957 			return luaL_error(L, "Incorrect arguments to Spring.Log(logsection, loglevel, ...)");
958 		}
959 	}
960 	else {
961 		return luaL_error(L, "Incorrect arguments to Spring.Log(logsection, loglevel, ...)");
962 	}
963 
964 	const std::string msg = getprintf_msg(L, 3);
965 	LOG_SI(section, loglevel, "%s", msg.c_str());
966 	return 0;
967 }
968 
969 /******************************************************************************/
970 /******************************************************************************/
971 
ScopedStackChecker(lua_State * L,int _returnVars)972 LuaUtils::ScopedStackChecker::ScopedStackChecker(lua_State* L, int _returnVars)
973 	: luaState(L)
974 	, prevTop(lua_gettop(luaState))
975 	, returnVars(_returnVars)
976 {
977 }
978 
~ScopedStackChecker()979 LuaUtils::ScopedStackChecker::~ScopedStackChecker() {
980 	const int curTop = lua_gettop(luaState); // use var so you can print it in gdb
981 	assert(curTop == prevTop + returnVars);
982 }
983 
984 /******************************************************************************/
985 /******************************************************************************/
986 
987 #define DEBUG_TABLE "debug"
988 #define DEBUG_FUNC "traceback"
989 
990 /// this function always leaves one item on the stack
991 /// and returns its index if valid and zero otherwise
PushDebugTraceback(lua_State * L)992 int LuaUtils::PushDebugTraceback(lua_State* L)
993 {
994 	lua_getglobal(L, DEBUG_TABLE);
995 
996 	if (lua_istable(L, -1)) {
997 		lua_getfield(L, -1, DEBUG_FUNC);
998 		lua_remove(L, -2); // remove DEBUG_TABLE from stack
999 
1000 		if (!lua_isfunction(L, -1)) {
1001 			return 0; // leave a stub on stack
1002 		}
1003 	} else {
1004 		lua_pop(L, 1);
1005 		static const LuaHashString traceback("traceback");
1006 		if (!traceback.GetRegistryFunc(L)) {
1007 			lua_pushnil(L); // leave a stub on stack
1008 			return 0;
1009 		}
1010 	}
1011 
1012 	return lua_gettop(L);
1013 }
1014 
1015 
1016 
ScopedDebugTraceBack(lua_State * _L)1017 LuaUtils::ScopedDebugTraceBack::ScopedDebugTraceBack(lua_State* _L)
1018 	: L(_L)
1019 	, errFuncIdx(PushDebugTraceback(_L))
1020 {
1021 	assert(errFuncIdx >= 0);
1022 }
1023 
~ScopedDebugTraceBack()1024 LuaUtils::ScopedDebugTraceBack::~ScopedDebugTraceBack() {
1025 	// make sure we are at same position on the stack
1026 	const int curTop = lua_gettop(L);
1027 	assert(errFuncIdx == 0 || curTop == errFuncIdx);
1028 
1029 	lua_pop(L, 1);
1030 }
1031 
1032 /******************************************************************************/
1033 /******************************************************************************/
1034 
PushStringVector(lua_State * L,const vector<string> & vec)1035 void LuaUtils::PushStringVector(lua_State* L, const vector<string>& vec)
1036 {
1037 	lua_createtable(L, vec.size(), 0);
1038 	for (size_t i = 0; i < vec.size(); i++) {
1039 		lua_pushsstring(L, vec[i]);
1040 		lua_rawseti(L, -2, (int)(i + 1));
1041 	}
1042 }
1043 
1044 /******************************************************************************/
1045 /******************************************************************************/
1046 
PushCommandDesc(lua_State * L,const CommandDescription & cd)1047 void LuaUtils::PushCommandDesc(lua_State* L, const CommandDescription& cd)
1048 {
1049 	const int numParams = cd.params.size();
1050 	const int numTblKeys = 12;
1051 
1052 	lua_checkstack(L, 1 + 1 + 1 + 1);
1053 	lua_createtable(L, 0, numTblKeys);
1054 
1055 	HSTR_PUSH_NUMBER(L, "id",          cd.id);
1056 	HSTR_PUSH_NUMBER(L, "type",        cd.type);
1057 	HSTR_PUSH_STRING(L, "name",        cd.name);
1058 	HSTR_PUSH_STRING(L, "action",      cd.action);
1059 	HSTR_PUSH_STRING(L, "tooltip",     cd.tooltip);
1060 	HSTR_PUSH_STRING(L, "texture",     cd.iconname);
1061 	HSTR_PUSH_STRING(L, "cursor",      cd.mouseicon);
1062 	HSTR_PUSH_BOOL(L,   "hidden",      cd.hidden);
1063 	HSTR_PUSH_BOOL(L,   "disabled",    cd.disabled);
1064 	HSTR_PUSH_BOOL(L,   "showUnique",  cd.showUnique);
1065 	HSTR_PUSH_BOOL(L,   "onlyTexture", cd.onlyTexture);
1066 
1067 	HSTR_PUSH(L, "params");
1068 
1069 	lua_createtable(L, 0, numParams);
1070 
1071 	for (int p = 0; p < numParams; p++) {
1072 		lua_pushsstring(L, cd.params[p]);
1073 		lua_rawseti(L, -2, p + 1);
1074 	}
1075 
1076 	// CmdDesc["params"] = {[1] = "string1", [2] = "string2", ...}
1077 	lua_settable(L, -3);
1078 }
1079 
1080