#include "lua.debug.h" #include "lua/lua.hpp" #include "mini.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace mini { namespace lua { namespace debug { struct Breakpoint { int line; std::string condition; // puede estar vacío std::string logMessage; // "" si no es logpoint std::string hitCondition; // "" si no hay hit count }; struct FrameInfo { int level; // nivel en lua_getstack std::string source; int line; //std::vector locals; //std::vector upvalues; }; struct StackFrameInfo { std::string file; int line; std::string function; }; std::string lastExceptionTraceback; std::vector lastExceptionStack; std::string lastExceptionMessage; bool hasException = false; std::set exceptionFilters;// {"all"}; std::vector g_frames; //std::unordered_map> g_breakpoints; std::unordered_map> g_breakpoints; std::mutex g_breakMutex; std::queue g_debugCommands; std::mutex g_cmdMutex; std::atomic g_running = true; std::atomic g_paused = false; std::string g_pauseFile; int g_pauseLine = 0; bool function_has_breakpoints = false; std::stack funBreakStack; bool debug_enabled = true; enum StepMode { STEP_NONE, STEP_INTO, STEP_OVER, STEP_OUT }; StepMode g_stepMode = STEP_NONE; int g_stepDepth = 0; using json = nlohmann::json; std::string pathToChunk(const std::string& path) { std::filesystem::path p(path); // 1. Normalizar la ruta p = p.lexically_normal(); // 2. Buscar el directorio "data" auto it = std::find(p.begin(), p.end(), "data"); if (it == p.end()) return ""; // no es un script Lua válido // 3. Construir la parte relativa después de "data" std::filesystem::path rel; for (++it; it != p.end(); ++it) rel /= *it; // 4. Quitar ".lua" std::string s = rel.string(); if (s.ends_with(".lua")) s = s.substr(0, s.size() - 4); // 5. Convertir "/" → "." for (char& c : s) if (c == '/') c = '.'; return s; } std::string chunkToPath(const std::string& chunk) { // 1. Convertir "ia.test" → "ia/test" std::string rel; rel.reserve(chunk.size() + 10); for (char c : chunk) rel += (c == '.' ? '/' : c); // 2. Añadir prefijo y sufijo rel = "data/" + rel + ".lua"; // 3. Convertir a ruta absoluta std::filesystem::path abs = std::filesystem::current_path() / rel; return abs.lexically_normal().string(); } int getStackDepth(lua_State* L) { lua_Debug ar; int depth = 0; while (lua_getstack(L, depth, &ar)) { depth++; } return depth; } json getStackTrace(lua_State* L) { json frames = json::array(); lua_Debug ar; int depth = 0; while (lua_getstack(L, depth, &ar)) { lua_getinfo(L, "nSl", &ar); //const char* src = chunkToPath(ar.source).c_str(); json frame = { { "file", chunkToPath(ar.source).c_str() }, { "line", ar.currentline }, { "name", ar.name ? ar.name : "?" } }; frames.push_back(frame); depth++; } return json{ { "frames", frames } }; } int allocateRefForTable(lua_State* L, int index) { lua_pushvalue(L, index); // copia la tabla int ref = luaL_ref(L, LUA_REGISTRYINDEX); return ref; } bool pushTableFromRef(lua_State* L, int ref) { lua_rawgeti(L, LUA_REGISTRYINDEX, ref); if (!lua_istable(L, -1)) { lua_pop(L, 1); return false; } return true; } json getLocals(lua_State* L, int frame) { json vars = json::array(); lua_Debug ar; // Obtenemos el frame solicitado if (!lua_getstack(L, frame, &ar)) { printf("INVALID STACK FRAME\n"); return json{ {"variables", vars} }; } // Pedimos info del frame lua_getinfo(L, "nSl", &ar); int i = 1; const char* name; while ((name = lua_getlocal(L, &ar, i)) != NULL) { if (strcmp(name, "(temporary)") == 0) { lua_pop(L, 1); i++; continue; } json v; v["name"] = name; int type = lua_type(L, -1); switch (type) { case LUA_TNUMBER: v["value"] = std::to_string(lua_tonumber(L, -1)); v["type"] = "number"; v["ref"] = 0; break; case LUA_TBOOLEAN: v["value"] = lua_toboolean(L, -1) ? "true" : "false"; v["type"] = "boolean"; v["ref"] = 0; break; case LUA_TSTRING: v["value"] = lua_tostring(L, -1); v["type"] = "string"; v["ref"] = 0; break; case LUA_TTABLE: v["value"] = "{table}"; v["type"] = "table"; v["ref"] = allocateRefForTable(L, -1); break; case LUA_TFUNCTION: v["value"] = "function"; v["type"] = "function"; v["ref"] = 0; break; case LUA_TNIL: v["value"] = "nil"; v["type"] = "nil"; v["ref"] = 0; break; default: v["value"] = lua_typename(L, type); v["type"] = lua_typename(L, type); v["ref"] = 0; break; } vars.push_back(v); // Quitamos el valor de la pila lua_pop(L, 1); i++; } return json{ { "variables", vars } }; } json getUpvalues(lua_State* L, int frame) { json vars = json::array(); lua_Debug ar; if (!lua_getstack(L, frame, &ar)) { return json{ {"variables", vars} }; } // Pedimos info del frame, incluyendo la función ("f") lua_getinfo(L, "nSlf", &ar); // La función está ahora en la pila int funcIndex = lua_gettop(L); int i = 1; const char* name; while ((name = lua_getupvalue(L, funcIndex, i)) != NULL) { json v; v["name"] = name; int type = lua_type(L, -1); switch (type) { case LUA_TNUMBER: v["value"] = std::to_string(lua_tonumber(L, -1)); v["type"] = "number"; v["ref"] = 0; break; case LUA_TBOOLEAN: v["value"] = lua_toboolean(L, -1) ? "true" : "false"; v["type"] = "boolean"; v["ref"] = 0; break; case LUA_TSTRING: v["value"] = lua_tostring(L, -1); v["type"] = "string"; v["ref"] = 0; break; case LUA_TTABLE: v["value"] = "{table}"; v["type"] = "table"; v["ref"] = allocateRefForTable(L, -1); break; case LUA_TNIL: v["value"] = "nil"; v["type"] = "nil"; v["ref"] = 0; break; default: v["value"] = lua_typename(L, type); v["type"] = lua_typename(L, type); v["ref"] = 0; break; } vars.push_back(v); lua_pop(L, 1); // quitamos el valor del upvalue i++; } // Quitamos la función del stack lua_pop(L, 1); return json{ { "variables", vars } }; } json getGlobals(lua_State* L) { json vars = json::array(); // Empujamos _G a la pila lua_pushglobaltable(L); // equivalente a lua_getglobal(L, "_G") lua_pushnil(L); // primera clave while (lua_next(L, -2) != 0) { // Ahora en la pila: // -1 → valor // -2 → clave // -3 → _G // Solo aceptamos claves string if (lua_type(L, -2) == LUA_TSTRING) { const char* name = lua_tostring(L, -2); json v; v["name"] = name; int type = lua_type(L, -1); switch (type) { case LUA_TNUMBER: v["value"] = std::to_string(lua_tonumber(L, -1)); v["type"] = "number"; v["ref"] = 0; break; case LUA_TBOOLEAN: v["value"] = lua_toboolean(L, -1) ? "true" : "false"; v["type"] = "boolean"; v["ref"] = 0; break; case LUA_TSTRING: v["value"] = lua_tostring(L, -1); v["type"] = "string"; v["ref"] = 0; break; case LUA_TTABLE: v["value"] = "{table}"; v["type"] = "table"; v["ref"] = allocateRefForTable(L, -1); break; case LUA_TFUNCTION: v["value"] = "function"; v["type"] = "function"; v["ref"] = 0; break; case LUA_TNIL: v["value"] = "nil"; v["type"] = "nil"; v["ref"] = 0; break; default: v["value"] = lua_typename(L, type); v["type"] = lua_typename(L, type); v["ref"] = 0; break; } vars.push_back(v); } lua_pop(L, 1); // pop valor, deja clave para lua_next } lua_pop(L, 1); // pop _G return json{ { "variables", vars } }; } json expandTable(lua_State* L, int ref) { json vars = json::array(); // Recuperamos la tabla del registry lua_rawgeti(L, LUA_REGISTRYINDEX, ref); lua_pushnil(L); while (lua_next(L, -2) != 0) { json v; // clave if (lua_type(L, -2) == LUA_TSTRING) { v["name"] = lua_tostring(L, -2); } else if (lua_type(L, -2) == LUA_TNUMBER) { v["name"] = std::to_string(lua_tonumber(L, -2)); } else { v["name"] = ""; } // valor int type = lua_type(L, -1); switch (type) { case LUA_TNUMBER: v["value"] = std::to_string(lua_tonumber(L, -1)); v["type"] = "number"; v["ref"] = 0; break; case LUA_TBOOLEAN: v["value"] = lua_toboolean(L, -1) ? "true" : "false"; v["type"] = "boolean"; v["ref"] = 0; break; case LUA_TSTRING: v["value"] = lua_tostring(L, -1); v["type"] = "string"; v["ref"] = 0; break; case LUA_TTABLE: v["value"] = "{table}"; v["type"] = "table"; v["ref"] = allocateRefForTable(L, -1); break; default: v["value"] = lua_typename(L, type); v["type"] = lua_typename(L, type); v["ref"] = 0; break; } vars.push_back(v); lua_pop(L, 1); // pop valor } lua_pop(L, 1); // pop tabla return json{ { "kind", "expand" }, { "variables", vars } }; } json setVariable(lua_State* L, int frame, const std::string& scope, const std::string& name, const std::string& valueStr) { lua_Debug ar; if (!lua_getstack(L, frame, &ar)) { return { {"error", "invalid frame"} }; } lua_getinfo(L, "nSl", &ar); // Convertir valueStr a valor Lua auto pushValue = [&](const std::string& s) { // número char* end; double num = strtod(s.c_str(), &end); if (*end == '\0') { lua_pushnumber(L, num); return; } // boolean if (s == "true") { lua_pushboolean(L, 1); return; } if (s == "false") { lua_pushboolean(L, 0); return; } // nil if (s == "nil") { lua_pushnil(L); return; } // string lua_pushstring(L, s.c_str()); }; if (scope == "locals") { int i = 1; const char* localName; while ((localName = lua_getlocal(L, &ar, i)) != NULL) { lua_pop(L, 1); // pop old value if (name == localName) { pushValue(valueStr); lua_setlocal(L, &ar, i); return { {"value", valueStr} }; } i++; } } if (scope == "upvalues") { lua_getinfo(L, "f", &ar); // push function int i = 1; const char* upName; while ((upName = lua_getupvalue(L, -1, i)) != NULL) { lua_pop(L, 1); // pop old value if (name == upName) { pushValue(valueStr); lua_setupvalue(L, -1, i); lua_pop(L, 1); // pop function return { {"value", valueStr} }; } i++; } lua_pop(L, 1); // pop function } if (scope == "globals") { pushValue(valueStr); lua_setglobal(L, name.c_str()); return { {"value", valueStr} }; } return { {"error", "variable not found"} }; } json setTableField(lua_State* L, int ref, const std::string& key, const std::string& valueStr) { // Recuperar la tabla desde tu sistema de referencias if (!pushTableFromRef(L, ref)) { return { {"error", "invalid table ref"} }; } // Convertir el valor auto pushValue = [&](const std::string& s) { char* end; double num = strtod(s.c_str(), &end); if (*end == '\0') { lua_pushnumber(L, num); return; } if (s == "true") { lua_pushboolean(L, 1); return; } if (s == "false") { lua_pushboolean(L, 0); return; } if (s == "nil") { lua_pushnil(L); return; } lua_pushstring(L, s.c_str()); }; pushValue(valueStr); // tabla[key] = valor lua_setfield(L, -2, key.c_str()); lua_pop(L, 1); // pop tabla return { {"value", valueStr} }; } json evalExpression(lua_State* L, const std::string& expr) { lua_Debug ar; if (!lua_getstack(L, 0, &ar)) { return { {"error", "no stack"} }; } lua_getinfo(L, "nSl", &ar); // Creamos un entorno para la evaluación lua_newtable(L); // env int envIndex = lua_gettop(L); // 1. Copiar locals al env int i = 1; const char* name; while ((name = lua_getlocal(L, &ar, i)) != NULL) { lua_setfield(L, envIndex, name); i++; } // 2. Copiar upvalues al env lua_getinfo(L, "f", &ar); int funcIndex = lua_gettop(L); i = 1; while ((name = lua_getupvalue(L, funcIndex, i)) != NULL) { lua_setfield(L, envIndex, name); i++; } lua_pop(L, 1); // pop función // 3. Copiar globals (_G) lua_pushglobaltable(L); lua_setfield(L, envIndex, "_G"); // mt = { __index = _G } lua_newtable(L); // mt lua_pushglobaltable(L); // _G lua_setfield(L, -2, "__index"); // mt.__index = _G // setmetatable(env, mt) lua_setmetatable(L, envIndex); // 4. Construir código: función que recibe _ENV std::string code = "return function(_ENV) return " + expr + " end"; if (luaL_loadbuffer(L, code.c_str(), code.size(), "eval") != LUA_OK) { std::string err = lua_tostring(L, -1); lua_pop(L, 1); return { {"error", err} }; } // Ejecutar para obtener la función interna if (lua_pcall(L, 0, 1, 0) != LUA_OK) { std::string err = lua_tostring(L, -1); lua_pop(L, 1); return { {"error", err} }; } // Ahora en la pila está la función interna // Le pasamos env como argumento lua_pushvalue(L, envIndex); // Llamamos a la función(env) if (lua_pcall(L, 1, 1, 0) != LUA_OK) { std::string err = lua_tostring(L, -1); lua_pop(L, 1); return { {"error", err} }; } // Convertir el resultado json result; int type = lua_type(L, -1); switch (type) { case LUA_TNUMBER: result["value"] = std::to_string(lua_tonumber(L, -1)); result["type"] = "number"; result["ref"] = 0; break; case LUA_TBOOLEAN: result["value"] = lua_toboolean(L, -1) ? "true" : "false"; result["type"] = "boolean"; result["ref"] = 0; break; case LUA_TSTRING: result["value"] = lua_tostring(L, -1); result["type"] = "string"; result["ref"] = 0; break; case LUA_TTABLE: result["value"] = "{table}"; result["type"] = "table"; result["ref"] = allocateRefForTable(L, -1); break; default: result["value"] = lua_typename(L, type); result["type"] = lua_typename(L, type); result["ref"] = 0; break; } lua_pop(L, 1); // pop result lua_pop(L, 1); // pop env return result; } json evalAssign(lua_State* L, int frame, const std::string& expr) { // 1. Separar LHS y RHS size_t eq = expr.find('='); if (eq == std::string::npos) { return { {"error", "not an assignment"} }; } std::string lhs = expr.substr(0, eq); std::string rhs = expr.substr(eq + 1); // limpiar espacios auto trim = [](std::string& s) { size_t a = s.find_first_not_of(" \t"); size_t b = s.find_last_not_of(" \t"); if (a == std::string::npos) { s = ""; return; } s = s.substr(a, b - a + 1); }; trim(lhs); trim(rhs); // 2. Evaluar RHS usando tu evalExpression json rhsValue = evalExpression(L, rhs); if (rhsValue.contains("error")) { return rhsValue; } std::string rhsStr = rhsValue["value"]; // 3. Determinar si LHS es: // - variable simple: x // - acceso tabla: player.health // - acceso profundo: a.b.c if (lhs.find('.') == std::string::npos) { // variable simple → locals, upvalues o globals // Intentar locals json r = setVariable(L, frame, "locals", lhs, rhsStr); if (!r.contains("error")) return r; // Intentar upvalues r = setVariable(L, frame, "upvalues", lhs, rhsStr); if (!r.contains("error")) return r; // Intentar globals r = setVariable(L, frame, "globals", lhs, rhsStr); return r; } // 4. Acceso tabla: a.b.c // separar en partes std::vector parts; { std::stringstream ss(lhs); std::string item; while (std::getline(ss, item, '.')) { trim(item); parts.push_back(item); } } if (parts.size() < 2) { return { {"error", "invalid table assignment"} }; } // La clave final std::string finalKey = parts.back(); parts.pop_back(); // 5. Evaluar la ruta de tabla (a.b.c → obtener tabla c) // usando tu evalExpression std::string tableExpr; for (size_t i = 0; i < parts.size(); i++) { if (i > 0) tableExpr += "."; tableExpr += parts[i]; } json tableValue = evalExpression(L, tableExpr); if (tableValue.contains("error")) { return tableValue; } if (tableValue["type"] != "table") { return { {"error", "LHS is not a table"} }; } int ref = tableValue["ref"]; // 6. Asignar dentro de la tabla return setTableField(L, ref, finalKey, rhsStr); } void sendDebugResponse(const std::string& type, const json& payload) { json msg = { { "type", type }, { "payload", payload } }; //printf("STACKTRACE: %s", msg.dump().c_str()); std::cout << "@@DEBUG@@" << msg.dump() << std::endl; } void parseLuaTraceback(const char* tb, std::vector& out) { out.clear(); if (!tb) return; std::stringstream ss(tb); std::string line; bool inFrames = false; while (std::getline(ss, line)) { // Quitar espacios iniciales y tabs while (!line.empty() && (line[0] == ' ' || line[0] == '\t')) line.erase(line.begin()); // Saltar la primera línea (mensaje de error) if (!inFrames) { if (line.rfind("stack traceback:", 0) == 0) { inFrames = true; } continue; } // Formato esperado: // [string "modules.ia.hero"]:189: in field 'ia' // // O: // [string "main"]:34: in function <[string "main"]:32> if (line.rfind("[string \"", 0) != 0) continue; // Extraer chunk name size_t q1 = line.find('"'); size_t q2 = line.find('"', q1 + 1); if (q1 == std::string::npos || q2 == std::string::npos) continue; std::string chunk = line.substr(q1 + 1, q2 - q1 - 1); // Extraer línea size_t colon1 = line.find(':', q2 + 1); if (colon1 == std::string::npos) continue; size_t colon2 = line.find(':', colon1 + 1); if (colon2 == std::string::npos) continue; int lineNumber = std::stoi(line.substr(colon1 + 1, colon2 - colon1 - 1)); // Extraer nombre de función (si existe) std::string func = "unknown"; size_t inField = line.find("in field '"); if (inField != std::string::npos) { size_t start = inField + 10; size_t end = line.find("'", start); func = line.substr(start, end - start); } size_t inFunc = line.find("in function <"); if (inFunc != std::string::npos) { func = "anonymous"; } // Convertir chunk → ruta real std::string filePath = chunkToPath(chunk); out.push_back({ filePath, lineNumber, func }); } } void processDebugCommand(lua_State* L, const std::string& line) { //printf("COMANDO PROCESADO: %s\n", line.c_str()); if (!line.starts_with("@@DEBUGCMD@@")) return; json j = json::parse(line.substr(12)); std::string cmd = j["cmd"]; if (cmd == "setBreakpoints") { std::string file = j["file"]; std::string chunk = pathToChunk(file); std::lock_guard lock(g_breakMutex); g_breakpoints[chunk].clear(); for (auto& bp : j["breakpoints"]) { Breakpoint b; b.line = bp["line"]; if (bp.contains("condition") && bp["condition"].is_string()) b.condition = bp["condition"]; else b.condition = ""; if (bp.contains("logMessage") && bp["logMessage"].is_string()) b.logMessage = bp["logMessage"]; else b.logMessage = ""; if (bp.contains("hitCondition") && bp["hitCondition"].is_string()) b.hitCondition = bp["hitCondition"]; else b.hitCondition = ""; g_breakpoints[chunk].push_back(b); } } else if (cmd == "continue") { printf("CONTINUA\n\n"); g_stepMode = STEP_NONE; g_paused = false; raisewindow(); } else if (cmd == "pause") { g_stepMode = STEP_INTO; g_paused = false; } else if (cmd == "stepOver") { g_stepMode = STEP_OVER; g_stepDepth = getStackDepth(L); g_paused = false; } else if (cmd == "stepInto") { g_stepMode = STEP_INTO; g_paused = false; } else if (cmd == "stepOut") { g_stepMode = STEP_OUT; g_stepDepth = getStackDepth(L); g_paused = false; } else if (cmd == "stackTrace") { if (hasException) { parseLuaTraceback(lastExceptionTraceback.c_str(), lastExceptionStack); json frames = json::array(); for (size_t i = 0; i < lastExceptionStack.size(); i++) { frames.push_back({ { "id", (int)i + 1 }, { "name", lastExceptionStack[i].function }, { "line", lastExceptionStack[i].line }, { "column", 1 }, { "source", { { "path", lastExceptionStack[i].file } }} }); } json result = { { "stackFrames", frames }, { "totalFrames", frames.size() } }; sendDebugResponse("stackTrace", result); } else { json result = getStackTrace(L); sendDebugResponse("stackTrace", result); } } else if (cmd == "locals") { int frame = j.value("frame", 0); json result = getLocals(L, frame); json payload = { { "kind", "locals" }, { "variables", result["variables"] } }; sendDebugResponse("variables", payload); } else if (cmd == "upvalues") { int frame = j.value("frame", 0); json result = getUpvalues(L, frame); json payload = { { "kind", "upvalues" }, { "variables", result["variables"] } }; sendDebugResponse("variables", payload); } else if (cmd == "globals") { json result = getGlobals(L); json payload = { { "kind", "globals" }, { "variables", result["variables"] } }; sendDebugResponse("variables", payload); } else if (cmd == "expand") { int ref = j["ref"]; json result = expandTable(L, ref); sendDebugResponse("variables", result); } else if (cmd == "eval") { std::string expr = j["expr"]; json result = evalExpression(L, expr); json payload = { { "kind", "eval" }, { "result", result } }; sendDebugResponse("eval", payload); } else if (cmd == "evalAssign") { int frame = j.value("frame", 0); std::string expr = j["expr"]; json result = evalAssign(L, frame, expr); json payload = { { "kind", "eval" }, { "result", result } }; sendDebugResponse("eval", payload); } else if (cmd == "setVariable") { int frame = j.value("frame", 0); std::string scope = j["scope"]; std::string name = j["name"]; std::string value = j["value"]; json result = setVariable(L, frame, scope, name, value); sendDebugResponse("setVariable", result); } else if (cmd == "setTableField") { int ref = j["ref"]; std::string key = j["key"]; std::string valueStr = j["value"]; json result = setTableField(L, ref, key, valueStr); sendDebugResponse("setVariable", result); } else if (cmd == "setExceptionFilters") { exceptionFilters.clear(); for (auto& f : j["filters"]) { exceptionFilters.insert(f.get()); } } } bool checkBreakpointCondition(lua_State* L, const Breakpoint& bp) { if (bp.condition.empty()) return true; json result = evalExpression(L, bp.condition); if (result.contains("error")) return false; if (result["type"] == "boolean") return result["value"] == "true"; // Si la condición no devuelve booleano, se considera false return false; } std::string expandLogMessage(lua_State* L, const std::string& msg) { std::string out; size_t i = 0; while (i < msg.size()) { if (msg[i] == '{') { size_t j = msg.find('}', i + 1); if (j == std::string::npos) break; std::string expr = msg.substr(i + 1, j - i - 1); json r = evalExpression(L, expr); out += r.value("value", "nil"); i = j + 1; } else { out += msg[i++]; } } return out; } void sendLogOutput(const std::string& text) { json payload = { { "kind", "log" }, { "text", text } }; sendDebugResponse("log", payload); } bool isBreakpoint(lua_State* L, const std::string& file, int line) { std::lock_guard lock(g_breakMutex); auto it = g_breakpoints.find(file); if (it == g_breakpoints.end()) return false; for (auto& bp : it->second) { if (bp.line == line) { if (!bp.logMessage.empty()) { std::string msg = expandLogMessage(L, bp.logMessage); sendLogOutput(msg); return false; // NO parar } else if (checkBreakpointCondition(L, bp)) { return true; } } } return false; } void sendBreakEvent(const std::string& file, int line) { json j = { {"type", "break"}, {"file", file}, {"line", line} }; std::cout << "@@DEBUG@@" << j.dump() << std::endl; std::cout.flush(); } std::string waitForDebugCommand() { //printf("HOLA"); while (true) { { std::lock_guard lock(g_cmdMutex); if (!g_debugCommands.empty()) { std::string cmd = g_debugCommands.front(); g_debugCommands.pop(); return cmd; } } SDL_Delay(1); } } bool shouldPauseForStepping(lua_State* L, lua_Debug* ar) { int depth = getStackDepth(L); switch (g_stepMode) { case STEP_INTO: return true; // siempre parar en la siguiente línea case STEP_OVER: return depth <= g_stepDepth; case STEP_OUT: return depth < g_stepDepth; default: return false; } } void captureStack(lua_State* L) { g_frames.clear(); lua_Debug ar; int level = 0; while (lua_getstack(L, level, &ar)) { lua_getinfo(L, "nSl", &ar); FrameInfo fi; fi.level = level; fi.source = ar.source; fi.line = ar.currentline; g_frames.push_back(fi); level++; } } void pauseHere(lua_State* L, const std::string& src, int line) { g_paused = true; g_pauseFile = src; g_pauseLine = line; g_stepMode = STEP_NONE; captureStack(L); sendBreakEvent(chunkToPath(src), line); while (g_paused) { std::string cmd = waitForDebugCommand(); processDebugCommand(L, cmd); } } extern "C" void luaHook(lua_State* L, lua_Debug* ar) { lua_getinfo(L, "Sl", ar); const char* src = ar->source; if (src[0]=='=') return; if (ar->event == LUA_HOOKCALL) { funBreakStack.push(function_has_breakpoints); bool new_function_has_breakpoints = false; { std::lock_guard lock(g_breakMutex); auto it = g_breakpoints.find(src); new_function_has_breakpoints = (it != g_breakpoints.end() && !it->second.empty()); } if (new_function_has_breakpoints || g_stepMode != STEP_NONE) { if (!function_has_breakpoints) lua_sethook(L, luaHook, LUA_MASKCALL | LUA_MASKLINE | LUA_MASKRET, 0); } else { //if (function_has_breakpoints) lua_sethook(L, luaHook, LUA_MASKCALL | LUA_MASKRET, 0); } function_has_breakpoints = new_function_has_breakpoints; } else if (ar->event == LUA_HOOKRET) { // Siempre volver al hook base bool new_function_has_breakpoints = funBreakStack.empty() ? false : funBreakStack.top(); funBreakStack.pop(); if (new_function_has_breakpoints || g_stepMode != STEP_NONE) { if (!function_has_breakpoints) lua_sethook(L, luaHook, LUA_MASKCALL | LUA_MASKLINE | LUA_MASKRET, 0); } else { //if (function_has_breakpoints) lua_sethook(L, luaHook, LUA_MASKCALL | LUA_MASKRET, 0); } function_has_breakpoints = new_function_has_breakpoints; } else if (ar->event == LUA_HOOKLINE) { if (ar->currentline <= 0) return; const char* src = ar->source; int line = ar->currentline; if (isBreakpoint(L, src, line)) { pauseHere(L, src, line); return; } // 2. Stepping if (g_stepMode != STEP_NONE) { if (shouldPauseForStepping(L, ar)) { pauseHere(L, src, line); return; } } } } void sendExceptionStoppedEvent(const std::string& msg) { json payload = { { "reason", "exception" }, { "text", msg } }; sendDebugResponse("stopped", payload); } int luaErrorHandler(lua_State* L) { const char* msg = lua_tostring(L, 1); if (!msg) msg = "Unknown error"; // Construir traceback luaL_traceback(L, L, msg, 1); const char* tb = lua_tostring(L, -1); if (tb) { lastExceptionTraceback = tb; } else { lastExceptionTraceback = msg; } // Devolver el traceback a Lua (aunque luego Lua lo sustituya) return 1; } bool call_and_handle_exceptions(lua_State* L) { lua_pushcfunction(L, luaErrorHandler); lua_insert(L, -2); // poner handler debajo de la función int status = lua_pcall(L, 0, 0, -2); lua_pop(L, 1); // quitar handler if (status != LUA_OK) { lastExceptionMessage = lastExceptionTraceback; lua_pop(L, 1); hasException = true; if (exceptionFilters.count("all") || exceptionFilters.count("uncaught")) { sendExceptionStoppedEvent(lastExceptionMessage); return false; } sendLogOutput(lastExceptionMessage); SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Mini Runtime Exception", lastExceptionTraceback.c_str(), NULL); return false; } return true; } std::jthread stdinThread([](std::stop_token st) { std::string line; struct pollfd pfd; pfd.fd = 0; // stdin pfd.events = POLLIN; // queremos saber si hay datos while (!st.stop_requested()) { int ret = poll(&pfd, 1, 10); // timeout 10 ms if (ret > 0 && (pfd.revents & POLLIN)) { if (!std::getline(std::cin, line)) { break; // EOF } { std::lock_guard lock(g_cmdMutex); g_debugCommands.push(line); printf("COMANDO RECIBIDO: %s\n", line.c_str()); } } else { SDL_Delay(1); } } }); void process_commands(lua_State* L) { function_has_breakpoints = false; while (!funBreakStack.empty()) funBreakStack.pop(); while (true) { std::string cmd; { std::lock_guard lock(g_cmdMutex); if (g_debugCommands.empty()) break; cmd = g_debugCommands.front(); g_debugCommands.pop(); } processDebugCommand(L, cmd); } } void kill_thread() { g_running = false; stdinThread.request_stop(); } void toggle(lua_State* L) { if (debug_enabled) { debug_enabled = false; lua_sethook(L, NULL,0, 0); } else { debug_enabled = true; lua_sethook(L, luaHook, LUA_MASKCALL | LUA_MASKRET, 0); } } } } }