- [NEW] [debugger] Soport per a multiples nivells (frames) de stack

- [NEW] [debugger] Soport per a breakpoints condicionals
- [NEW] [debugger] Soport per a modificar variables
This commit is contained in:
2026-03-31 13:03:53 +02:00
parent 6a24086556
commit 6f5bdd274a

250
lua.cpp
View File

@@ -17,12 +17,30 @@
#include <iostream>
#include <nlohmann/json.hpp>
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;
};
std::vector<FrameInfo> g_frames;
lua_State *L;
bool is_playing = false;
bool init_exists = false;
bool update_exists = false;
std::unordered_map<std::string, std::vector<int>> g_breakpoints;
//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;
@@ -138,12 +156,24 @@ int allocateRefForTable(lua_State* L, int index) {
return ref;
}
json getLocals(lua_State* L) {
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 0 (el actual)
if (!lua_getstack(L, 0, &ar)) {
// Obtenemos el frame solicitado
if (!lua_getstack(L, frame, &ar)) {
printf("INVALID STACK FRAME\n");
return json{ {"variables", vars} };
}
@@ -161,12 +191,9 @@ json getLocals(lua_State* L) {
continue;
}
// El valor está ahora en la pila
json v;
v["name"] = name;
// Convertimos el valor Lua a string y tipo
int type = lua_type(L, -1);
switch (type) {
@@ -226,11 +253,11 @@ json getLocals(lua_State* L) {
};
}
json getUpvalues(lua_State* L) {
json getUpvalues(lua_State* L, int frame) {
json vars = json::array();
lua_Debug ar;
if (!lua_getstack(L, 0, &ar)) {
if (!lua_getstack(L, frame, &ar)) {
return json{ {"variables", vars} };
}
@@ -559,6 +586,112 @@ json evalExpression(lua_State* L, const std::string& expr) {
return result;
}
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} };
}
void sendDebugResponse(const std::string& type, const json& payload) {
json msg = {
{ "type", type },
@@ -579,11 +712,32 @@ void processDebugCommand(const std::string& line) {
if (cmd == "setBreakpoints") {
std::string file = j["file"];
std::vector<int> lines = j["lines"];
std::lock_guard<std::mutex> lock(g_breakMutex);
std::string chunk = pathToChunk(file);
//printf("Es un BREAKPOINT: %s, %i\n", chunk.c_str(), lines[0]);
g_breakpoints[chunk] = lines;
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");
@@ -613,7 +767,8 @@ void processDebugCommand(const std::string& line) {
sendDebugResponse("stackTrace", result);
}
else if (cmd == "locals") {
json result = getLocals(L);
int frame = j.value("frame", 0);
json result = getLocals(L, frame);
json payload = {
{ "kind", "locals" },
{ "variables", result["variables"] }
@@ -621,7 +776,8 @@ void processDebugCommand(const std::string& line) {
sendDebugResponse("variables", payload);
}
else if (cmd == "upvalues") {
json result = getUpvalues(L);
int frame = j.value("frame", 0);
json result = getUpvalues(L, frame);
json payload = {
{ "kind", "upvalues" },
{ "variables", result["variables"] }
@@ -651,21 +807,52 @@ void processDebugCommand(const std::string& line) {
};
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);
}
}
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;
}
bool isBreakpoint(const std::string& file, int line) {
std::lock_guard<std::mutex> lock(g_breakMutex);
//if (!g_breakpoints.empty()) {
// printf("file: %s\n", file.c_str());
//}
auto it = g_breakpoints.find(file);
if (it == g_breakpoints.end())
return false;
for (int bp : it->second) {
if (bp == line)
return true;
for (auto& bp : it->second) {
if (bp.line == line) {
if (checkBreakpointCondition(L, bp)) {
return true;
}
}
}
return false;
@@ -732,12 +919,33 @@ bool shouldPauseForStepping(lua_State* L, lua_Debug* ar) {
}
}
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(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) {