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