Lua debugger part 1

This commit is contained in:
kepler155c@gmail.com
2020-05-23 21:44:55 -06:00
parent 2c27787f27
commit cb58a553f5
11 changed files with 923 additions and 349 deletions

View File

@@ -335,92 +335,23 @@ local page = UI.Page {
return UI.SlideOut.eventHandler(self, event)
end,
},
quick_open = UI.SlideOut {
filter_entry = UI.TextEntry {
x = 2, y = 2, ex = -2,
shadowText = 'File name',
accelerators = {
[ 'enter' ] = 'accept',
[ 'up' ] = 'grid_up',
[ 'down' ] = 'grid_down',
},
},
grid = UI.ScrollingGrid {
x = 2, y = 3, ex = -2, ey = -4,
disableHeader = true,
columns = {
{ key = 'name' },
{ key = 'dir', textColor = 'lightGray' },
},
accelerators = {
grid_select = 'accept',
},
},
cancel = UI.Button {
x = -9, y = -2,
text = 'Cancel',
event = 'slide_hide',
},
apply_filter = function(self, filter)
if filter then
filter = filter:lower()
self.grid.sortColumn = 'score'
for _,v in pairs(self.grid.values) do
v.score = -fuzzy(v.lname, filter)
end
else
self.grid.sortColumn = 'lname'
end
self.grid:update()
self.grid:setIndex(1)
end,
quick_open = UI.QuickSelect {
modal = true,
enable = function() end,
show = function(self)
local function recurse(dir)
local files = fs.list(dir)
for _,f in ipairs(files) do
local fullName = fs.combine(dir, f)
if fs.native.isDir(fullName) then -- skip virtual dirs
if f ~= '.git' then recurse(fullName) end
else
_insert(self.grid.values, {
name = f,
dir = dir,
lname = f:lower(),
fullName = fullName,
})
end
end
end
recurse('')
self:apply_filter()
self.filter_entry:reset()
UI.SlideOut.show(self)
UI.QuickSelect.enable(self)
self:focusFirst()
self:draw()
self:addTransition('expandUp', { easing = 'outBounce', ticks = 12 })
end,
eventHandler = function(self, event)
if event.type == 'grid_up' then
self.grid:emit({ type = 'scroll_up' })
elseif event.type == 'grid_down' then
self.grid:emit({ type = 'scroll_down' })
elseif event.type == 'accept' then
local sel = self.grid:getSelected()
if sel then
actions.process('open', sel.fullName)
self:hide()
if event.type == 'select_cancel' then
self:disable()
elseif event.type == 'select_file' then
self:disable()
actions.process('open', event.file)
end
elseif event.type == 'text_change' then
self:apply_filter(event.text)
self.grid:draw()
else
return UI.SlideOut.eventHandler(self, event)
end
return true
return UI.QuickSelect.eventHandler(self, event)
end,
},
completions = UI.SlideOut {

6
debugger/.package Normal file
View File

@@ -0,0 +1,6 @@
{
title = 'Lua Debugger',
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/debugger',
description = [[Lua interactive debugger]],
license = 'MIT',
}

201
debugger/apis/init.lua Normal file
View File

@@ -0,0 +1,201 @@
--[[
some portions from https://github.com/slembcke/debugger.lua
]]
local fs = _G.fs
local dbg = { }
local function hookBreakpoint(info)
if dbg.breakpoints then
for _,v in pairs(dbg.breakpoints) do
if v.line == info.currentline and v.file == info.short_src then
return true
end
end
end
end
local function hookFunction(fn)
return function(info)
return info.func == fn
end
end
local function hookStep()
local co = coroutine.running()
return function()
return co == coroutine.running()
end
end
local function hookStepStacksize(n)
local co = coroutine.running()
local i = 2
while true do
local info = debug.getinfo(i)
if not info then
break
end
i = i + 1
end
return function()
if co == coroutine.running() then
if not debug.getinfo(i - n) then
return true
end
end
end
end
local function hookStepOut()
return hookStepStacksize(1)
end
local function hookStepOver()
return hookStepStacksize(0)
end
local hookEval = function() end
-- Create a table of all the locally accessible variables.
-- Globals are not included when running the locals command
local function local_bindings(offset, stack_inspect_offset)
offset = offset + 1 + stack_inspect_offset -- add this function to the offset
local func = debug.getinfo(offset).func
local bindings = {}
-- Retrieve the upvalues
do local i = 1; while true do
local name, value = debug.getupvalue(func, i)
if not name then break end
bindings[name] = value
i = i + 1
end end
-- Retrieve the locals (overwriting any upvalues)
do local i = 1; while true do
local name, value = debug.getlocal(offset, i)
if not name then break end
bindings[name] = value
i = i + 1
end end
-- Retrieve the varargs (works in Lua 5.2 and LuaJIT)
local varargs = {}
do local i = 1; while true do
local name, value = debug.getlocal(offset, -i)
if not name then break end
varargs[i] = value
i = i + 1
end end
if #varargs > 0 then bindings["..."] = varargs end
return bindings
end
local function get_trace(offset, stack_inspect_offset)
local function format_loc(file, line) return file..":"..line end
local function format_stack_frame_info(info)
local filename = info.source:match("@(.*)")
local source = filename and fs.getName(filename) or info.short_src
local namewhat = (info.namewhat == "" and "chunk at" or info.namewhat)
local name = (info.name and "'"..info.name.."'" or format_loc(source, info.linedefined))
return format_loc(source, info.currentline).." in "..namewhat.." "..name
end
offset = offset + 1 -- add this function to the offset
local t = { }
local i = 0
while true do
local info = debug.getinfo(offset + i)
if not info then break end
t[i] = {
index = i,
current = (i == stack_inspect_offset),
desc = format_stack_frame_info(info),
info = info,
}
i = i + 1
end
return t
end
local inHook = false
local function hook()
local info = debug.getinfo(2)
if info.currentline < 0 then
return
end
if not inHook and hookEval(info) then
inHook = true
local offset = 2 -- the offset from this function to the code being debugged
local inspectOffset = 0
repeat
local done = true
local snapshot = {
info = debug.getinfo(offset + inspectOffset),
locals = local_bindings(offset, inspectOffset),
stack = get_trace(offset, inspectOffset),
}
inspectOffset = 0 -- reset
local cmd, param = dbg.read(snapshot)
if cmd == 's' then
hookEval = hookStep()
elseif cmd == 'n' then
hookEval = hookStepOver()
elseif cmd == 'f' then
hookEval = hookStepOut()
elseif cmd == 'c' then
hookEval = hookBreakpoint
elseif cmd == 'd' then -- detach
debug.sethook()
elseif cmd == 'q' then
os.exit(0)
elseif cmd == 'b' then
dbg.breakpoints = param
done = false
elseif cmd == 'i' then
-- inspect stack at this offset
inspectOffset = param
done = false
end
until done
inHook = false
end
end
debug.sethook(hook, 'l')
-- Expose the debugger's functions
dbg.hook = hook
dbg.exit = function(err) os.exit(err) end
dbg.stopIn = function(fn)
hookEval = hookFunction(fn)
end
dbg.debugger = nil
dbg.read = function(info)
_G._pinfo = info
os.sleep(0) -- this is important ...
dbg.debugger:resume('debugger', 'info', info)
while true do
local _, cmd, args = os.pullEvent('debugger')
if cmd == 'b' then
dbg.breakpoints = args
else
return cmd, args
end
end
end
return dbg

407
debugger/debug.lua Normal file
View File

@@ -0,0 +1,407 @@
local Config = require('opus.config')
local Event = require('opus.event')
local UI = require('opus.ui')
local Util = require('opus.util')
local fs = _G.fs
local getfenv = _G.getfenv
local kernel = _G.kernel
local multishell = _ENV.multishell
local shell = _ENV.shell
local args = { ... }
local filename = shell.resolveProgram(table.remove(args, 1))
if not filename then
error('file not found')
end
local config = Config.load('debugger')
if not config.filename then
config.filename = { }
end
local breakpoints = config.filename
local currentFile
local debugFile, debugLine
local debugger = kernel.getCurrent()
local client
local function startClient()
local env = kernel.makeEnv(_ENV)
local clientId = multishell.openTab(nil, {
env = env,
title = fs.getName(filename):match('([^%.]+)'),
args = args,
fn = function(...)
local dbg = require('debugger')
local fn = loadfile(filename, env)
local cocreate = coroutine.create
env.coroutine = require('opus.util').shallowCopy(coroutine)
env.coroutine.create = function(f, ...)
local co = cocreate(f, ...)
debug.sethook(co, dbg.hook, "l")
return co
end
dbg.debugger = debugger
dbg.breakpoints = breakpoints
dbg.stopIn(fn)
fn(...)
end,
})
client = kernel.find(clientId)
end
local function loadSource(file)
currentFile = file:match('@?(.*)')
local src = { }
local lines = Util.readLines(file:match('@?(.*)'))
if lines then
for i = 1, #lines do
table.insert(src, { line = i, source = lines[i] })
end
end
return src
end
local function message(...)
client:resume('debugger', ...)
end
local page = UI.Page {
menuBar = UI.MenuBar {
buttons = {
{ text = 'Continue', event = 'cmd', cmd = 'c' },
{ text = 'Step', event = 'cmd', cmd = 's' },
{ text = 'Step Over', event = 'cmd', cmd = 'n' },
{ text = 'Step Out', event = 'cmd', cmd = 'f' },
{ text = 'Restart', event = 'restart', width = 9, ex = -1 },
},
},
container = UI.Window {
y = 2, ey = '50%',
locals = UI.ScrollingGrid {
ey = -2,
disableHeader = true,
columns = {
{ heading = 'Key', key = 'name' },
{ heading = 'Value', key = 'value', textColor = 'yellow' },
},
--sortColumn = 'name',
autospace = true,
accelerators = {
grid_select = 'show_variable',
},
},
statusBar = UI.StatusBar {
ex = -7, y = -1,
backgroundColor = 'primary',
textColor = 'white',
},
UI.Button {
y = -1, x = -6,
event = 'open',
text = 'Open',
}
},
tabs = UI.Tabs {
y = '50%',
source = UI.Tab {
title = 'Source',
index = 1,
grid = UI.ScrollingGrid {
disableHeader = true,
columns = {
{ key = 'marker', width = 1 },
{ key = 'line', textColor = 'cyan', width = 4 },
{ heading = 'heading', key = 'source' },
},
getDisplayValues = function(_, row)
for _,v in pairs(breakpoints) do
if v.file == currentFile and v.line == row.line then
return {
marker = '!',
line = row.line,
source = row.source,
}
end
end
return row
end,
getRowTextColor = function(self, row, selected)
return row.line == debugLine and currentFile == debugFile and 'yellow'
or UI.Grid.getRowTextColor(self, row, selected)
end,
eventHandler = function(self, event)
if event.type == 'grid_select' then
self:emit({
type = 'toggle_breakpoint',
file = currentFile,
line = event.selected.line,
})
end
return UI.Grid.eventHandler(self, event)
end,
},
},
stack = UI.Tab {
title = 'Stack',
index = 3,
grid = UI.ScrollingGrid {
columns = {
{ key = 'index', width = 2 },
{ heading = 'heading', key = 'desc' },
},
getRowTextColor = function(self, row, selected)
return row.current and 'yellow'
or UI.Grid.getRowTextColor(self, row, selected)
end,
sortColumn = 'index',
},
eventHandler = function(self, event)
if event.type == 'grid_select' then
message('r', event.selected.index)
else
return UI.Grid.eventHandler(self, event)
end
end,
},
env = UI.Tab {
title = 'Env',
index = 4,
grid = UI.ScrollingGrid {
columns = {
{ heading = 'Key', key = 'name' },
{ heading = 'Value', key = 'value', textColor = 'yellow' },
},
autospace = true,
accelerators = {
grid_select = 'show_variable',
},
sortCompare = function() end,
},
},
breaks = UI.Tab {
title = 'Breakpoints',
index = 2,
menuBar = UI.MenuBar {
buttons = {
{ text = 'Toggle', event = 'toggle' },
{ text = 'Remove', event = 'remove' },
{ text = 'Clear', event = 'clear' },
},
},
grid = UI.ScrollingGrid {
y = 2,
columns = {
{ heading = 'Line', key = 'line', width = 5 },
{ heading = 'Name', key = 'short' },
{ heading = 'Path', key = 'path', textColor = 'lightGray' },
},
values = breakpoints,
autospace = true,
getRowTextColor = function(self, row, selected)
return row.disabled and 'lightGray'
or UI.Grid.getRowTextColor(self, row, selected)
end,
},
eventHandler = function(self, event)
if event.type == 'clear' then
Util.clear(self.grid.values)
self:emit({ type = 'update_breakpoints' })
elseif event.type == 'toggle' then
local bp = self.grid:getSelected()
if bp then
bp.disabled = not bp.disabled
self:emit({ type = 'update_breakpoints' })
end
elseif event.type == 'grid_select' then
self:emit({
type = 'open_file',
file = event.selected.file,
line = event.selected.line,
})
elseif event.type == 'remove' then
local bp = self.grid:getSelected()
if bp then
Util.removeByValue(self.grid.values, bp)
self:emit({ type = 'update_breakpoints' })
end
end
return UI.Tab.eventHandler(self, event)
end,
},
},
quick_open = UI.QuickSelect {
modal = true,
enable = function() end,
show = function(self)
UI.QuickSelect.enable(self)
self:focusFirst()
self:draw()
self:addTransition('expandUp', { easing = 'outBounce', ticks = 12 })
end,
eventHandler = function(self, event)
if event.type == 'select_cancel' then
self:disable()
elseif event.type == 'select_file' then
self.parent:openFile(event.file)
self:disable()
end
return UI.QuickSelect.eventHandler(self, event)
end,
},
openFile = function(self, file, line)
if file ~= currentFile then
local src = loadSource(file)
self.tabs.source.grid:setValues(src)
end
if line then
self.tabs.source.grid:setIndex(#self.tabs.source.grid.values)
self.tabs.source.grid:setIndex(math.max(1, line - 4))
end
self.tabs.source.grid:setIndex(line or 1)
self.tabs:selectTab(self.tabs.source)
if currentFile == debugFile then
self.container.statusBar:setStatus(
string.format('%s : %d', fs.getName(file), debugLine))
else
self.container.statusBar:setStatus(fs.getName(file))
end
self:draw()
end,
eventHandler = function(self, event)
if event.type == 'cmd' then
self.container.statusBar:setStatus('Running...')
message(event.element.cmd)
elseif event.type == 'restart' then
if kernel.find(client.uid) then
client:resume('terminate')
end
startClient()
elseif event.type == 'open' then
self.quick_open:show()
elseif event.type == 'open_file' then
self:openFile(event.file, event.line)
elseif event.type == 'update_breakpoints' then
self.tabs.breaks.grid:update()
self.tabs.breaks.grid:draw()
self.tabs.source.grid:draw()
message('b', breakpoints)
Config.update('debugger', config)
elseif event.type == 'toggle_breakpoint' then
for k,v in pairs(breakpoints) do
if v.file == event.file and v.line == event.line then
table.remove(breakpoints, k)
self:emit({ type = 'update_breakpoints' })
return
end
end
table.insert(breakpoints, {
file = event.file,
line = event.line,
short = fs.getName(event.file),
path = fs.getDir(event.file),
})
self:emit({ type = 'update_breakpoints' })
elseif event.type == 'show_variable' then
if type(event.selected.raw) == 'table' then
if event.selected.children then
event.selected.children = nil
else
event.selected.children = { }
local t = event.selected.raw
for k,v in pairs(t) do
local depth = event.selected.depth or 0
table.insert(event.selected.children,
{ name = (' '):rep(depth + 2) .. k, value = tostring(v), raw = v, depth = depth + 2 })
end
table.sort(event.selected.children, function(a, b) return a.name < b.name end)
end
local t = { }
local function insert(values)
for _,v in pairs(values) do
table.insert(t, v)
if v.children then
insert(v.children)
end
end
end
insert(event.element.orig)
event.element:setValues(t)
event.element:draw()
end
end
return UI.Page.eventHandler(self, event)
end,
enable = function(self)
UI.Page.enable(self)
startClient()
end,
}
Event.on('debugger', function(_, cmd, data)
if cmd == 'info' then
kernel.raise(debugger.uid)
-- local tab
local t = { }
for k,v in pairs(data.locals or { }) do
table.insert(t, { name = k, value = tostring(v), raw = v })
end
table.sort(t, function(a, b) return a.name < b.name end)
page.container.locals:setValues(t)
page.container.locals.orig = Util.shallowCopy(t)
-- env tab
t = { }
for k,v in pairs(getfenv(data.info.func)) do
table.insert(t, { name = k, value = tostring(v), raw = v })
end
page.tabs.env.grid:setValues(t)
page.tabs.env.grid.orig = Util.shallowCopy(t)
debugLine = data.info.currentline
debugFile = data.info.source:match('@?(.*)')
-- source tab
page:openFile(debugFile, debugLine)
-- stack
page.tabs.stack.grid:setValues(data.stack)
page:draw()
page:sync()
end
end)
UI:setPage(page)
UI:start()
message('d')

28
debugger/example.lua Normal file
View File

@@ -0,0 +1,28 @@
local function m2(a)
return a
end
local function method(times)
local a = 2
-- use step out to return out of method
for _ = 1, times do
a = a * a
end
return m2(a)
end
print('before')
-- breakpoint
--dbg()
print('after')
local i = 2
print(i)
local res = method(i)
dofile("rom/modules/main/cc/expect.lua")
print(res)
print('result: ' .. res)
error('f')

View File

@@ -3,7 +3,7 @@ local shell = _ENV.shell
if not fs.exists('.mbs') then
print('Installing MBS')
shell.run('mbs download')
--shell.run('mbs download')
end
print('Initializing MBS')
shell.run('mbs startup')
--shell.run('mbs startup')

View File

@@ -4,6 +4,7 @@
[ 'ccemux' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/ccemux/.package',
[ 'common' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/common/.package',
[ 'core' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/core/.package',
[ 'debugger' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/debugger/.package',
[ 'farms' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/farms/.package',
-- [ 'forestry' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/forestry/.package',
[ 'games' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/games/.package',
@@ -11,7 +12,7 @@
[ 'gps' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/gps/.package',
[ 'lfs' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/lfs/.package',
[ 'lzwfs' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/lzwfs/.package',
[ 'mbs' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/mbs/.package',
-- [ 'mbs' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/mbs/.package',
[ 'milo' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/milo/.package',
[ 'miloApps' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/miloApps/.package',
[ 'miners' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/miners/.package',