Files
mini/source/lua.debug.cpp

1362 lines
47 KiB
C++

#include "lua.debug.h"
#include "lua/lua.hpp"
#include "mini.h"
#include <vector>
#include <stack>
#include <set>
#include <unordered_map>
#include <string>
#include <mutex>
#include <queue>
#include <thread>
#include <atomic>
#include <iostream>
#include <nlohmann/json.hpp>
#include <poll.h>
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<Variable> locals;
//std::vector<Variable> upvalues;
};
struct StackFrameInfo {
std::string file;
int line;
std::string function;
};
std::string lastExceptionTraceback;
std::vector<StackFrameInfo> lastExceptionStack;
std::string lastExceptionMessage;
bool hasException = false;
std::set<std::string> exceptionFilters;// {"all"};
std::vector<FrameInfo> g_frames;
//std::unordered_map<std::string, std::vector<int>> g_breakpoints;
std::unordered_map<std::string, std::vector<Breakpoint>> g_breakpoints;
std::mutex g_breakMutex;
std::queue<std::string> g_debugCommands;
std::mutex g_cmdMutex;
std::atomic<bool> g_running = true;
std::atomic<bool> g_paused = false;
std::string g_pauseFile;
int g_pauseLine = 0;
bool function_has_breakpoints = false;
std::stack<bool> 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"] = "<non-string-key>";
}
// 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<std::string> 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<StackFrameInfo>& 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<std::mutex> 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<std::string>());
}
}
}
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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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);
}
}
}
}
}