Files
jpdebug/debugger.lua
2025-11-20 12:36:55 +01:00

139 lines
3.7 KiB
Lua

local core = require "core"
---@class runner
local runner = {
new = function(self, o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end,
caps = {
can_pause = false,
can_continue = false,
can_step_in = false,
can_step_over = false,
can_step_out = false,
can_breakpoints = false,
has_stack = false,
has_locals = false,
can_eval = false,
},
run = function(self, target, name) end, ---@meta
pause = function(self) end, --@meta
continue = function(self) end, --@meta
step_in = function(self) end, --@meta
step_over = function(self) end, --@meta
step_out = function(self) end, --@meta
wait = function(self, time) end, ---@meta
kill = function(self) end, ---@meta
terminate = function(self) end, ---@meta
-- Callbacks
log = function(msg) end, ---@meta
error = function(msg) end, ---@meta
on_stdout = function(msg) end, ---@meta
on_stderr = function(msg) end, ---@meta
on_exit = function(exitcode) end, ---@meta
on_state = function(state) end, ---@meta
on_break = function(file, line, reason) end, --@meta
on_stack = function(frames) end, ---@meta
on_locals = function(frame, vars) end, ---@meta
on_evaluated = function(expr, ok, value) end, ---@meta
}
local debugger = {
runner = runner, -- Set here as member to let runners extend this base class
debugwindow = nil, ---@type JPDebugView
debugrunner = nil, ---@type runner|nil
state = "idle",
}
function debugger.log(msg)
core.log("[jpdebug][debugger] %s", msg)
if debugger.debugwindow then debugger.debugwindow:push("meta", "debugger] "..msg) end
end
function debugger.error(msg)
core.error("[jpdebug][debugger]"..msg)
if debugger.debugwindow then debugger.debugwindow:push("meta", "debugger] ERROR: "..msg) end
end
function debugger.on_stdout(msg)
if debugger.debugwindow then debugger.debugwindow:push("stdout", msg) end
end
function debugger.on_stderr(msg)
if debugger.debugwindow then debugger.debugwindow:push("stderr", msg) end
end
function debugger.is_running()
return debugger.debugrunner~=nil
end
function debugger.on_state(state)
debugger.log(string.format("state %s -> %s", debugger.state, state))
debugger.state = state
end
function debugger.on_break(file, line, reason)
debugger.log(string.format("breakpoint hit: %s:%d", file, line))
-- Temporary continue at unknown breaks
if file=='?' and line==-1 then
if debugger.debugrunner.caps.can_continue then
debugger.debugrunner:continue()
end
end
end
function debugger.run(target, name, r, view)
if debugger.debugrunner then
if debugger.state == "paused" then
debugger.debugrunner:continue()
else
debugger.error("Already an active session")
end
return
end
debugger.debugwindow = view
debugger.debugwindow:clear()
debugger.log(string.format("Running %s", name))
-- Create new runner object
debugger.debugrunner = r:new({
-- Set callbacks
log = debugger.log,
error = debugger.error,
on_stdout = debugger.on_stdout,
on_stderr = debugger.on_stderr,
on_exit = debugger.on_exit,
on_state = debugger.on_state,
on_break = debugger.on_break,
})
-- And run
debugger.debugrunner:run(target, name)
end
function debugger.stop()
if debugger.debugrunner then
debugger.debugrunner.on_exit = function() end
debugger.debugrunner:kill()
local exitcode = debugger.debugrunner:wait(1000)
-- TODO terminate if needed
debugger.log(string.format("... Stoped with exit code %d", exitcode))
end
debugger.debugrunner = nil
debugger.on_state('idle')
end
function debugger.on_exit(exitcode)
debugger.log(string.format("exit: %d", exitcode))
debugger.debugrunner = nil
debugger.on_state('idle')
end
return debugger