dbg/dd.lua

176 lines
4.3 KiB
Lua

local debug = ...
local nil_placeholder = {}
local function dd(level, caught_err)
level = (level or 1) + 1 -- add one stack level for this func (dd)
print(dbg.traceback(level))
local func = debug.getinfo(level, "f").func
local func_env = getfenv(level)
level = level + 3 -- __[new]index + loaded chunk + xpcall
local mt = {
__index = function(env, varname)
-- Debug locals
local val = env._L[varname]
if val ~= nil then
if val == nil_placeholder then
return nil
end
return val
end
-- Locals of the caller
local i = 1
while true do
local name, value = debug.getlocal(level, i)
if name == nil then break end
if varname == name then return value end
i = i + 1
end
-- Upvalues of the caller
i = 1
while true do
local name, value = debug.getupvalue(func, i)
if name == nil then break end
if varname == name then return value end
i = i + 1
end
return func_env[varname]
end,
__newindex = function(env, varname, value)
-- Debug locals
if env._L[varname] ~= nil then
if value == nil then value = nil_placeholder end
env._L[varname] = value
return
end
-- Locals
local i = 1
while true do
local name = debug.getlocal(level, i)
if name == nil then break end
if varname == name then
debug.setlocal(level, i, value)
return
end
i = i + 1
end
-- Upvalues
i = 1
while true do
local name = debug.getupvalue(func, i)
if name == nil then break end
if varname == name then
debug.setupvalue(func, i, value)
return
end
i = i + 1
end
func_env[varname] = value -- if local by default - how to access parent env then? _ENV?
end
}
-- TODO (?) special debug & dbg wrappers with stack level offsets
-- Functions: debug.(getinfo|[gs]et(local|upvalue)|traceback), [gs]etfenv, dbg.(* \ getargs)
local env = setmetatable({_L = {}, _G = _G, _ENV = func_env}, mt)
-- Source buffer
local buf, buf_i = {}, 0
local function getbuf()
buf_i = buf_i + 1
if not buf[buf_i] then return end
return buf[buf_i] .. "\n"
end
local function loadbuf()
buf_i = 0
return load(getbuf, "=stdin")
end
while true do
io.write(#buf == 0 and "dbg> " or ("[%d]> "):format(#buf + 1))
local line = io.read()
if not line then -- EOF
print()
minetest.request_shutdown("debugging", true, 0)
break
end
if line == "cont" or (caught_err and line == "err") then
return line == "err"
end
local chunk, err, continuation
if line:match"^%s+$" then -- skip spacing-only lines
continuation = #buf ~= 0
else
if line:match"^=" then
line = "return " .. line:sub(2)
end
buf[#buf + 1] = line
chunk, err = loadbuf()
if #buf == 1 and not chunk and not line:match"^return " then
-- Try implicit return
buf = {"return " .. line}
chunk, err = loadbuf()
end
continuation = err and err:find"<eof>" -- same hack as used in the Lua REPL
end
if chunk then
setfenv(chunk, env)
local hook, mask, count = debug.gethook()
local function restore_hook()
return debug.sethook(hook, mask, count)
end
debug.sethook(function()
if debug.getinfo(2, "f").func ~= chunk then return end
local i = 1
while true do
local name, value = debug.getlocal(2, i)
if name == nil then break end
if value == nil then value = nil_placeholder end
env._L[name] = value
i = i + 1
end
end, "r");
(function(status, ...)
if status then
restore_hook()
dbg.pp(...)
end
end)(xpcall(chunk, function(error)
restore_hook()
print(dbg._traceback(3, chunk)) -- this handler + [C]: function error -> 2 levels to skip
io.write"error: "; dbg.pp(error)
end))
buf = {} -- clear buffer
elseif continuation then
if #buf == 1 then -- overwrite first line
io.write("\27[F[1]> ", buf[1], "\n")
end
else
io.write("syntax error: ", err, "\n")
buf = {} -- clear buffer
end
end
end
function dbg.dd(level)
return dd(level)
end
local error = error -- localize error to allow overriding _G.error = dbg.error
function dbg.error(msg, level)
print("caught error: "); dbg.pp(msg)
if dd((level or 1) + 1, true) then
return error(msg, level)
end
end
function dbg.assert(value, msg)
if not value then dbg.error(msg or "assertion failed!") end
return value
end