1362 lines
47 KiB
C++
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);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|