From f5a9331ed140fcb2f338b7b7a0b380b0b7efc407 Mon Sep 17 00:00:00 2001 From: Timur Safin <tsafin@tarantool.org> Date: Mon, 1 Aug 2022 02:44:42 +0300 Subject: [PATCH] debugger: refactor commands helper Create command matcher and help map using convenient builder function. Commands described in a form: ``` {'f.inish|step_out', 'step out', cmd_finish}, {'p.rint $expression', 'execute the expression', cmd_print}, ``` where we could introduce multiple aliases, commands with arguments, commands descriptions and function handlers. NO_DOC=internal NO_CHANGELOG=internal NO_TEST=internal --- third_party/lua/README-luadebug.md | 39 ++++--- third_party/lua/luadebug.lua | 168 +++++++++++++++++++---------- 2 files changed, 139 insertions(+), 68 deletions(-) diff --git a/third_party/lua/README-luadebug.md b/third_party/lua/README-luadebug.md index b7f9d823b3..4a94dd03f6 100644 --- a/third_party/lua/README-luadebug.md +++ b/third_party/lua/README-luadebug.md @@ -57,19 +57,32 @@ Debugger Commands: If you have used other CLI debuggers, debugger.lua shouldn't be surprising. I didn't make a fancy parser, so the commands are just single letters. Since the debugger is pretty simple there are only a small handful of commands anwyay. - [return] - re-run last command - c(ontinue) - contiue execution - s(tep) - step forward by one line (into functions) - n(ext) - step forward by one line (skipping over functions) - p(rint) [expression] - execute the expression and print the result - f(inish) - step forward until exiting the current function - u(p) - move up the stack by one frame - d(own) - move down the stack by one frame - w(here) [line count] - print source code around the current line - t(race) - print the stack trace - l(ocals) - print the function arguments, locals and upvalues. - h(elp) - print this message - q(uit) - halt execution + c|cont|continue + - continue execution + d|down + - move down the stack by one frame + e|eval $expression + - execute the statement + f|finish|step_out + - step forward until exiting the current function + h|help|? + - print this help message + l|locals + - print the function arguments, locals and upvalues + n|next|step_over + - step forward by one line (skipping over functions) + p|print $expression + - execute the expression and print the result + q|quit + - exit debugger + s|st|step|step_into + - step forward by one line (into functions) + t|trace|bt + - print the stack trace + u|up + - move up the stack by one frame + w|where $linecount + - print source code around the current line If you've never used a command line debugger before, start a nice warm cozy fire, run tutorial.lua, and open it up in your favorite editor so you can follow along. diff --git a/third_party/lua/luadebug.lua b/third_party/lua/luadebug.lua index f352ddcba4..9440ea3974 100644 --- a/third_party/lua/luadebug.lua +++ b/third_party/lua/luadebug.lua @@ -332,6 +332,11 @@ local function cmd_finish() return true, offset < 0 and hook_factory(offset - 1) or hook_finish end +-- simply continue execution +local function cmd_continue() + return true +end + local function cmd_print(expr) local env = local_bindings(1, true) local chunk = compile_chunk("return " .. expr, env) @@ -468,63 +473,109 @@ local function cmd_locals() return false end +local gen_commands + local function cmd_help() - dbg.write("" - .. COLOR_BLUE .. " <return>" .. GREEN_CARET .. "re-run last command\n" - .. COLOR_BLUE .. " c" .. COLOR_YELLOW .. "(ontinue)" .. GREEN_CARET .. "continue execution\n" - .. COLOR_BLUE .. " s" .. COLOR_YELLOW .. "(tep)" .. GREEN_CARET .. - "step forward by one line (into functions)\n" - .. - COLOR_BLUE .. " n" .. - COLOR_YELLOW .. "(ext)" .. GREEN_CARET .. "step forward by one line (skipping over functions)\n" - .. COLOR_BLUE .. - " f" .. COLOR_YELLOW .. "(inish)" .. GREEN_CARET .. "step forward until exiting the current function\n" - .. COLOR_BLUE .. " u" .. COLOR_YELLOW .. "(p)" .. GREEN_CARET .. "move up the stack by one frame\n" - .. COLOR_BLUE .. " d" .. COLOR_YELLOW .. "(own)" .. GREEN_CARET .. "move down the stack by one frame\n" - .. - COLOR_BLUE .. - " w" .. - COLOR_YELLOW .. - "(here) " .. COLOR_BLUE .. "[line count]" .. GREEN_CARET .. "print source code around the current line\n" - .. COLOR_BLUE .. - " e" .. COLOR_YELLOW .. "(val) " .. COLOR_BLUE .. "[statement]" .. GREEN_CARET .. "execute the statement\n" - .. - COLOR_BLUE .. - " p" .. - COLOR_YELLOW .. - "(rint) " .. COLOR_BLUE .. "[expression]" .. GREEN_CARET .. "execute the expression and print the result\n" - .. COLOR_BLUE .. " t" .. COLOR_YELLOW .. "(race)" .. GREEN_CARET .. "print the stack trace\n" - .. - COLOR_BLUE .. - " l" .. COLOR_YELLOW .. "(ocals)" .. GREEN_CARET .. "print the function arguments, locals and upvalues.\n" - .. COLOR_BLUE .. " h" .. COLOR_YELLOW .. "(elp)" .. GREEN_CARET .. "print this message\n" - .. COLOR_BLUE .. " q" .. COLOR_YELLOW .. "(uit)" .. GREEN_CARET .. "halt execution\n" - ) + for _, v in ipairs(gen_commands) do + local map = gen_commands[v] + if #map.aliases > 0 then + local fun = require 'fun' + local txt = '' + fun.each(function(x) txt = txt .. '|' .. color_yellow(x) end, + map.aliases) + print(color_blue(v) .. '|' .. string.sub(txt, 2, #txt) .. ' ' .. + (map.arg or '')) + else + print(color_blue(v) .. ' ' .. (map.arg or '')); + end + print(' - ' .. map.help) + end return false end -local last_cmd = false - -local commands = { - ["^c$"] = function() return true end, - ["^s$"] = cmd_step, - ["^n$"] = cmd_next, - ["^f$"] = cmd_finish, - ["^p%s+(.*)$"] = cmd_print, - ["^e%s+(.*)$"] = cmd_eval, - ["^u$"] = cmd_up, - ["^d$"] = cmd_down, - ["^w%s*(%d*)$"] = cmd_where, - ["^t$"] = cmd_trace, - ["^l$"] = cmd_locals, - ["^h$"] = cmd_help, - ["^q$"] = function() dbg.exit(0); return true end, +local function cmd_quit() + dbg.exit(0) + return true +end + +local commands_help = { + {'c.ont.inue', 'continue execution', cmd_continue}, + {'d.own', 'move down the stack by one frame', cmd_down}, + {'e.val $expression', 'execute the statement', cmd_eval}, + {'f.inish|step_out', 'step forward until exiting the current function', cmd_finish}, + {'h.elp|?', 'print this help message', cmd_help}, + {'l.ocals', 'print the function arguments, locals and upvalues', cmd_locals}, + {'n.ext|step_over', 'step forward by one line (skipping over functions)', cmd_next}, + {'p.rint $expression', 'execute the expression and print the result', cmd_print}, + {'q.uit', 'exit debugger', cmd_quit}, + {'s.t.ep|step_into', 'step forward by one line (into functions)', cmd_step}, + {'t.race|bt', 'print the stack trace', cmd_trace}, + {'u.p', 'move up the stack by one frame', cmd_up}, + {'w.here $linecount', 'print source code around the current line', cmd_where}, } +local function build_commands_map(commands) + local gen_commands = {} + + for _, cmds in ipairs(commands) do + local c, h, f = unpack(cmds) + local first = true + local main_cmd + local pattern = '^[^%s]+%s+([^%s]+)' + + for subcmds in c:gmatch('[^|]+') do + local arg = subcmds:match(pattern) + subcmds = subcmds:match('^([^%s]+)') + local cmd = '' + local gen = subcmds:gmatch('[^.]+') + local prefix = gen() + local suffix = '' + local segment = prefix + + -- remember the first segment (main shortcut for command) + if first then + main_cmd = prefix + end + + repeat + cmd = cmd .. segment + gen_commands[cmd] = { + help = h, + handler = f, + first = first, + suffix = suffix, + aliases = {}, + arg = arg + } + if first then + table.insert(gen_commands, main_cmd) + else + assert(#main_cmd > 0) + table.insert(gen_commands[main_cmd].aliases, cmd) + end + first = false + segment = gen() + suffix = suffix .. (segment or '') + until not segment + end + end + return gen_commands +end + +gen_commands = build_commands_map(commands_help) + +local last_cmd = false + +-- Recognize a command, then return command handler, +-- 1st argument passed, and flag what argument is expected. local function match_command(line) - for pat, func in pairs(commands) do - -- Return the matching command and capture argument. - if line:find(pat) then return func, line:match(pat) end + local gen = line:gmatch('[^%s]+') + local cmd = gen() + local arg1st = gen() + if not gen_commands[cmd] then + return nil + else + return gen_commands[cmd].handler, arg1st, gen_commands[cmd].arg end end @@ -537,11 +588,18 @@ local function run_command(line) -- Re-execute the last command if you press return. if line == "" then line = last_cmd or "h" end - local command, command_arg = match_command(line) - if command then - last_cmd = line - -- unpack({...}) prevents tail call elimination so the stack frame indices are predictable. - return unpack({ command(command_arg) }) + local handler, command_arg, arg_expected = match_command(line) + if handler then + if arg_expected and command_arg == nil then + dbg_writeln(color_red("Error:") .. + " command expects argument, but none received '%s'.\n" .. + "Type 'h' and press return for a command list.", line) + return false + else + last_cmd = line + -- unpack({...}) prevents tail call elimination so the stack frame indices are predictable. + return unpack({ handler(command_arg) }) + end elseif dbg.cfg.auto_eval then return unpack({ cmd_eval(line) }) else -- GitLab