418 lines
13 KiB
C++
418 lines
13 KiB
C++
#include <iostream>
|
|
#include <iomanip>
|
|
#include <random>
|
|
#include <sstream>
|
|
|
|
#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<const uint8_t*>(ptr);
|
|
for (size_t i = 0; i < size; ++i) {
|
|
*output_stream << std::hex << std::setw(2) << std::setfill('0')
|
|
<< static_cast<int>(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<unsigned char>(*str))) {
|
|
// Print other control characters in \xHH format
|
|
*output_stream << "\\x" << std::hex << std::setw(2) << std::setfill('0')
|
|
<< static_cast<int>(static_cast<unsigned char>(*str)) << std::dec;
|
|
} else {
|
|
*output_stream << *str;
|
|
}
|
|
break;
|
|
}
|
|
++str;
|
|
}
|
|
};
|
|
|
|
std::function<void(int, int, bool)> 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<double> 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<ptrdiff_t>(lua::topointer(L, idx));
|
|
return ss.str();
|
|
}
|
|
case LUA_TUSERDATA: {
|
|
std::stringstream ss;
|
|
ss << "userdata: 0x" << std::hex
|
|
<< reinterpret_cast<ptrdiff_t>(lua::topointer(L, idx));
|
|
return ss.str();
|
|
}
|
|
case LUA_TTHREAD: {
|
|
std::stringstream ss;
|
|
ss << "thread: 0x" << std::hex
|
|
<< reinterpret_cast<ptrdiff_t>(lua::topointer(L, idx));
|
|
return ss.str();
|
|
}
|
|
default: {
|
|
std::stringstream ss;
|
|
ss << "cdata: 0x" << std::hex
|
|
<< reinterpret_cast<ptrdiff_t>(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<int>(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<int>(event.type));
|
|
lua::rawseti(L, 1);
|
|
|
|
if (auto dto = std::get_if<BreakpointEventDto>(&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<GetValueEventDto>(&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<std::string>(&segment)) {
|
|
lua::pushstring(L, *string);
|
|
} else {
|
|
lua::pushinteger(L, std::get<int>(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<l_debug_error>);
|
|
lua::setfield(L, "error");
|
|
|
|
lua::pushcfunction(L, lua::wrap<l_debug_warning>);
|
|
lua::setfield(L, "warning");
|
|
|
|
lua::pushcfunction(L, lua::wrap<l_debug_log>);
|
|
lua::setfield(L, "log");
|
|
|
|
lua::pushcfunction(L, lua::wrap<l_debug_print>);
|
|
lua::setfield(L, "print");
|
|
|
|
lua::pushcfunction(L, lua::wrap<l_debug_pause>);
|
|
lua::setfield(L, "pause");
|
|
|
|
lua::pushcfunction(L, lua::wrap<l_debug_pull_events>);
|
|
lua::setfield(L, "__pull_events");
|
|
|
|
lua::pushcfunction(L, lua::wrap<l_debug_sendvalue>);
|
|
lua::setfield(L, "__sendvalue");
|
|
|
|
lua::pushcfunction(L, lua::wrap<l_debug_is_debugging>);
|
|
lua::setfield(L, "is_debugging");
|
|
|
|
lua::pop(L);
|
|
}
|
|
if (lua::getglobal(L, "math")) {
|
|
lua::pushcfunction(L, lua::wrap<l_math_normal_random>);
|
|
lua::setfield(L, "normal_random");
|
|
|
|
lua::pop(L);
|
|
}
|
|
}
|