#include #include #include #include #include "libs/api_lua.hpp" #include "debug/Logger.hpp" #include "engine/Engine.hpp" #include "devtools/DebuggingServer.hpp" #include "logic/scripting/scripting.hpp" using namespace devtools; using namespace scripting; static debug::Logger logger("lua-debug"); static int l_debug_error(lua::State* L) { auto text = lua::require_string(L, 1); logger.error() << text; return 0; } static int l_debug_warning(lua::State* L) { auto text = lua::require_string(L, 1); logger.warning() << text; return 0; } static int l_debug_log(lua::State* L) { auto text = lua::require_string(L, 1); logger.info() << text; return 0; } const int MAX_DEPTH = 10; int l_debug_print(lua::State* L) { auto addIndentation = [](int depth) { for (int i = 0; i < depth; ++i) *output_stream << " "; }; auto printHexData = [](const void* ptr, size_t size) { const auto* bytePtr = reinterpret_cast(ptr); for (size_t i = 0; i < size; ++i) { *output_stream << std::hex << std::setw(2) << std::setfill('0') << static_cast(bytePtr[i]) << ((i + 1) % 8 == 0 && i + 1 < size ? "\n" : " "); } }; auto printEscapedString = [](const char* str) { while (*str) { switch (*str) { case '\\': *output_stream << "\\\\"; break; case '\"': *output_stream << "\\\""; break; case '\n': *output_stream << "\\n"; break; case '\t': *output_stream << "\\t"; break; case '\r': *output_stream << "\\r"; break; case '\b': *output_stream << "\\b"; break; case '\f': *output_stream << "\\f"; break; default: if (iscntrl(static_cast(*str))) { // Print other control characters in \xHH format *output_stream << "\\x" << std::hex << std::setw(2) << std::setfill('0') << static_cast(static_cast(*str)) << std::dec; } else { *output_stream << *str; } break; } ++str; } }; std::function debugPrint = [&](int index, int depth, bool is_key) { if (depth > MAX_DEPTH) { *output_stream << "{...}"; return; } switch (lua::type(L, index)) { case LUA_TSTRING: if (is_key){ *output_stream << lua::tostring(L, index); }else{ *output_stream << "\""; printEscapedString(lua::tostring(L, index)); *output_stream << "\""; } break; case LUA_TBOOLEAN: *output_stream << (lua::toboolean(L, index) ? "true" : "false"); break; case LUA_TNUMBER: *output_stream << lua::tonumber(L, index); break; case LUA_TTABLE: { bool is_list = lua::objlen(L, index) > 0, hadItems = false; int absTableIndex = index > 0 ? index : lua::gettop(L) + index + 1; *output_stream << "{"; lua::pushnil(L); while (lua::next(L, absTableIndex) != 0) { if (hadItems) *output_stream << "," << '\n'; else *output_stream << '\n'; addIndentation(depth + 1); if (!is_list) { debugPrint(-2, depth, true); *output_stream << " = "; } debugPrint(-1, depth + 1, false); lua::pop(L, 1); hadItems = true; } if (hadItems) *output_stream << '\n'; addIndentation(depth); *output_stream << "}"; break; } case LUA_TFUNCTION: *output_stream << "function(0x" << std::hex << lua::topointer(L, index) << std::dec << ")"; break; case LUA_TUSERDATA: *output_stream << "userdata:\n"; printHexData(lua::topointer(L, index), lua::objlen(L, index)); break; case LUA_TLIGHTUSERDATA: *output_stream << "lightuserdata:\n"; printHexData(lua::topointer(L, index), sizeof(void*)); break; case LUA_TNIL: *output_stream << "nil"; break; default: *output_stream << lua::type_name(L, lua::type(L, index)); break; } }; int n = lua::gettop(L); *output_stream << "debug.print(" << '\n'; for (int i = 1; i <= n; ++i) { addIndentation(1); debugPrint(i, 1, false); if (i < n) *output_stream << "," << '\n'; } *output_stream << '\n' << ")" << std::endl; lua::pop(L, n); return 0; } namespace { std::normal_distribution randomFloats(0.0f, 1.0f); std::default_random_engine generator; } static int l_math_normal_random(lua::State* L) { return lua::pushnumber(L, randomFloats(generator)); } constexpr inline int MAX_SHORT_STRING_LEN = 50; static std::string get_short_value(lua::State* L, int idx, int type) { switch (type) { case LUA_TNIL: return "nil"; case LUA_TBOOLEAN: return lua::toboolean(L, idx) ? "true" : "false"; case LUA_TNUMBER: { std::stringstream ss; ss << lua::tonumber(L, idx); return ss.str(); } case LUA_TSTRING: { const char* str = lua::tostring(L, idx); if (strlen(str) > MAX_SHORT_STRING_LEN) { return std::string(str, MAX_SHORT_STRING_LEN); } else { return str; } } case LUA_TTABLE: return "{...}"; case LUA_TFUNCTION: { std::stringstream ss; ss << "function: 0x" << std::hex << reinterpret_cast(lua::topointer(L, idx)); return ss.str(); } case LUA_TUSERDATA: { std::stringstream ss; ss << "userdata: 0x" << std::hex << reinterpret_cast(lua::topointer(L, idx)); return ss.str(); } case LUA_TTHREAD: { std::stringstream ss; ss << "thread: 0x" << std::hex << reinterpret_cast(lua::topointer(L, idx)); return ss.str(); } default: { std::stringstream ss; ss << "cdata: 0x" << std::hex << reinterpret_cast(lua::topointer(L, idx)); return ss.str(); } } } static dv::value collect_locals(lua::State* L, lua_Debug& frame) { auto locals = dv::list(); int localIndex = 1; const char* name; while ((name = lua_getlocal(L, &frame, localIndex++))) { if (name[0] == '(') { lua::pop(L); continue; } auto local = dv::object(); local["name"] = name; local["index"] = localIndex - 1; int type = lua::type(L, -1); local["type"] = lua::type_name(L, type); local["short"] = get_short_value(L, -1, type); locals.add(std::move(local)); lua::pop(L); } return locals; } static dv::value create_stack_trace(lua::State* L, int initFrame = 2) { auto entriesList = dv::list(); lua_Debug frame; int level = initFrame; while (lua_getstack(L, level, &frame)) { auto entry = dv::object(); if (lua_getinfo(L, "nSlf", &frame) == 0) { level++; entriesList.add(std::move(entry)); continue; } if (frame.name) { entry["function"] = frame.name; } if (frame.source) { const char* src = (frame.source[0] == '@') ? frame.source + 1 : frame.source; entry["source"] = src; entry["line"] = frame.currentline; } entry["what"] = frame.what; entry["locals"] = collect_locals(L, frame); entriesList.add(std::move(entry)); level++; } return entriesList; } static int l_debug_pause(lua::State* L) { if (auto server = engine->getDebuggingServer()) { std::string reason; std::string message; if (lua::isstring(L, 1)) { reason = lua::tolstring(L, 1); } if (lua::isstring(L, 2)) { message = lua::tolstring(L, 2); } server->pause( std::move(reason), std::move(message), create_stack_trace(L) ); } return 0; } static int l_debug_sendvalue(lua::State* L) { auto server = engine->getDebuggingServer(); if (!server) { return 0; } int frame = lua::tointeger(L, 2); int local = lua::tointeger(L, 3); ValuePath path; int pathSectors = lua::objlen(L, 4); for (int i = 0; i < pathSectors; i++) { lua::rawgeti(L, i + 1, 4); if (lua::isstring(L, -1)) { path.emplace_back(lua::tostring(L, -1)); } else { path.emplace_back(static_cast(lua::tointeger(L, -1))); } lua::pop(L); } dv::value value = nullptr; if (lua::istable(L, 1)) { auto table = dv::object(); lua::pushnil(L); while (lua::next(L, 1)) { auto key = lua::tolstring(L, -2); int type = lua::type(L, -1); table[std::string(key)] = dv::object({ {"type", std::string(lua::type_name(L, type))}, {"short", get_short_value(L, -1, type)}, }); lua::pop(L); } lua::pop(L); value = std::move(table); } else { value = lua::tovalue(L, 1); } server->sendValue(std::move(value), frame, local, std::move(path)); return 0; } static int l_debug_pull_events(lua::State* L) { auto server = engine->getDebuggingServer(); if (!server) { return 0; } auto events = server->pullEvents(); if (events.empty()) { return 0; } lua::createtable(L, events.size(), 0); for (int i = 0; i < events.size(); i++) { const auto& event = events[i]; lua::createtable(L, 3, 0); lua::pushinteger(L, static_cast(event.type)); lua::rawseti(L, 1); if (auto dto = std::get_if(&event.data)) { lua::pushstring(L, dto->source); lua::rawseti(L, 2); lua::pushinteger(L, dto->line); lua::rawseti(L, 3); } else if (auto dto = std::get_if(&event.data)) { lua::pushinteger(L, dto->frame); lua::rawseti(L, 2); lua::pushinteger(L, dto->localIndex); lua::rawseti(L, 3); lua::createtable(L, dto->path.size(), 0); for (int i = 0; i < dto->path.size(); i++) { const auto& segment = dto->path[i]; if (auto string = std::get_if(&segment)) { lua::pushstring(L, *string); } else { lua::pushinteger(L, std::get(segment)); } lua::rawseti(L, i + 1); } lua::rawseti(L, 4); } lua::rawseti(L, i + 1); } return 1; } static int l_debug_is_debugging(lua::State* L) { return lua::pushboolean(L, engine->getDebuggingServer() != nullptr); } void initialize_libs_extends(lua::State* L) { if (lua::getglobal(L, "debug")) { lua::pushcfunction(L, lua::wrap); lua::setfield(L, "error"); lua::pushcfunction(L, lua::wrap); lua::setfield(L, "warning"); lua::pushcfunction(L, lua::wrap); lua::setfield(L, "log"); lua::pushcfunction(L, lua::wrap); lua::setfield(L, "print"); lua::pushcfunction(L, lua::wrap); lua::setfield(L, "pause"); lua::pushcfunction(L, lua::wrap); lua::setfield(L, "__pull_events"); lua::pushcfunction(L, lua::wrap); lua::setfield(L, "__sendvalue"); lua::pushcfunction(L, lua::wrap); lua::setfield(L, "is_debugging"); lua::pop(L); } if (lua::getglobal(L, "math")) { lua::pushcfunction(L, lua::wrap); lua::setfield(L, "normal_random"); lua::pop(L); } }