├── .gitignore ├── src ├── break_point.lua ├── writer.lua ├── command_factory │ ├── run_command_factory.lua │ ├── eval_command_factory.lua │ ├── defined_line_command_factory.lua │ ├── step_command_factory.lua │ ├── vars_command_factory.lua │ ├── profile_command_factory.lua │ ├── list_command_factory.lua │ ├── break_point_command_factory.lua │ ├── watch_command_factory.lua │ └── info_command_factory.lua ├── reader.lua ├── wrapper.lua ├── prompt.lua ├── break_point_manager.lua ├── json.lua ├── coroutine_debugger.lua ├── profiler.lua ├── var_info.lua ├── watch_manager.lua ├── evaluator.lua ├── step_execute_manager.lua ├── utils.lua ├── context.lua └── lupe.lua ├── concat.sh ├── README.md └── lupe.lua /.gitignore: -------------------------------------------------------------------------------- 1 | HEADER 2 | -------------------------------------------------------------------------------- /src/break_point.lua: -------------------------------------------------------------------------------- 1 | --- break_point.lua 2 | 3 | --- ブレークポイントを表すクラス. 4 | local BreakPoint = {} 5 | 6 | function BreakPoint.create(source, line) 7 | 8 | local m = { 9 | source = source, 10 | line = line, 11 | } 12 | 13 | --- JSONにする. 14 | --- ソースを相対パスにする. 15 | function m:toJSON() 16 | local t = { 17 | source = utils:withoutPrefixSource(self.source), 18 | line = line, 19 | } 20 | 21 | return JSON.stringify(t) 22 | end 23 | 24 | return m 25 | end 26 | -------------------------------------------------------------------------------- /src/writer.lua: -------------------------------------------------------------------------------- 1 | --- writer.lua 2 | 3 | local Writer = {} 4 | 5 | Writer.TAG = { 6 | CALL_STACK = 'CALL_STACK', 7 | WATCHES = 'WATCHES', 8 | BREAK_POINTS = 'BREAK_POINTS', 9 | WARNING = 'WARNING', 10 | } 11 | 12 | function Writer.create() 13 | local m = {} 14 | 15 | function m:write(msg, tag) 16 | io.write(msg) 17 | end 18 | 19 | function m:writeln(msg, tag) 20 | msg = msg or '' 21 | self:write(msg .. '\n', tag) 22 | end 23 | 24 | return m 25 | end 26 | -------------------------------------------------------------------------------- /src/command_factory/run_command_factory.lua: -------------------------------------------------------------------------------- 1 | --- run_command_factory.lua 2 | 3 | --- runコマンドを作成するファクトリクラス. 4 | local RunCommandFactory = {} 5 | 6 | --- RunCommandFactoryを作る. 7 | function RunCommandFactory.create() 8 | local m = {} 9 | 10 | --- runコマンドを作る. 11 | -- line: 入力された文字列 12 | -- 入力された文字列が run コマンドに当てはまらなかった場合はnil 13 | -- そうでない場合 run コマンド 14 | function m:createCommand(line) 15 | if line == 'run' or line == 'r' then 16 | return function(debugger) 17 | return false 18 | end 19 | end 20 | return nil 21 | end 22 | 23 | return m 24 | end 25 | -------------------------------------------------------------------------------- /src/reader.lua: -------------------------------------------------------------------------------- 1 | --- reader.lua 2 | 3 | --- io.linesを使ってソースコードの各行をテーブルに格納して返す. 4 | -- source: ソースコード 5 | -- ソースコードを格納したテーブル 6 | local function _lines(source) 7 | local lines = {} 8 | for l in io.lines(source) do 9 | table.insert(lines, l) 10 | end 11 | return lines 12 | end 13 | 14 | local Reader = { 15 | -- 同じファイルを何度も開いても良いようにキャッシュする 16 | cache = {}, 17 | } 18 | 19 | function Reader.create(source) 20 | local m = { 21 | source = source, 22 | } 23 | 24 | function m:lines() 25 | if Reader.cache[self.source] then 26 | return Reader.cache[self.source] 27 | end 28 | 29 | return _lines(self.source) 30 | end 31 | 32 | return m 33 | end 34 | -------------------------------------------------------------------------------- /src/wrapper.lua: -------------------------------------------------------------------------------- 1 | --- wrapper.lua 2 | 3 | --- assertが呼ばれたら停止する 4 | local _assert = assert 5 | assert = function(...) 6 | local args = {...} 7 | if not args[1] then 8 | local debugger = rawget(_G, 'Lupe') 9 | debugger.writer:writeln('====== STOP BY ASSERT ======') 10 | debugger:traceback(args[2]) 11 | local debug_info = debug.getinfo(2) 12 | local var_infoes = VarInfo.getlocal(2) 13 | local context = Context.create(debug_info, var_infoes) 14 | if not debugger.call_stack[1] then 15 | debugger.call_stack[1] = context 16 | else 17 | debugger.call_stack[1]:update(context) 18 | end 19 | debugger:showPrompt(debugger.call_stack[1]) 20 | _assert(...) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /concat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | pushd `dirname $0` > /dev/null 4 | 5 | echo "-- LUPE" > HEADER 6 | echo "-- built at `date '+%Y-%m-%d %H:%M:%S'`" >> HEADER 7 | echo "-- Author: Takuya Ueda" >> HEADER 8 | echo >> HEADER 9 | 10 | cat \ 11 | HEADER\ 12 | src/utils.lua\ 13 | src/json.lua\ 14 | src/reader.lua\ 15 | src/writer.lua\ 16 | src/var_info.lua\ 17 | src/context.lua\ 18 | src/profiler.lua\ 19 | src/evaluator.lua\ 20 | src/break_point.lua\ 21 | src/break_point_manager.lua\ 22 | src/step_execute_manager.lua\ 23 | src/watch_manager.lua\ 24 | src/command_factory/*.lua\ 25 | src/prompt.lua\ 26 | src/coroutine_debugger.lua\ 27 | src/lupe.lua\ 28 | src/wrapper.lua\ 29 | > lupe.lua 30 | 31 | popd > /dev/null 32 | -------------------------------------------------------------------------------- /src/command_factory/eval_command_factory.lua: -------------------------------------------------------------------------------- 1 | --- eval_command_factory.lua 2 | 3 | --- Luaコードを実行するコマンドを作るファクトリクラス. 4 | local EvalCommandFactory = {} 5 | 6 | --- EvalCommandFactoryを作る. 7 | function EvalCommandFactory.create() 8 | local m = {} 9 | 10 | --- evalコマンドを作る. 11 | -- line: 入力された文字列 12 | -- evalコマンド 13 | function m:createCommand(line) 14 | return function(debugger) 15 | local context = debugger.call_stack[1] 16 | local evaluator = Evaluator.create(context) 17 | local _, err = evaluator:eval(line) 18 | 19 | if err then 20 | debugger.writer:writeln("Command not found or given Lua chunk has error.") 21 | debugger.writer:writeln('Eval ERROR: ' .. err) 22 | end 23 | 24 | -- ウォッチ式の更新 25 | debugger.watch_manager:update(context) 26 | 27 | return true 28 | end 29 | end 30 | 31 | return m 32 | end 33 | -------------------------------------------------------------------------------- /src/command_factory/defined_line_command_factory.lua: -------------------------------------------------------------------------------- 1 | --- defined_line_command_factory.lua 2 | 3 | local DefinedLineCommandFactory = {} 4 | 5 | function DefinedLineCommandFactory.create() 6 | local m = {} 7 | 8 | --- listコマンドを作る. 9 | -- line: 入力された文字列 10 | -- 入力された文字列が definedLine コマンドに当てはまらなかった場合はnil 11 | -- そうでない場合 definedLine コマンド 12 | function m:createCommand(line) 13 | local cmd = utils:splitWords(line) 14 | if not cmd and #cmd <= 0 then 15 | return nil 16 | end 17 | 18 | if (cmd[1] == 'definedLine' or cmd[1] == 'd') and type(cmd[2]) == 'string' then 19 | return function(debugger) 20 | local var_name = cmd[2] 21 | local defined_line 22 | 23 | for level = 1, #debugger.call_stack do 24 | if debugger.call_stack[level].var_defined_lines[var_name] then 25 | defined_line = debugger.call_stack[level].var_defined_lines[var_name] 26 | break 27 | end 28 | end 29 | 30 | defined_line = defined_line or Context.global_defined_lines[var_name] 31 | if defined_line then 32 | local source = defined_line.source or '-' 33 | local line = defined_line.line or -1 34 | debugger.writer:writeln(string.format('%s:%d ', source, line)) 35 | end 36 | 37 | return true 38 | end 39 | end 40 | 41 | return nil 42 | end 43 | 44 | return m 45 | end 46 | -------------------------------------------------------------------------------- /src/command_factory/step_command_factory.lua: -------------------------------------------------------------------------------- 1 | -- step_command_factory.lua 2 | 3 | --- ステップ実行に関するコマンドを作るファクトリクラス. 4 | local StepCommandFactory = {} 5 | 6 | --- StepCommandFactoryを作る. 7 | function StepCommandFactory.create() 8 | local m = {} 9 | 10 | --- ステップ実行に関するコマンドを作る. 11 | -- line: 入力された文字列 12 | -- 入力された文字列から 13 | -- ・ステップオーバー 14 | -- ・ステップイン 15 | -- ・ステップアウト 16 | -- のいずれかのコマンドを返す. 17 | -- 上記のどれにも当てはまらなかったら,nil を返す. 18 | function m:createCommand(line) 19 | local cmd = utils:splitWords(line) 20 | if not cmd and #cmd <= 0 then 21 | return nil 22 | end 23 | 24 | -- ステップオーバー 25 | if cmd[1] == 'step' or cmd[1] == 's' then 26 | return function(debugger) 27 | debugger.step_execute_manager:setStepOver(debugger.call_stack, tonumber(cmd[2] or '1')) 28 | return false 29 | end 30 | end 31 | 32 | -- ステップイン 33 | if cmd[1] == 'stepIn' or cmd[1] == 'si' then 34 | return function(debugger) 35 | debugger.step_execute_manager:setStepIn(debugger.call_stack, tonumber(cmd[2] or '1')) 36 | return false 37 | end 38 | end 39 | 40 | -- ステップアウト 41 | if cmd[1] == 'stepOut' or cmd[1] == 'so' then 42 | return function(debugger) 43 | debugger.step_execute_manager:setStepOut(debugger.call_stack, tonumber(cmd[2] or '1')) 44 | return false 45 | end 46 | end 47 | 48 | return nil 49 | end 50 | 51 | return m 52 | end 53 | -------------------------------------------------------------------------------- /src/command_factory/vars_command_factory.lua: -------------------------------------------------------------------------------- 1 | --- vars_command_factory.lua 2 | 3 | local VarsCommandFactory = {} 4 | 5 | function VarsCommandFactory.create() 6 | local m = { 7 | showDefinedLine = false, 8 | } 9 | 10 | --- listコマンドを作る. 11 | -- line: 入力された文字列 12 | -- 入力された文字列が vars コマンドに当てはまらなかった場合はnil 13 | -- そうでない場合 vars コマンド 14 | function m:createCommand(line) 15 | local cmd = utils:splitWords(line) 16 | if not cmd and #cmd <= 0 then 17 | return nil 18 | end 19 | 20 | if cmd[1] == 'vars' or cmd[1] == 'v' then 21 | return function(debugger) 22 | local level = math.min(tonumber(cmd[2] or 1) or 1, #debugger.call_stack) 23 | local var_infoes = debugger.call_stack[level].var_infoes 24 | local var_defined_lines = debugger.call_stack[level].var_defined_lines 25 | local show_level = tonumber(cmd[3]) -- nilの場合はデフォルト値で表示される 26 | for _, var_info in ipairs(var_infoes) do 27 | if self.showDefinedLine then 28 | local source = var_defined_lines[var_info.name].source or '-' 29 | local line = var_defined_lines[var_info.name].line or -1 30 | debugger.writer:write(string.format('%s:%d ', source, line)) 31 | end 32 | debugger.writer:writeln(string.format('%s(%s): %s', var_info.name, var_info.value_type, utils:inspect(var_info.value, show_level))) 33 | end 34 | 35 | return true 36 | end 37 | end 38 | 39 | return nil 40 | end 41 | 42 | return m 43 | end 44 | -------------------------------------------------------------------------------- /src/prompt.lua: -------------------------------------------------------------------------------- 1 | --- prompt.lua 2 | 3 | --- プロンプトを扱うクラス. 4 | local Prompt = {} 5 | 6 | --- Promptを作る. 7 | function Prompt.create() 8 | 9 | local m = { 10 | command_factories = { 11 | StepCommandFactory.create(), -- ステップ実行 12 | RunCommandFactory.create(), -- Run 13 | BreakPointCommandFactory.create(), -- ブレークポイント 14 | InfoCommandFactory.create(), -- デバッグ情報の出力 15 | ListCommandFactory.create(), -- ソースコードの出力 16 | VarsCommandFactory.create(), -- 変数の出力 17 | DefinedLineCommandFactory.create(), -- 変数の宣言位置の出力 18 | WatchCommandFactory.create(), -- ウォッチ式 19 | ProfileCommandFactory.create(), -- プロファイラ 20 | EvalCommandFactory.create(), -- 式の評価(最後にしないとダメ) 21 | }, 22 | } 23 | 24 | --- 処理を再開するコマンドを受け付けるまで, 25 | --- コマンドの受付をループする. 26 | -- debugger: デバッガ 27 | function m:loop(debugger) 28 | while true do 29 | local is_loop = self:doCommand(debugger) 30 | if not is_loop then 31 | break 32 | end 33 | 34 | -- ループする場合はコールバックを呼んでおく 35 | debugger.callback(debugger) 36 | end 37 | end 38 | 39 | --- コマンドを受け付けて実行する. 40 | -- debugger: デバッガ 41 | function m:doCommand(debugger) 42 | debugger.writer:write('LUPE>') 43 | local line = io.read() 44 | for _, command_factory in pairs(self.command_factories) do 45 | local cmd = command_factory:createCommand(line) 46 | if cmd then 47 | return cmd(debugger) 48 | end 49 | end 50 | 51 | return true 52 | end 53 | 54 | return m 55 | end 56 | -------------------------------------------------------------------------------- /src/command_factory/profile_command_factory.lua: -------------------------------------------------------------------------------- 1 | --- profile_command_factory.lua 2 | 3 | --- プロファイルを行うためのコマンド 4 | local ProfileCommandFactory = {} 5 | 6 | --- ProfileCommandFactoryを作る. 7 | function ProfileCommandFactory.create() 8 | local m = { 9 | last_profiler = nil 10 | } 11 | 12 | --- プロファイリングに関するコマンドを作る. 13 | -- line: 入力された文字列 14 | -- 入力された文字列から 15 | -- ・プロファイラの開始 16 | -- ・プロファイル結果の出力 17 | -- ・プロファイラの終了 18 | -- のいずれかのコマンドを返す. 19 | -- 上記のどれにも当てはまらなかったら,nil を返す. 20 | function m:createCommand(line) 21 | local cmd = utils:splitWords(line) 22 | if not cmd and #cmd <= 0 then 23 | return nil 24 | end 25 | 26 | if cmd[1] == 'startProfile' or cmd[1] == 'sp' then 27 | return function(debugger) 28 | debugger:startProfile() 29 | debugger.writer:writeln('start profiler') 30 | return true 31 | end 32 | end 33 | 34 | if cmd[1] == 'profile' or cmd[1] == 'p' then 35 | return function(debugger) 36 | if not m.last_profiler then 37 | debugger.writer:writeln('ERROR: profiler is running or does not start', 'ERROR') 38 | return true 39 | end 40 | 41 | local summary = m.last_profiler:summary() 42 | if not next(summary) then 43 | return true 44 | end 45 | 46 | debugger.writer:writeln(JSON.stringify(summary)) 47 | return true 48 | end 49 | end 50 | 51 | if cmd[1] == 'endProfile' or cmd[1] == 'ep' then 52 | return function(debugger) 53 | m.last_profiler = debugger.profiler 54 | debugger:endProfile() 55 | debugger.writer:writeln('stop profiler') 56 | return true 57 | end 58 | end 59 | 60 | return nil 61 | end 62 | 63 | return m 64 | 65 | end 66 | -------------------------------------------------------------------------------- /src/command_factory/list_command_factory.lua: -------------------------------------------------------------------------------- 1 | --- list_command_factory.lua 2 | 3 | --- 現在の行の周辺の行を表示するコマンドを作るファクトリクラス. 4 | local ListCommandFactory = { 5 | -- デフォルトの表示する行数(現在の行の他に表示する上下の行数) 6 | DEFAULT_NUM_LINES = 3, 7 | } 8 | 9 | --- ListCommandFactoryを作る 10 | function ListCommandFactory.create() 11 | local m = {} 12 | 13 | --- listコマンドを作る. 14 | -- line: 入力された文字列 15 | -- 入力された文字列が list コマンドに当てはまらなかった場合はnil 16 | -- そうでない場合 list コマンド 17 | function m:createCommand(line) 18 | local cmd = utils:splitWords(line) 19 | if not cmd and #cmd <= 0 then 20 | return nil 21 | end 22 | 23 | if cmd[1] == 'list' or cmd[1] == 'l' then 24 | return function(debugger) 25 | local context = debugger.call_stack[1] 26 | local num_lines = tonumber(cmd[2] or ListCommandFactory.DEFAULT_NUM_LINES) 27 | local reader = Reader.create(utils:getSource(context)) 28 | local lines = reader:lines() 29 | 30 | for i = math.max(context.currentline - num_lines, 1), math.min(context.currentline + num_lines, #lines) do 31 | 32 | -- 現在の行の場合は>を出す 33 | if i == context.currentline then 34 | debugger.writer:write('>') 35 | else 36 | debugger.writer:write(' ') 37 | end 38 | 39 | -- ブレークポイントの場合は*を出す 40 | if debugger.break_point_manager:isBreakPoint(context.source, i) then 41 | debugger.writer:write('*') 42 | else 43 | debugger.writer:write(' ') 44 | end 45 | 46 | local fmt = '%' .. tostring(utils:numDigits(#lines)) .. 'd: %s' 47 | print(string.format(fmt, i, lines[i])) 48 | end 49 | 50 | return true 51 | end 52 | end 53 | return nil 54 | end 55 | 56 | return m 57 | 58 | end 59 | -------------------------------------------------------------------------------- /src/command_factory/break_point_command_factory.lua: -------------------------------------------------------------------------------- 1 | --- break_point_command_factory.lua 2 | 3 | local BreakPointCommandFactory = {} 4 | 5 | function BreakPointCommandFactory.create() 6 | local m = {} 7 | 8 | --- ブレークポイントに関するコマンドを作る. 9 | -- line: 入力された文字列 10 | -- 入力された文字列から 11 | -- ・ブレークポイントの追加 12 | -- ・ブレークポイントの削除 13 | -- ・ブレークポイントの一覧 14 | -- のいずれかのコマンドを返す. 15 | -- 上記のどれにも当てはまらなかったら,nil を返す. 16 | function m:createCommand(line) 17 | local cmd = utils:splitWords(line) 18 | if not cmd and #cmd <= 0 then 19 | return nil 20 | end 21 | 22 | -- ブレークポイントの追加 23 | if cmd[1] == 'addBreakPoint' or cmd[1] == 'ab' then 24 | return function(debugger) 25 | if cmd[3] then 26 | debugger.break_point_manager:add(utils:withPrefixSource(cmd[2]), tonumber(cmd[3])) 27 | else 28 | debugger.break_point_manager:add(debugger.call_stack[1].source, tonumber(cmd[2])) 29 | end 30 | return true 31 | end 32 | end 33 | 34 | -- ブレークポイントの削除 35 | if cmd[1] == 'removeBreakPoint' or cmd[1] == 'rb' then 36 | return function(debugger) 37 | if cmd[3] then 38 | debugger.break_point_manager:remove(utils:withPrefixSource(cmd[2]), tonumber(cmd[3])) 39 | else 40 | debugger.break_point_manager:remove(debugger.call_stack[1].source, tonumber(cmd[2])) 41 | end 42 | return true 43 | end 44 | end 45 | 46 | -- ブレークポイントの一覧 47 | if line == 'breakPointList' or line == 'bl' then 48 | return function(debugger) 49 | for id, _ in pairs(debugger.break_point_manager.break_points) do 50 | debugger.writer:writeln(id) 51 | end 52 | return true 53 | end 54 | end 55 | 56 | return nil 57 | end 58 | 59 | return m 60 | end 61 | -------------------------------------------------------------------------------- /src/break_point_manager.lua: -------------------------------------------------------------------------------- 1 | --- break_point_manager.lua 2 | 3 | --- ブレークポイントを管理するクラス. 4 | local BreakPointManager = {} 5 | 6 | --- BreakPointManagerを作る. 7 | function BreakPointManager.create() 8 | local m = { 9 | break_points = {}, 10 | } 11 | 12 | --- IDを取得する. 13 | -- source: ソースファイル 14 | -- line: 行番号 15 | function m:id(source, line) 16 | return string.format('%s:%d', source, line) 17 | end 18 | 19 | --- ブレークポイントを追加する. 20 | -- source: ソースファイル 21 | -- line: 行番号 22 | function m:add(source, line) 23 | local id = self:id(source, line) 24 | if self.break_points[id] then 25 | return 26 | end 27 | 28 | local break_point = BreakPoint.create(source, line) 29 | self.break_points[id] = break_point 30 | end 31 | 32 | --- 指定したブレークポイントを消す. 33 | -- source: ソースファイル 34 | -- line: 行番号 35 | function m:remove(source, line) 36 | local id = self:id(source, line) 37 | self.break_points[id] = nil 38 | end 39 | 40 | --- 設定されているブレークポイントをすべて消す. 41 | function m:clear() 42 | self.break_points = {} 43 | end 44 | 45 | --- 設定されているブレークポイントを取得する. 46 | -- すべてのブレークポイント. 47 | function m:getAll() 48 | local all_break_points = {} 49 | for _, break_point in pairs(self.break_points) do 50 | table.insert(all_break_points, break_point) 51 | end 52 | return all_break_points 53 | end 54 | 55 | --- 指定した箇所がブレークポイントかどうか取得する. 56 | -- source: ソースファイル 57 | -- line: 行番号 58 | function m:isBreakPoint(source, line) 59 | local id = self:id(source, line) 60 | return self.break_points[id] 61 | end 62 | 63 | --- ブレークポイントで止まるべきか取得する. 64 | -- call_stack: 現在のコールスタック 65 | function m:shouldStop(call_stack) 66 | local source = call_stack[1].source 67 | local currentline = call_stack[1].currentline 68 | return self:isBreakPoint(source, currentline) 69 | end 70 | 71 | return m 72 | end 73 | -------------------------------------------------------------------------------- /src/json.lua: -------------------------------------------------------------------------------- 1 | -- json.lua 2 | -- JSONのエンコーダを提供する. 3 | 4 | local JSON = {} 5 | 6 | local escapeTable = {} 7 | escapeTable['"'] = '\\"' 8 | escapeTable['\\'] = '\\\\' 9 | escapeTable['\b'] = '\\b' 10 | escapeTable['\f'] = '\\f' 11 | escapeTable['\n'] = '\\n' 12 | escapeTable['\r'] = '\\r' 13 | escapeTable['\t'] = '\\t' 14 | 15 | --- 文字列をエスケープする. 16 | -- str: エスケープする文字列 17 | function JSON.escape(str) 18 | local s = '' 19 | 20 | string.gsub(str, '.', function(c) 21 | if escapeTable[c] then 22 | s = s .. escapeTable[c] 23 | else 24 | s = s .. c 25 | end 26 | end) 27 | 28 | return s 29 | end 30 | 31 | --- 配列をJSONエンコードする. 32 | -- arry: 配列 33 | function JSON.stringifyArray(arry) 34 | local s = '' 35 | for i = 1, #arry do 36 | s = s .. JSON.stringify(arry[i]) .. ',' 37 | end 38 | s = string.sub(s, 1, string.len(s) - 1) 39 | 40 | return string.format('[%s]', s) 41 | end 42 | 43 | --- テーブルをJSONエンコードする. 44 | --- toJSONをメソッドとして持つ場合はそれを呼ぶ. 45 | -- tbl: テーブル 46 | function JSON.stringifyTable(tbl) 47 | if tbl.toJSON and type(tbl.toJSON) == 'function' then 48 | return tbl:toJSON() 49 | end 50 | 51 | local isArray = true 52 | local s = '' 53 | for k, v in pairs(tbl) do 54 | if type(k) ~= 'number' then 55 | isArray = false 56 | end 57 | 58 | s = s .. string.format('"%s":%s,', tostring(k), JSON.stringify(v)) 59 | end 60 | s = string.sub(s, 1, string.len(s) - 1) 61 | 62 | if isArray then 63 | return JSON.stringifyArray(tbl) 64 | end 65 | 66 | return string.format('{%s}', s) 67 | end 68 | 69 | --- JSONエンコードする. 70 | -- v: エンコードする値 71 | function JSON.stringify(v) 72 | local t = type(v) 73 | 74 | if t == 'table' then 75 | return JSON.stringifyTable(v) 76 | elseif t == 'string' then 77 | return '"' .. JSON.escape(v) .. '"' 78 | elseif t == 'function' or t == 'thread' or t == 'userdata' then 79 | return '"' .. tostring(v) .. '"' 80 | elseif t == 'nil' then 81 | return 'null' 82 | end 83 | 84 | return tostring(v) 85 | end 86 | -------------------------------------------------------------------------------- /src/coroutine_debugger.lua: -------------------------------------------------------------------------------- 1 | --- coroutine_debugger.lua 2 | 3 | --- コルーチンをデバッグするための機能を提供するクラス. 4 | local CoroutineDebugger = {} 5 | 6 | --- CoroutineDebuggerを作る. 7 | -- debugger: デバッガ本体 8 | function CoroutineDebugger.create(debugger) 9 | 10 | local m = { 11 | debugger = debugger, 12 | threads = {}, 13 | } 14 | 15 | --- Lua5.1用にsethookする. 16 | -- mode: hookのモード 17 | function m.sethook51(mode) 18 | local hook = debug.gethook() 19 | if not hook and m.debugger.is_started then 20 | debug.sethook(m.debugger.stop_callback, mode) 21 | end 22 | end 23 | 24 | --- Lua5.2用にsethookする. 25 | -- mode: hookのモード 26 | function m.sethook52(mode) 27 | local th = coroutine.running() 28 | local hook = debug.gethook(th) 29 | if not hook and m.debugger.is_started then 30 | debug.sethook(th, m.debugger.stop_callback, mode) 31 | table.insert(m.threads, th) 32 | end 33 | end 34 | 35 | -- Luaのバージョンによって,sethookの方法を分ける 36 | function m.sethook(mode) 37 | if _VERSION == 'Lua 5.2' then 38 | m.sethook52(mode) 39 | else 40 | m.sethook51(mode) 41 | end 42 | end 43 | 44 | --- コルーチンのデバッグを開始する. 45 | --- coroutine.createとcoroutine.wrapを上書いているため注意する. 46 | function m:start() 47 | self.cocreate = coroutine.create 48 | coroutine.create = function(func) 49 | return self.cocreate(function(...) 50 | m.sethook('crl') 51 | return func(...) 52 | end) 53 | end 54 | 55 | self.cowrap = coroutine.wrap 56 | coroutine.wrap = function(func) 57 | return self.cowrap(function(...) 58 | m.sethook('crl') 59 | return func(...) 60 | end) 61 | end 62 | end 63 | 64 | --- コルーチンのデバッグを停止する. 65 | --- Lua 5.1では,debug.sethookにthreadを渡せないため,うまく動作しない. 66 | function m:stop() 67 | coroutine.create = self.cocreate or coroutine.create 68 | coroutine.wrap = self.cowrap or coroutine.wrap 69 | for _, th in ipairs(self.threads) do 70 | debug.sethook(th) 71 | end 72 | end 73 | 74 | return m 75 | end 76 | -------------------------------------------------------------------------------- /src/profiler.lua: -------------------------------------------------------------------------------- 1 | --- profiler.lua 2 | 3 | --- プロファイルを行うクラス. 4 | local Profiler = {} 5 | 6 | --- Profilerを作る. 7 | -- callback: 記録されるたびに呼ばれるコールバック 8 | function Profiler.create(callback) 9 | 10 | local m = { 11 | profiles = {}, 12 | callback = callback or function() end, 13 | } 14 | 15 | --- 関数ごとのIDを生成する. 16 | -- context: 元にするコンテキスト 17 | function m:id(context) 18 | local name = context.name or 'NO_NAME' 19 | local source = utils:withoutPrefixSource(context.source) 20 | local line = context.linedefined 21 | return string.format('%s(%s:%d)<%s>', name, source, line, tostring(context.func)) 22 | end 23 | 24 | --- プロファイルを記録する. 25 | -- context: 記録する情報 26 | function m:record(context) 27 | local duration_ms, use_memory_kB = context:record() 28 | local id = self:id(context) 29 | if not self.profiles[id] then 30 | self.profiles[id] = {} 31 | end 32 | local profile = { 33 | duration_ms = duration_ms, 34 | use_memory_kB = use_memory_kB, 35 | } 36 | table.insert(self.profiles[id], profile) 37 | self.callback(profile, self.profiles) 38 | end 39 | 40 | --- 集計を行う. 41 | -- 各関数ごと経過時間(平均と合計),使用メモリ(平均と合計),呼び出し回数 42 | function m:summary() 43 | local summary = {} 44 | 45 | for id, func_profiles in pairs(self.profiles) do 46 | local sum = { 47 | duration_ms = 0, 48 | use_memory_kB = 0.0 49 | } 50 | for _, profile in ipairs(func_profiles) do 51 | sum.duration_ms = sum.duration_ms + profile.duration_ms 52 | sum.use_memory_kB = sum.use_memory_kB + profile.use_memory_kB 53 | end 54 | 55 | local average = { 56 | duration_ms = sum.duration_ms / math.max(#func_profiles, 1), 57 | use_memory_kB = sum.use_memory_kB / math.max(#func_profiles, 1), 58 | } 59 | summary[id] = { 60 | sum = sum, 61 | average = average, 62 | count = #func_profiles, 63 | } 64 | end 65 | 66 | return summary 67 | end 68 | 69 | return m 70 | end 71 | -------------------------------------------------------------------------------- /src/var_info.lua: -------------------------------------------------------------------------------- 1 | --- var_info.lua 2 | 3 | --- 変数情報を表すクラス. 4 | local VarInfo = {} 5 | 6 | --- VarInfoを作成する. 7 | -- name: 変数名 8 | -- value: 値 9 | -- index: 変数のインデックス 10 | -- is_upvalue: 上位値かどうか 11 | function VarInfo.create(name, value, index, is_upvalue) 12 | local m = { 13 | name = name, 14 | value_type = type(value), 15 | value = value, 16 | is_nil = (value == nil), 17 | index = index, 18 | new_value = value, 19 | is_upvalue = is_upvalue, 20 | } 21 | 22 | --- 指定したレベルで変数を更新する. 23 | -- level: レベル 24 | function m:update(level) 25 | self.value = self.new_value 26 | if is_upvalue then 27 | local func = debug.getinfo(level + 1, 'f').func 28 | debug.setupvalue(func, self.index, self.value) 29 | else 30 | debug.setlocal(level + 1, self.index, self.value) 31 | end 32 | end 33 | 34 | --- JSONに変換する. 35 | function m:toJSON() 36 | local t = { 37 | name = self.name, 38 | value_type = self.value_type, 39 | value = self.new_value, 40 | is_nil = self.is_nil, 41 | is_upvalue = is_upvalue, 42 | } 43 | return JSON.stringify(t) 44 | end 45 | 46 | return m 47 | end 48 | 49 | --- 指定したレベルのローカル変数をすべて取得する. 50 | -- level: レベル 51 | -- 指定したレベルで取得できる変数のVarInfoの配列 52 | function VarInfo.getlocal(level) 53 | 54 | local var_infoes = {} 55 | local defined_name = {} 56 | 57 | -- ローカル変数を取得する 58 | local local_index = 1 59 | while true do 60 | local name, value = debug.getlocal(level + 1, local_index) 61 | if not name then 62 | break 63 | end 64 | if name ~= '(*temporary)' then 65 | table.insert(var_infoes, VarInfo.create(name, value, local_index, false)) 66 | defined_name[name] = true 67 | end 68 | local_index = local_index + 1 69 | end 70 | 71 | -- 上位値を取得する 72 | local func = debug.getinfo(level + 1, 'f').func 73 | local up_index = 1 74 | while true do 75 | local name, value = debug.getupvalue(func, up_index) 76 | if not name then 77 | break 78 | end 79 | if _ENV == nil or value ~= _ENV then 80 | table.insert(var_infoes, VarInfo.create(name, value, up_index, true)) 81 | end 82 | up_index = up_index + 1 83 | end 84 | 85 | return var_infoes 86 | end 87 | -------------------------------------------------------------------------------- /src/watch_manager.lua: -------------------------------------------------------------------------------- 1 | --- watch_manager.lua 2 | 3 | --- ウォッチを管理するクラス. 4 | local WatchManager = {} 5 | 6 | --- WatchManagerを作る. 7 | function WatchManager.create() 8 | local m = { 9 | watches = {}, 10 | } 11 | 12 | --- ウォッチを追加する. 13 | --- ウォッチに指定できるチャンクは, 14 | --- 代入文の右辺値にできるものに限る. 15 | -- context: 現在のコンテキスト 16 | -- chunk: チャンク 17 | -- 不正なチャンクを指定した場合にエラーを返す. 18 | function m:add(context, chunk) 19 | 20 | if type(chunk) ~= 'string' then 21 | return 'chunk must be string' 22 | end 23 | 24 | local evaluator = Evaluator.create(context) 25 | local ret_val, err = evaluator:eval(chunk, true) 26 | 27 | if err ~= nil then 28 | return err 29 | end 30 | 31 | local watch = { 32 | chunk = chunk, 33 | value = ret_val, 34 | } 35 | 36 | table.insert(self.watches, watch) 37 | 38 | return nil 39 | end 40 | 41 | --- 指定したインデックスのウォッチ式を変更する. 42 | -- index: インデックス 43 | -- context: 現在のコンテキスト 44 | -- chunk: チャンク 45 | -- 不正なチャンクまたはインデックスを指定した場合にエラーを返す. 46 | function m:set(index, context, chunk) 47 | if type(chunk) ~= 'string' then 48 | return 'chunk must be string' 49 | end 50 | 51 | if index <= 0 or #self.watches < index then 52 | return 'index is out of bounds' 53 | end 54 | 55 | local evaluator = Evaluator.create(context) 56 | local ret_val, err = evaluator:eval(chunk, true) 57 | 58 | if err ~= nil then 59 | return err 60 | end 61 | 62 | local watch = { 63 | chunk = chunk, 64 | value = ret_val, 65 | } 66 | table.insert(self.watches, index, watch) 67 | 68 | return nil 69 | end 70 | 71 | --- 指定したインデックスのウォッチ式を削除する. 72 | -- index: インデックス 73 | function m:remove(index) 74 | if self.watches[index] then 75 | return table.remove(self.watches, index) 76 | end 77 | return nil 78 | end 79 | 80 | --- 指定したコンテキストでウォッチ式の評価値を更新する. 81 | -- context: コンテキスト 82 | function m:update(context) 83 | local evaluator = Evaluator.create(context) 84 | for i, watch in ipairs(self.watches) do 85 | local ret_val, err = evaluator:eval(watch.chunk, true) 86 | if err ~= nil then 87 | self.watches[i].value = tostring(err) 88 | else 89 | self.watches[i].value = ret_val 90 | end 91 | end 92 | end 93 | 94 | return m 95 | end 96 | -------------------------------------------------------------------------------- /src/command_factory/watch_command_factory.lua: -------------------------------------------------------------------------------- 1 | --- watch_command_factory.lua 2 | 3 | local WatchCommandFactory = {} 4 | 5 | function WatchCommandFactory.create() 6 | local m = {} 7 | 8 | --- ウォッチに関するコマンドを作る. 9 | -- line: 入力された文字列 10 | -- 入力された文字列から 11 | -- ・ウォッチの追加 12 | -- ・ウォッチの削除 13 | -- ・ウォッチの一覧 14 | -- のいずれかのコマンドを返す. 15 | -- 上記のどれにも当てはまらなかったら,nil を返す. 16 | function m:createCommand(line) 17 | local cmd = utils:splitWords(line) 18 | if not cmd and #cmd <= 0 then 19 | return nil 20 | end 21 | 22 | -- ウォッチ式の一覧 23 | if cmd[1] == 'watch' or cmd[1] == 'w' then 24 | return function(debugger) 25 | local watches = debugger.watch_manager.watches 26 | local fmt = '%' .. tostring(utils:numDigits(#watches)) .. 'd: %s = %s' 27 | for i, watch in ipairs(watches) do 28 | local str_value = utils:inspect(watch.value) 29 | debugger.writer:writeln(string.format(fmt, i, watch.chunk, str_value)) 30 | end 31 | return true 32 | end 33 | end 34 | 35 | -- ウォッチ式の追加・更新 36 | if cmd[1] == 'setWatch' or cmd[1] == 'sw' then 37 | return function(debugger) 38 | local context = debugger.call_stack[1] 39 | local chunk, err 40 | local index = tonumber(cmd[2]) 41 | if not index then 42 | chunk = utils:join(utils:slice(cmd, 2), " ") 43 | err = debugger.watch_manager:add(context, chunk) 44 | else 45 | chunk = utils:join(utils:slice(cmd, 3), " ") 46 | err = debugger.watch_manager:set(index, context, chunk) 47 | end 48 | 49 | if err then 50 | debugger.writer:writeln('ERROR: ' .. tostring(err)) 51 | return true 52 | end 53 | 54 | debugger.writer:writeln('add watch ' .. chunk) 55 | return true 56 | end 57 | end 58 | 59 | -- ウォッチ式の削除 60 | if cmd[1] == 'removeWatch' or cmd[1] == 'rw' then 61 | local index = tonumber(cmd[2]) 62 | if not index then 63 | return nil 64 | end 65 | 66 | return function(debugger) 67 | local watch = debugger.watch_manager:remove(index) 68 | if watch then 69 | debugger.writer:writeln('remove watch ' .. watch.chunk) 70 | end 71 | return true 72 | end 73 | end 74 | 75 | return nil 76 | end 77 | 78 | return m 79 | end 80 | -------------------------------------------------------------------------------- /src/command_factory/info_command_factory.lua: -------------------------------------------------------------------------------- 1 | --- info_command_factory.lua 2 | 3 | local InfoCommandFactory = {} 4 | 5 | function InfoCommandFactory.create() 6 | local m = {} 7 | 8 | --- infoコマンドを作る. 9 | -- line: 入力された文字列 10 | -- 入力された文字列が info コマンドに当てはまらなかった場合はnil 11 | -- そうでない場合 info コマンド 12 | function m:createCommand(line) 13 | local cmd = utils:splitWords(line) 14 | if not cmd and #cmd <= 0 then 15 | return nil 16 | end 17 | 18 | if cmd[1] == 'info' or cmd[1] == 'i' then 19 | local cmd_index = 2 20 | 21 | -- コールスタック情報を表示する 22 | local call_stack_cmd = function(debugger) 23 | debugger.writer:writeln('call stack:') 24 | if tonumber(cmd[cmd_index+1]) then 25 | local call_stack = utils:slice(debugger.call_stack, tonumber(cmd[cmd_index+1])) 26 | debugger.writer:writeln(JSON.stringify(call_stack), Writer.TAG.CALL_STACK) 27 | cmd_index = cmd_index + 1 28 | else 29 | debugger.writer:writeln(JSON.stringify(debugger.call_stack), Writer.TAG.CALL_STACK) 30 | end 31 | debugger.writer:writeln() 32 | end 33 | 34 | -- ブレークポイント情報を表示する 35 | local break_points_cmd = function(debugger) 36 | debugger.writer:writeln('break points:') 37 | local break_points = debugger.break_point_manager.break_points 38 | if next(break_points) then 39 | debugger.writer:writeln(JSON.stringify(break_points), Writer.TAG.BREAK_POINTS) 40 | else 41 | debugger.writer:writeln('{}', Writer.TAG.BREAK_POINTS) 42 | end 43 | debugger.writer:writeln() 44 | end 45 | 46 | -- ウォッチ情報を表示する 47 | local watches_cmd = function(debugger) 48 | debugger.writer:writeln('watches:') 49 | debugger.writer:writeln(JSON.stringify(debugger.watch_manager.watches), Writer.TAG.WATCHES) 50 | debugger.writer:writeln() 51 | end 52 | 53 | return function(debugger) 54 | -- 指定がない場合はすべて 55 | if not cmd[cmd_index] then 56 | call_stack_cmd(debugger) 57 | break_points_cmd(debugger) 58 | watches_cmd(debugger) 59 | return true 60 | end 61 | 62 | while cmd[cmd_index] do 63 | if cmd[cmd_index] == 'call_stack' then 64 | call_stack_cmd(debugger) 65 | elseif cmd[cmd_index] == 'break_points' then 66 | break_points_cmd(debugger) 67 | elseif cmd[cmd_index] == 'watches' then 68 | watches_cmd(debugger) 69 | end 70 | cmd_index = cmd_index + 1 71 | end 72 | 73 | return true 74 | end 75 | end 76 | return nil 77 | end 78 | 79 | return m 80 | end 81 | -------------------------------------------------------------------------------- /src/evaluator.lua: -------------------------------------------------------------------------------- 1 | -- evaluator.lua 2 | 3 | --- Lua 5.1用のeval関数. 4 | -- chunk: 実行するLuaコード 5 | -- lenv: 実行する環境 6 | local function eval51(chunk, lenv) 7 | setfenv(0, lenv) 8 | local fnc, err = loadstring(chunk) 9 | if err then 10 | return err 11 | end 12 | ok, err = pcall(fnc) 13 | setfenv(0, _G) 14 | if not ok then 15 | return err 16 | end 17 | return nil 18 | end 19 | 20 | --- Lua 5.2用のeval関数. 21 | -- chunk: 実行するLuaコード 22 | -- lenv: 実行する環境 23 | local function eval52(chunk, lenv) 24 | local fnc, err = load(chunk, 'eval', 't', lenv) 25 | if err then 26 | return err 27 | end 28 | local ok, err = pcall(fnc) 29 | if not ok then 30 | return err 31 | end 32 | return nil 33 | end 34 | 35 | --- 評価を行うクラス. 36 | local Evaluator = {} 37 | 38 | -- Luaのバージョンでチャンクを実行する関数が違う 39 | if _VERSION == 'Lua 5.2' then 40 | Evaluator.EVAL_FUNC = eval52 41 | else 42 | Evaluator.EVAL_FUNC = eval51 43 | end 44 | 45 | --- Evaluatorを作る. 46 | -- context: 評価に使用するコンテキスト 47 | function Evaluator.create(context) 48 | 49 | local m = { 50 | context = context, 51 | } 52 | 53 | --- 環境を作る. 54 | -- 環境 55 | -- 戻り値用の変数名 56 | function m:createLocalEnv() 57 | local lenv = utils:tableCopy(_G) 58 | for _, var_info in ipairs(self.context.var_infoes) do 59 | lenv[var_info.name] = var_info.new_value 60 | end 61 | 62 | -- 戻り値用の変数を用意 63 | local ret_key = '_ret' 64 | while lenv[ret_key] do 65 | ret_key = '_' .. ret_key 66 | end 67 | 68 | return lenv, ret_key 69 | end 70 | 71 | --- 指定したチャンクを評価する. 72 | --- is_ret_value を trueにした場合,指定できるチャンクは, 73 | --- 代入分の右辺値だけとなる. 74 | -- chunk: チャンク(Luaのコード) 75 | -- is_ret_value: 戻り値を必要とするか 76 | function m:eval(chunk, is_ret_value) 77 | local lenv, ret_key = self:createLocalEnv() 78 | 79 | -- 戻り値を戻り値用の変数に入れて取得できるようにする 80 | if is_ret_value then 81 | chunk = string.format('%s = (%s)', ret_key, chunk) 82 | end 83 | 84 | -- 実行 85 | local err = Evaluator.EVAL_FUNC(chunk, lenv) 86 | if err ~= nil then 87 | return nil, err 88 | end 89 | 90 | -- 変数の変更の反映 91 | for k, v in pairs(lenv) do 92 | 93 | -- ローカル変数に変更を反映 94 | local is_updated = false 95 | for _, var_info in ipairs(self.context.var_infoes) do 96 | if var_info.name == k then 97 | var_info.new_value = v 98 | is_updated = true 99 | break 100 | end 101 | end 102 | 103 | -- ローカル変数にない場合グローバル変数を更新 104 | if not is_updated and _G[k] then 105 | rawset(_G, k, v) 106 | end 107 | end 108 | 109 | -- 戻り値を取得 110 | local ret_val 111 | if is_ret_value then 112 | ret_val = lenv[ret_key] 113 | end 114 | 115 | return ret_val, nil 116 | end 117 | 118 | return m 119 | end 120 | -------------------------------------------------------------------------------- /src/step_execute_manager.lua: -------------------------------------------------------------------------------- 1 | --- step_execute_manager.lua 2 | 3 | local StepExecuteManager = { 4 | MODE_STEP_OVER = 0, 5 | MODE_STEP_IN = 1, 6 | MODE_STEP_OUT = 2, 7 | } 8 | 9 | function StepExecuteManager.create() 10 | local m = { 11 | mode = nil, 12 | call_stack = nil, 13 | count = 0, 14 | } 15 | 16 | --- ステップオーバーを設定する. 17 | -- call_stack: コールスタック 18 | -- count: 実行するステップ数 19 | function m:setStepOver(call_stack, count) 20 | self.mode = StepExecuteManager.MODE_STEP_OVER 21 | self.call_stack = utils:tableCopy(call_stack) 22 | self.count = count 23 | end 24 | 25 | --- ステップインを設定する. 26 | -- call_stack: コールスタック 27 | -- count: 実行するステップ数 28 | function m:setStepIn(call_stack, count) 29 | self.mode = StepExecuteManager.MODE_STEP_IN 30 | self.call_stack = utils:tableCopy(call_stack) 31 | self.count = count 32 | end 33 | 34 | --- ステップアウトを設定する. 35 | -- call_stack: コールスタック 36 | -- count: 実行するステップ数 37 | function m:setStepOut(call_stack, count) 38 | self.mode = StepExecuteManager.MODE_STEP_OUT 39 | self.call_stack = utils:tableCopy(call_stack) 40 | self.count = count 41 | end 42 | 43 | --- ステップ実行をやめる. 44 | function m:clear() 45 | self.mode = nil 46 | self.call_stack = nil 47 | self.count = 0 48 | end 49 | 50 | --- 停止すべきか取得する. 51 | -- call_stack: コールスタック 52 | function m:shouldStop(call_stack) 53 | if not self.mode then 54 | return false 55 | end 56 | 57 | if self.mode == StepExecuteManager.MODE_STEP_OVER then 58 | return self:shouldStopStepOver(call_stack) 59 | end 60 | 61 | if self.mode == StepExecuteManager.MODE_STEP_IN then 62 | return self:shouldStopStepIn(call_stack) 63 | end 64 | 65 | if self.mode == StepExecuteManager.MODE_STEP_OUT then 66 | return self:shouldStopStepOut(call_stack) 67 | end 68 | end 69 | 70 | --- ステップオーバーで停止すべきか取得する. 71 | -- call_stack: コールスタック 72 | function m:shouldStopStepOver(call_stack) 73 | if #call_stack <= 0 or 74 | #self.call_stack <= 0 or 75 | utils:getLevelByFunc(self.call_stack, call_stack[1]) >= 1 then 76 | self.count = self.count - 1 77 | return self.count <= 0 78 | end 79 | return false 80 | end 81 | 82 | --- ステップインで停止すべきか取得する. 83 | -- call_stack: コールスタック 84 | function m:shouldStopStepIn(call_stack) 85 | self.count = self.count - 1 86 | return self.count <= 0 87 | end 88 | 89 | --- ステップアウトで停止すべきか取得する. 90 | -- call_stack: コールスタック 91 | function m:shouldStopStepOut(call_stack) 92 | if #call_stack <= 0 or 93 | #self.call_stack <= 0 or 94 | utils:getLevelByFunc(self.call_stack, call_stack[1]) > 1 then 95 | self.count = self.count - 1 96 | return self.count <= 0 97 | end 98 | return false 99 | end 100 | 101 | return m 102 | end 103 | -------------------------------------------------------------------------------- /src/utils.lua: -------------------------------------------------------------------------------- 1 | --- utils.lua 2 | 3 | local Utils = {} 4 | 5 | function Utils.create() 6 | local m = { 7 | debugger_source = debug.getinfo(1, 'S').source, 8 | source_prefix = string.gsub(debug.getinfo(1, 'S').source, '@?.*/(.*)', '%1'), 9 | } 10 | 11 | --- debug_infoがLupeのものか調べる. 12 | -- debug_info: デバッグ情報 13 | function m:isLupe(debug_info) 14 | return debug_info.source == self.debugger_source 15 | end 16 | 17 | --- ソースコードを取得する. 18 | -- debug_info: デバッグ情報 19 | -- @がある場合はそれを取り除いたinfo.source 20 | function m:getSource(debug_info) 21 | local source, _ = string.gsub(debug_info.source, '@?(.+)', '%1') 22 | return source 23 | end 24 | 25 | --- 相対パスでのソースファイルパスを取得する. 26 | -- source: ソースファイル 27 | function m:withoutPrefixSource(source) 28 | local without_prefix, _ = string.gsub(source, self.source_prefix .. '/(.*)', '%1') 29 | local without_atto, _ = string.gsub(without_prefix, '@?(.+)', '%1') 30 | return without_atto 31 | end 32 | 33 | --- 絶対パスでのソースファイルパスを取得する. 34 | -- source: ソースファイル 35 | -- hoge.lua -> file://install/hoge.lua 36 | function m:withPrefixSource(source) 37 | return self.source_prefix .. '/' .. self:withoutPrefixSource(source) 38 | end 39 | 40 | --- コールスタックの中で,指定したコンテキストの関数が一致する階層を取得する. 41 | -- call_stack: コールスタック 42 | -- context: コンテキスト 43 | -- 見つからない場合は0 44 | function m:getLevelByFunc(call_stack, context) 45 | for i, c in ipairs(call_stack) do 46 | if c.func == context.func then 47 | return i 48 | end 49 | end 50 | 51 | return 0 52 | end 53 | 54 | --- 文字列を単語に分ける. 55 | -- str: 文字列 56 | -- 単語毎に分けた文字列の配列 57 | function m:splitWords(str) 58 | local words = {} 59 | for word in string.gmatch(str, '[^%s]+') do 60 | table.insert(words, word) 61 | end 62 | return words 63 | end 64 | 65 | --- テーブルをコピーする 66 | function m:tableCopy(tbl) 67 | local t = {} 68 | for k, v in pairs(tbl) do 69 | t[k] = v 70 | end 71 | return t 72 | end 73 | 74 | --- 10進数で何桁か返す. 75 | -- num: 桁数を数える数値 76 | function m:numDigits(num) 77 | local num_digits = 0 78 | num = math.floor(math.abs(num)) 79 | while true do 80 | if num <= 0 then 81 | return num_digits 82 | end 83 | 84 | num = math.floor(num / 10) 85 | num_digits = num_digits + 1 86 | end 87 | end 88 | 89 | --- テーブルが空の場合に,ダミーデータを入れます. 90 | -- tbl: テーブル 91 | function m:dummy(tbl) 92 | if not next(tbl) then 93 | tbl['__dummy'] = 'dummy' 94 | end 95 | return tbl 96 | end 97 | 98 | --- 配列のスライスを取得する. 99 | --- 第3引数を省略すると,終了インデクスは配列の長さと同じになる. 100 | -- array: 配列 101 | -- start_index: 開始インデックス 102 | -- end_index: 終了インデックス 103 | function m:slice(array, start_index, end_index) 104 | if not array or #array <= 0 then 105 | return {} 106 | end 107 | 108 | start_index = math.max(start_index, 1) 109 | end_index = math.min(end_index or #array, #array) 110 | 111 | local a = {} 112 | for i = start_index, end_index do 113 | table.insert(a, array[i]) 114 | end 115 | 116 | return a 117 | end 118 | 119 | --- 配列の中の指定した値のインデックスを取得する. 120 | -- array: 配列 121 | -- value: 検索する値 122 | -- ある場合はインデックス,ない場合は-1 123 | function m:indexOf(array, value) 124 | for i, v in ipairs(array) do 125 | if v == value then 126 | return i 127 | end 128 | end 129 | return -1 130 | end 131 | 132 | --- inspectのヘルパー関数. 133 | -- value: 文字列にする値 134 | -- max_level: 表示する階層の最大値 135 | -- level: 現在の階層 136 | local function _inspect(value, max_level, level) 137 | 138 | local str = '' 139 | 140 | local t = type(value) 141 | if t == 'table' then 142 | if level >= max_level then 143 | return '...' 144 | end 145 | 146 | local indent = '' 147 | for i = 1, level do 148 | indent = indent .. ' ' 149 | end 150 | 151 | str = str .. '{\n' 152 | for k, v in pairs(value) do 153 | str = str .. indent .. string.format(' %s(%s): %s\n', tostring(k), type(v), _inspect(v, max_level, level + 1)) 154 | end 155 | str = str .. indent .. '}' 156 | else 157 | str = str .. tostring(value) 158 | end 159 | 160 | return str 161 | end 162 | 163 | --- 文字列にする. 164 | -- value: 文字列にする値 165 | -- max_level: 表示する階層の最大値 166 | function m:inspect(value, max_level) 167 | max_level = max_level or 5 168 | return _inspect(value, max_level, 0) 169 | end 170 | 171 | --- 文字列の配列を結合する. 172 | -- array: 文字列の配列 173 | -- delimiter: デリミタ 174 | function m:join(array, delimiter) 175 | if not array or #array <= 0 then 176 | return '' 177 | end 178 | 179 | delimiter = tostring(delimiter or ' ') 180 | 181 | local s = '' 182 | for _, v in ipairs(array) do 183 | s = s .. tostring(v) .. delimiter 184 | end 185 | 186 | return string.sub(s, 1, string.len(s) - string.len(delimiter)) 187 | end 188 | 189 | return m 190 | end 191 | 192 | local utils = Utils.create() 193 | -------------------------------------------------------------------------------- /src/context.lua: -------------------------------------------------------------------------------- 1 | --- context.lua 2 | 3 | --- デバッグコンテキスト 4 | local Context = { 5 | global_defined_lines = {} 6 | } 7 | 8 | for k, v in pairs(_G) do 9 | Context.global_defined_lines[k] = { 10 | source = 'Unknown', 11 | line = -1, 12 | } 13 | end 14 | 15 | --- Contextを作る. 16 | -- debug_info: デバッグ情報 17 | -- var_infoes: 変数情報 18 | function Context.create(debug_info, var_infoes) 19 | 20 | local var_defined_lines = {} 21 | 22 | -- ローカル変数の宣言箇所を記録 23 | -- とりあえず,この行にする 24 | for _, var_info in ipairs(var_infoes) do 25 | if not var_info.is_upvalue then 26 | var_defined_lines[var_info.name] = { 27 | source = utils:withoutPrefixSource(debug_info.source), 28 | line = debug_info.currentline, 29 | } 30 | end 31 | end 32 | 33 | local m = { 34 | var_infoes = var_infoes, 35 | var_defined_lines = var_defined_lines, 36 | name = (debug_info.name or ''), 37 | namewhat = debug_info.namewhat, 38 | what = debug_info.what, 39 | source = debug_info.source, 40 | currentline = debug_info.currentline, 41 | linedefined = debug_info.linedefined, 42 | lastlinedefined = debug_info.lastlinedefined, 43 | nups = debug_info.nups, 44 | nparams = debug_info.nparams, 45 | isvararg = debug_info.isvararg, 46 | istailcall = debug_info.istailcall, 47 | short_src = debug_info.short_src, 48 | func = debug_info.func, 49 | start_time_ms = os.clock() * 1000, 50 | start_memory_kB = collectgarbage('count') 51 | } 52 | 53 | --- コンテキストが作られた時からの経過時間と使用メモリを取得する. 54 | -- 経過時間[ms] 55 | -- 使用メモリ[kB] 56 | function m:record() 57 | local end_time_ms = os.clock() * 1000 58 | local duration_ms = end_time_ms - self.start_time_ms 59 | local use_memory_kB = collectgarbage('count') - self.start_memory_kB 60 | return duration_ms, use_memory_kB 61 | end 62 | 63 | --- 新しい情報に更新する. 64 | -- context: 更新する情報を持つコンテキスト 65 | function m:update(context) 66 | 67 | local warnings = {} 68 | 69 | -- グローバル変数の宣言箇所を記録 70 | for k, v in pairs(_G) do 71 | if not Context.global_defined_lines[k] then 72 | Context.global_defined_lines[k] = { 73 | source = utils:withoutPrefixSource(self.source), 74 | line = self.currentline, 75 | } 76 | end 77 | end 78 | 79 | -- 新しい変数の場合は宣言された場所を記録 80 | for name, var_defined_line in pairs(context.var_defined_lines) do 81 | if not self.var_defined_lines[name] then 82 | self.var_defined_lines[name] = { 83 | source = utils:withoutPrefixSource(self.source), 84 | line = self.currentline, 85 | } 86 | 87 | -- グローバル変数を上書いているか? 88 | if _G[name] then 89 | table.insert(warnings, string.format('local variable %s overwrites global variable', name)) 90 | end 91 | end 92 | end 93 | 94 | self.var_infoes = context.var_infoes 95 | self.name = context.name 96 | self.namewhat = context.namewhat 97 | self.what = context.what 98 | self.source = context.source 99 | self.currentline = context.currentline 100 | self.linedefined = context.linedefined 101 | self.lastlinedefined = context.lastlinedefined 102 | self.nups = context.nups 103 | self.nparams = context.nparams 104 | self.isvararg = context.isvararg 105 | self.istailcall = context.istailcall 106 | self.short_src = context.short_src 107 | self.func = context.func 108 | 109 | return warnings 110 | end 111 | 112 | --- JSONに変換する. 113 | function m:toJSON() 114 | local t = { 115 | var_infoes = self.var_infoes, 116 | var_defined_lines = self.var_defined_lines, 117 | name = self.name, 118 | namewhat = self.namewhat, 119 | what = self.what, 120 | source = utils:withoutPrefixSource(self.source), 121 | currentline = self.currentline, 122 | linedefined = self.linedefined, 123 | lastlinedefined = self.lastlinedefined, 124 | nups = self.nups, 125 | nparams = self.nparams, 126 | isvararg = self.isvararg, 127 | istailcall = self.istailcall or false, 128 | short_src = self.short_src, 129 | start_time_ms = self.start_time_ms, 130 | start_memory_kB = self.start_memory_kB, 131 | } 132 | 133 | -- グローバル変数も反映させておく 134 | for name, global_defined_line in pairs(Context.global_defined_lines) do 135 | if not self.var_defined_lines[name] and global_defined_line.line ~= -1 then 136 | self.var_defined_lines[name] = global_defined_line 137 | end 138 | end 139 | 140 | -- ない場合はnilにしておく 141 | if not next(t.var_defined_lines) then 142 | t.var_defined_lines = nil 143 | end 144 | 145 | return JSON.stringify(t) 146 | end 147 | 148 | return m 149 | end 150 | -------------------------------------------------------------------------------- /src/lupe.lua: -------------------------------------------------------------------------------- 1 | --- lupe.lua 2 | 3 | --- デバッガの機能を提供するクラス. 4 | local Lupe = {} 5 | 6 | --- Lupeを作る. 7 | function Lupe.create() 8 | local m = { 9 | callback = function()end, 10 | call_stack = {}, 11 | top_level_context = nil, 12 | prompt = Prompt.create(), 13 | break_point_manager = BreakPointManager.create(), 14 | step_execute_manager = StepExecuteManager.create(), 15 | watch_manager = WatchManager.create(), 16 | profiler = nil, 17 | writer = Writer.create(), 18 | is_called = false, 19 | is_started = false, 20 | -- 21 | JSON = JSON, 22 | } 23 | 24 | m.coroutine_debugger = CoroutineDebugger.create(m) 25 | 26 | --- sethookで呼ばれるコードバック 27 | function m.stop_callback(t) 28 | 29 | local debug_info = debug.getinfo(2) 30 | if not debug_info or utils:isLupe(debug_info) then 31 | return 32 | end 33 | 34 | local var_infoes = VarInfo.getlocal(2) 35 | local context = Context.create(debug_info, var_infoes) 36 | 37 | -- 各行 38 | if t == 'line' then 39 | m:lineStop(context) 40 | 41 | -- 変数の変更があったら反映 42 | for _, var_info in pairs(context.var_infoes) do 43 | var_info:update(2) 44 | end 45 | 46 | -- ウォッチ式の更新 47 | m.watch_manager:update(context) 48 | return 49 | end 50 | 51 | -- 関数呼び出し 52 | if t == 'call' or t == 'tail call' then 53 | m:callStop(context) 54 | return 55 | end 56 | 57 | -- return 58 | if t == 'return' or t == 'tail return' then 59 | m:returnStop(context) 60 | return 61 | end 62 | end 63 | 64 | local function __call() 65 | m.is_called = true 66 | end 67 | 68 | --- デバッグを開始する. 69 | function m:start() 70 | debug.sethook(self.stop_callback, 'crl') 71 | self.coroutine_debugger:start() 72 | self.is_started = true 73 | end 74 | 75 | --- デバッガを停止する. 76 | function m:stop() 77 | debug.sethook() 78 | self.coroutine_debugger:stop() 79 | self.call_stack = {} 80 | self.is_started = false 81 | self.break_point_manager:clear() 82 | end 83 | 84 | --- コールスタックを消します. 85 | function m:clear() 86 | self.call_stack = {} 87 | end 88 | 89 | --- プロファイルを開始する. 90 | function m:startProfile() 91 | self.profiler = Profiler.create() 92 | end 93 | 94 | --- プロファイルを停止する. 95 | function m:endProfile() 96 | self:dump(self.profiler:summary()) 97 | self.profiler = nil 98 | end 99 | 100 | --- infoコマンドを実行する. 101 | -- サブコマンド 102 | function m:info(sub_cmd) 103 | local info_cmd_factory = InfoCommandFactory.create() 104 | local line = 'info' 105 | if sub_cmd then 106 | line = line .. ' ' .. sub_cmd 107 | end 108 | local info_cmd = info_cmd_factory:createCommand(line) 109 | info_cmd(self) 110 | end 111 | 112 | --- 行ごとに呼ばれる. 113 | --- この行で止まるべきか判断し,止まる場合はコマンドを受け付けるプロンプトを表示させる. 114 | -- context: コンテキスト 115 | function m:lineStop(context) 116 | local is_top_level = false 117 | if #self.call_stack <= 0 then 118 | self.top_level_context = self.top_level_context or context 119 | self.call_stack[1] = self.top_level_context 120 | is_top_level = true 121 | end 122 | 123 | -- コンテキストのアップデート 124 | local warnings = self.call_stack[1]:update(context) 125 | if warnings and #warnings > 0 then 126 | self.writer:writeln('====== WARNING ======') 127 | for _, warning in pairs(warnings) do 128 | self.writer:writeln(tostring(warning), Writer.TAG.WARNING) 129 | end 130 | self.writer:writeln('====== WARNING ======') 131 | end 132 | 133 | -- デバッグモードに入ったか,ブレークポイントか,ステップ実行で停止するか? 134 | if self.is_called or 135 | self.break_point_manager:shouldStop(self.call_stack) or 136 | self.step_execute_manager:shouldStop(self.call_stack) then 137 | 138 | -- コールバックを呼ぶ 139 | self.callback(self) 140 | 141 | self:showPrompt(context) 142 | end 143 | 144 | if is_top_level then 145 | table.remove(self.call_stack, 1) 146 | end 147 | end 148 | 149 | --- プロンプトを表示させる 150 | -- context: コンテキスト 151 | function m:showPrompt(context) 152 | self.is_called = false 153 | self.step_execute_manager:clear() 154 | self.writer:writeln(string.format('stop at %s:%d', context.source, context.currentline)) 155 | self.prompt:loop(self) 156 | end 157 | 158 | --- スタックトレースを表示させる 159 | -- msg: 一緒に表示するメッセージ 160 | function m:traceback(msg) 161 | if msg then 162 | self.writer:writeln(tostring(msg)) 163 | end 164 | local count = 0 165 | for _, context in ipairs(self.call_stack) do 166 | if context.source ~= '=[C]' then 167 | count = count + 1 168 | for i = 1, count do 169 | self.writer:write(' ') 170 | end 171 | self.writer:writeln(string.format('%s:%d %s', context.source, context.currentline, context.name)) 172 | end 173 | end 174 | end 175 | 176 | --- 関数の呼び出し時に呼ばれる. 177 | --- コールスタックをプッシュする. 178 | -- context: コンテキスト 179 | function m:callStop(context) 180 | table.insert(self.call_stack, 1, context) 181 | end 182 | 183 | --- 関数のreturn時に呼ばれる. 184 | --- コールスタックをポップする. 185 | -- context: コンテキスト 186 | function m:returnStop(context) 187 | -- Lua 5.2 では,tail call は return イベントが呼ばれない. 188 | -- そのため, istailcall が true の間は pop し続ける. 189 | while true do 190 | local _context = table.remove(self.call_stack, 1) 191 | 192 | -- プロファイルを行う 193 | if self.profiler then 194 | self.profiler:record(_context) 195 | end 196 | 197 | if not _context or not _context.istailcall then 198 | break 199 | end 200 | end 201 | end 202 | 203 | --- 値をダンプする. 204 | -- value: ダンプする値 205 | function m:dump(value, max_level) 206 | self.writer:writeln(utils:inspect(value, max_level)) 207 | end 208 | 209 | return setmetatable(m, {__call = __call}) 210 | end 211 | 212 | --- export 213 | rawset(_G, 'Lupe', Lupe.create()) 214 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LUPE - Lua Debugger 2 | 3 | LUPEはピュアLuaで書かれたデバッガです。 4 | Lua 5.1とLua 5.2で動くように実装されています。 5 | 6 | # 機能 7 | 8 | * ブレークポイント(追加,削除,一覧表示) 9 | * ステップイン 10 | * ステップアウト 11 | * ステップオーバー 12 | * コードの出力 13 | * ローカル変数一覧の出力 14 | * 式の評価 15 | * ローカル変数とグローバル変数の宣言位置の取得 16 | * ウォッチ式 17 | * 簡易プロファイラ 18 | 19 | # ビルド方法 20 | 21 | `lupe.lua`を生成するには,`concat.sh`を実行する必要があります. 22 | 23 | ``` 24 | $ ./concat.sh 25 | ``` 26 | 27 | # 使い方 28 | ## デバッガの読み込み 29 | 30 | LUPEをコマンドラインから使うには,`lupe.lua`を読み込んで`Lupe`オブジェクト使えるようにする必要があります. 31 | `lupe.lua`はリポジトリ直下にあるため,それをデバッグしたいLuaコードのあるプロジェクトにコピーしておきます. 32 | 33 | ``` 34 | $ cp lupe.lua myproject/ 35 | ``` 36 | 37 | 次に`lupe.lua`読み込みを行います.ピュアLuaの場合は`require`を使います.読み込みはデバッガを使用したいLuaファイルの先頭で行うと良いでしょう. 38 | 39 | そして,読み込んだ後はデバッガをスタートするために`Lupe:start()`を呼び出します. 40 | なお,デバッガを停止させたい場合は`Lupe:stop()`を呼び出すとブレークポイントなどが無効になります. 41 | 42 | ```main.lua 43 | -- ピュアLua 44 | require('lupe') 45 | Lupe:start() 46 | ... 47 | ``` 48 | 49 | ## ブレークポイントの追加・削除・一覧の表示 50 | 51 | ### ブレークポイントの追加:`addBreakPoint`, `ab` 52 | `addBreakPoint`または`ab`を使用するとブレークポイントを追加できます. 53 | 54 | ``` 55 | LUPE>ab [source] line 56 | ``` 57 | 58 | ソースファイル(source)を省略すると,現在のファイルの指定した行にブレークポイントを追加します. 59 | 60 | ### ブレークポイントの削除:`removeBreakPoint `, `rb` 61 | `removeBreakPoint `または`rb`を使用するとブレークポイントを削除できます. 62 | 63 | ``` 64 | LUPE>rb [source] line 65 | ``` 66 | 67 | 引数は`addBreakPoint`と同様です. 68 | 69 | ### ブレークポイントの一覧:`breakPointList`, `bl` 70 | `breakPointList`または`bl`を使用するとブレークポイントの一覧を表示できます. 71 | 72 | ``` 73 | LUPE>bl 74 | @start.lua:63 75 | ``` 76 | 77 | ## ステップ実行と継続 78 | 79 | ### ステップオーバー:`step`, `s` 80 | `step`または`s`を使用するとステップオーバーでステップ実行できます. 81 | 82 | ``` 83 | LUPE>s [num_step] 84 | ``` 85 | 86 | `num_step`で実行するステップ数を指定することができます. 87 | 省略した場合は,`1`ステップだけ実行する. 88 | 89 | ### ステップイン:`stepIn`, `si` 90 | `stepIn`または`si`を使用するとステップインでステップ実行できます. 91 | 92 | ``` 93 | LUPE>si [num_step] 94 | ``` 95 | 引数については`step`と同様です. 96 | 97 | ### ステップアウト:`stepOut`, `so` 98 | `stepOut`または`so`を使用するとステップインでステップ実行できます. 99 | 100 | ``` 101 | LUPE>so [num_step] 102 | ``` 103 | 104 | 引数については`step`と同様です. 105 | 106 | ### 継続:`run` 107 | `run`を使用すると処理再開することができます. 108 | 次のブレークポイント等でとまるまで実行されます. 109 | 110 | ``` 111 | LUPE>run 112 | ``` 113 | 114 | ## ソースコードの表示:`list`, `l` 115 | `list`または`l`を使用すると現在の行の周辺のソースコードが表示されます. 116 | 117 | ``` 118 | LUPE>l [num_lines] 119 | ``` 120 | 121 | `num_lines`で現在の行の前後何行を表示するかを指定できます. 122 | 省略すると前後`3`行を出力します. 123 | 124 | ``` 125 | LUPE>l 126 | 60: local f = function() 127 | 61: print(b) 128 | 62: end 129 | >*63: f() 130 | 64: fuga() 131 | 65: fuga() 132 | 66: hoge() 133 | ``` 134 | 135 | 現在の行には`>`がつきます.また,ブレークポイントがある行には`*`がつきます. 136 | つまり,上記の例では,現在の行は63行目で,ブレークポイントで停止していることがわかります. 137 | 138 | ## チャンクの評価(式の評価) 139 | ここで上げているデバッグ用のコマンドに当てはまらない場合はLuaのコード(チャンク)として評価を行います. 140 | このとき,現在の行の環境(変数や関数)を参照することができ,式の評価による環境の変更(変数の変更)の反映も行います. 141 | 142 | ``` 143 | LUPE>l 144 | 49: Lupe() 145 | 50: -- comment 146 | 51: local b = 300 147 | > 52: local c = { 148 | 53: c1 = 100, 149 | 54: c2 = 200, 150 | 55: c3 = { 151 | LUPE>b=200 152 | LUPE>print(b) 153 | 200 154 | ``` 155 | 156 | ## ローカル変数の表示:`vars`, `v` 157 | `vars`または`v`を使用するとローカル変数(上位値も含む)を表示することができます. 158 | 159 | ``` 160 | $ v [level] [shwo_level] 161 | ``` 162 | 163 | `level`でどの階層のローカル変数を表示することができるか指定することができます.現在の関数が`1`で,その呼び出し元は`2`となります.`level`を省略すると`1`となります. 164 | 165 | ``` 166 | LUPE>l 167 | 58: } 168 | 59: } 169 | 60: local f = function() 170 | >*61: print(b) 171 | 62: end 172 | 63: f() 173 | 64: fuga() 174 | LUPE>v 175 | b: 300 176 | LUPE>v 2 177 | a: 200 178 | b: 300 179 | c: { 180 | c1: 100 181 | c3: { 182 | c32: { 183 | 1: 2 184 | 2: 4 185 | 3: 6 186 | 4: 8 187 | 5: 10 188 | } 189 | c31: 100 190 | } 191 | c2: 200 192 | } 193 | f: function: 0x7fc270606ae0 194 | ``` 195 | 196 | `show_level`で変数がテーブルの場合にどの深さまで表示するか指定することができます. 197 | 省略すると,`5`となります.指定した深さより深いものは,`...`で省略されます. 198 | 199 | ``` 200 | LUPE>v 1 2 201 | a: 200 202 | b: 300 203 | c: { 204 | c1: 100 205 | c3: { 206 | c32: ... 207 | c31: 100 208 | } 209 | c2: 200 210 | } 211 | ``` 212 | 213 | ## 変数の宣言位置の表示: `definedLine`, `d` 214 | `definedLine`または`d`を使用するとローカル変数またはグローバル変数の宣言位置を表示することができます. 215 | 216 | ``` 217 | LUPE>d var_name 218 | ``` 219 | 220 | `var_name`にはその行までに宣言されているローカル変数およびグローバル変数の名前を指定できます. 221 | なお,同じ変数名が複数存在する場合は現在の行の階層に最も近い階層の変数の宣言位置が表示されます. 222 | 223 | ``` 224 | LUPE>d a 225 | @start.lua:47 226 | LUPE>d HOGE 227 | @start.lua:4 228 | ``` 229 | 230 | ## ウォッチ式 231 | 232 | ### ウォッチ式の設定:`setWatch`, `sw` 233 | `setWatch`または`sw`を使用するとウォッチ式を設定することができます. 234 | 235 | ``` 236 | LUPE>sw a+1 237 | add watch a+1 238 | ``` 239 | 240 | 設定できる式は代入の右辺に使用できるものに限ります. 241 | 242 | ### ウォッチ式の削除:`removeWatch`, `rw` 243 | `removeWatch`または`rw`を使用するとウォッチ式を削除することができます. 244 | 245 | ``` 246 | LUPE>rw [index] 247 | ``` 248 | 249 | `index`はウォッチ式の番号です.`watch`で確認することができます. 250 | 251 | ``` 252 | LUPE>rw [index] 253 | remove watch a+1 254 | ``` 255 | 256 | ### ウォッチ式の表示:`watch`, `w` 257 | `watch`または`w`を使用すると設定されているウォッチ式を表示することができます. 258 | 259 | ``` 260 | LUPE>w 261 | 1: a+1 = 201 262 | ``` 263 | 264 | ここで表示されている番号は,`removeWatch`の際に使用します. 265 | 266 | 267 | ## Lupeの関数 268 | 269 | ### `Lupe:start()` 270 | 271 | `Lupe:start()`を呼び出すことでデバッガを開始します. 272 | デバッガが開始されると,各行を実行時にさまざまなデバッグ情報が集められます. 273 | また,デバッグの起動中にブレークポイントや`assert`によって処理が一時停止することができます. 274 | 処理が停止中に,上記のデバッグコマンドを駆使してデバッグを行うことができます. 275 | 276 | ### `Lupe:stop()` 277 | 278 | `Lupe:stop()`を呼び出すことでデバッガを停止することができます. 279 | 一度停止するとデバッガはデバッグ情報を集めなく成るため,再度`Lupe:start()`を呼び出しても整合性が取れなくなります. 280 | しかし,デバッガを停止するとデバッグ情報を集めるための処理を行わなく成るため,デバッグ対象のプログラムに影響を与えなくなります. 281 | 282 | ### `Lupe:clear()` 283 | 284 | コールスタックを消します. 285 | 286 | ### `Lupe:dump()` 287 | 288 | `Lupe:dump()`は引数で渡した値を見やすい形で出力します. 289 | 290 | ``` 291 | LUPE>Lupe:dump({a = 100, b = 200}) 292 | { 293 | a: 100 294 | b: 200 295 | } 296 | ``` 297 | 298 | #### `Lupe:traceback()` 299 | 300 | コールスタックを表示させます. 301 | 302 | ``` 303 | LUPE>Lupe:traceback() 304 | @start.lua:39 hoge 305 | @start.lua:66 setup 306 | ``` 307 | 308 | ### `Lupe:startProfile()`, `Lupe:endProfile()` 309 | 310 | `Lupe:startProfile()`呼び出すと,簡易プロファイラが起動します. 311 | プロファイリングの結果はテーブルとして`Lupe.profiler:summary()`から取得することができます. 312 | また,`Lupe:endProfile()`を呼び出すとプロファイリングの結果を表示し,プロファイラを停止することができます. 313 | プロファイラが取得するのは各関数の実行時間と使用メモリサイズです. 314 | 315 | ``` 316 | LUPE>Lupe:startProfile() 317 | ... 318 | LUPE>Lupe:endProfile() 319 | { 320 | (=[C]:-1): { 321 | count: 1 322 | sum: { 323 | duration_ms: 0.024 324 | use_memory_kB: 2.66796875 325 | } 326 | average: { 327 | duration_ms: 0.024 328 | use_memory_kB: 2.66796875 329 | } 330 | } 331 | print(=[C]:-1): { 332 | count: 1 333 | sum: { 334 | duration_ms: 0.090999999999999 335 | use_memory_kB: 8.0654296875 336 | } 337 | average: { 338 | duration_ms: 0.090999999999999 339 | use_memory_kB: 8.0654296875 340 | } 341 | } 342 | f(@start.lua:60): { 343 | count: 1 344 | sum: { 345 | duration_ms: 0.244 346 | use_memory_kB: 21.884765625 347 | } 348 | average: { 349 | duration_ms: 0.244 350 | use_memory_kB: 21.884765625 351 | } 352 | } 353 | } 354 | ``` 355 | -------------------------------------------------------------------------------- /lupe.lua: -------------------------------------------------------------------------------- 1 | -- LUPE 2 | -- built at 2015-01-04 17:46:05 3 | -- Author: Takuya Ueda 4 | 5 | --- utils.lua 6 | 7 | local Utils = {} 8 | 9 | function Utils.create() 10 | local m = { 11 | debugger_source = debug.getinfo(1, 'S').source, 12 | source_prefix = string.gsub(debug.getinfo(1, 'S').source, '@?.*/(.*)', '%1'), 13 | } 14 | 15 | --- debug_infoがLupeのものか調べる. 16 | -- debug_info: デバッグ情報 17 | function m:isLupe(debug_info) 18 | return debug_info.source == self.debugger_source 19 | end 20 | 21 | --- ソースコードを取得する. 22 | -- debug_info: デバッグ情報 23 | -- @がある場合はそれを取り除いたinfo.source 24 | function m:getSource(debug_info) 25 | local source, _ = string.gsub(debug_info.source, '@?(.+)', '%1') 26 | return source 27 | end 28 | 29 | --- 相対パスでのソースファイルパスを取得する. 30 | -- source: ソースファイル 31 | function m:withoutPrefixSource(source) 32 | local without_prefix, _ = string.gsub(source, self.source_prefix .. '/(.*)', '%1') 33 | local without_atto, _ = string.gsub(without_prefix, '@?(.+)', '%1') 34 | return without_atto 35 | end 36 | 37 | --- 絶対パスでのソースファイルパスを取得する. 38 | -- source: ソースファイル 39 | -- hoge.lua -> file://install/hoge.lua 40 | function m:withPrefixSource(source) 41 | return self.source_prefix .. '/' .. self:withoutPrefixSource(source) 42 | end 43 | 44 | --- コールスタックの中で,指定したコンテキストの関数が一致する階層を取得する. 45 | -- call_stack: コールスタック 46 | -- context: コンテキスト 47 | -- 見つからない場合は0 48 | function m:getLevelByFunc(call_stack, context) 49 | for i, c in ipairs(call_stack) do 50 | if c.func == context.func then 51 | return i 52 | end 53 | end 54 | 55 | return 0 56 | end 57 | 58 | --- 文字列を単語に分ける. 59 | -- str: 文字列 60 | -- 単語毎に分けた文字列の配列 61 | function m:splitWords(str) 62 | local words = {} 63 | for word in string.gmatch(str, '[^%s]+') do 64 | table.insert(words, word) 65 | end 66 | return words 67 | end 68 | 69 | --- テーブルをコピーする 70 | function m:tableCopy(tbl) 71 | local t = {} 72 | for k, v in pairs(tbl) do 73 | t[k] = v 74 | end 75 | return t 76 | end 77 | 78 | --- 10進数で何桁か返す. 79 | -- num: 桁数を数える数値 80 | function m:numDigits(num) 81 | local num_digits = 0 82 | num = math.floor(math.abs(num)) 83 | while true do 84 | if num <= 0 then 85 | return num_digits 86 | end 87 | 88 | num = math.floor(num / 10) 89 | num_digits = num_digits + 1 90 | end 91 | end 92 | 93 | --- テーブルが空の場合に,ダミーデータを入れます. 94 | -- tbl: テーブル 95 | function m:dummy(tbl) 96 | if not next(tbl) then 97 | tbl['__dummy'] = 'dummy' 98 | end 99 | return tbl 100 | end 101 | 102 | --- 配列のスライスを取得する. 103 | --- 第3引数を省略すると,終了インデクスは配列の長さと同じになる. 104 | -- array: 配列 105 | -- start_index: 開始インデックス 106 | -- end_index: 終了インデックス 107 | function m:slice(array, start_index, end_index) 108 | if not array or #array <= 0 then 109 | return {} 110 | end 111 | 112 | start_index = math.max(start_index, 1) 113 | end_index = math.min(end_index or #array, #array) 114 | 115 | local a = {} 116 | for i = start_index, end_index do 117 | table.insert(a, array[i]) 118 | end 119 | 120 | return a 121 | end 122 | 123 | --- 配列の中の指定した値のインデックスを取得する. 124 | -- array: 配列 125 | -- value: 検索する値 126 | -- ある場合はインデックス,ない場合は-1 127 | function m:indexOf(array, value) 128 | for i, v in ipairs(array) do 129 | if v == value then 130 | return i 131 | end 132 | end 133 | return -1 134 | end 135 | 136 | --- inspectのヘルパー関数. 137 | -- value: 文字列にする値 138 | -- max_level: 表示する階層の最大値 139 | -- level: 現在の階層 140 | local function _inspect(value, max_level, level) 141 | 142 | local str = '' 143 | 144 | local t = type(value) 145 | if t == 'table' then 146 | if level >= max_level then 147 | return '...' 148 | end 149 | 150 | local indent = '' 151 | for i = 1, level do 152 | indent = indent .. ' ' 153 | end 154 | 155 | str = str .. '{\n' 156 | for k, v in pairs(value) do 157 | str = str .. indent .. string.format(' %s(%s): %s\n', tostring(k), type(v), _inspect(v, max_level, level + 1)) 158 | end 159 | str = str .. indent .. '}' 160 | else 161 | str = str .. tostring(value) 162 | end 163 | 164 | return str 165 | end 166 | 167 | --- 文字列にする. 168 | -- value: 文字列にする値 169 | -- max_level: 表示する階層の最大値 170 | function m:inspect(value, max_level) 171 | max_level = max_level or 5 172 | return _inspect(value, max_level, 0) 173 | end 174 | 175 | --- 文字列の配列を結合する. 176 | -- array: 文字列の配列 177 | -- delimiter: デリミタ 178 | function m:join(array, delimiter) 179 | if not array or #array <= 0 then 180 | return '' 181 | end 182 | 183 | delimiter = tostring(delimiter or ' ') 184 | 185 | local s = '' 186 | for _, v in ipairs(array) do 187 | s = s .. tostring(v) .. delimiter 188 | end 189 | 190 | return string.sub(s, 1, string.len(s) - string.len(delimiter)) 191 | end 192 | 193 | return m 194 | end 195 | 196 | local utils = Utils.create() 197 | -- json.lua 198 | -- JSONのエンコーダを提供する. 199 | 200 | local JSON = {} 201 | 202 | local escapeTable = {} 203 | escapeTable['"'] = '\\"' 204 | escapeTable['\\'] = '\\\\' 205 | escapeTable['\b'] = '\\b' 206 | escapeTable['\f'] = '\\f' 207 | escapeTable['\n'] = '\\n' 208 | escapeTable['\r'] = '\\r' 209 | escapeTable['\t'] = '\\t' 210 | 211 | --- 文字列をエスケープする. 212 | -- str: エスケープする文字列 213 | function JSON.escape(str) 214 | local s = '' 215 | 216 | string.gsub(str, '.', function(c) 217 | if escapeTable[c] then 218 | s = s .. escapeTable[c] 219 | else 220 | s = s .. c 221 | end 222 | end) 223 | 224 | return s 225 | end 226 | 227 | --- 配列をJSONエンコードする. 228 | -- arry: 配列 229 | function JSON.stringifyArray(arry) 230 | local s = '' 231 | for i = 1, #arry do 232 | s = s .. JSON.stringify(arry[i]) .. ',' 233 | end 234 | s = string.sub(s, 1, string.len(s) - 1) 235 | 236 | return string.format('[%s]', s) 237 | end 238 | 239 | --- テーブルをJSONエンコードする. 240 | --- toJSONをメソッドとして持つ場合はそれを呼ぶ. 241 | -- tbl: テーブル 242 | function JSON.stringifyTable(tbl) 243 | if tbl.toJSON and type(tbl.toJSON) == 'function' then 244 | return tbl:toJSON() 245 | end 246 | 247 | local isArray = true 248 | local s = '' 249 | for k, v in pairs(tbl) do 250 | if type(k) ~= 'number' then 251 | isArray = false 252 | end 253 | 254 | s = s .. string.format('"%s":%s,', tostring(k), JSON.stringify(v)) 255 | end 256 | s = string.sub(s, 1, string.len(s) - 1) 257 | 258 | if isArray then 259 | return JSON.stringifyArray(tbl) 260 | end 261 | 262 | return string.format('{%s}', s) 263 | end 264 | 265 | --- JSONエンコードする. 266 | -- v: エンコードする値 267 | function JSON.stringify(v) 268 | local t = type(v) 269 | 270 | if t == 'table' then 271 | return JSON.stringifyTable(v) 272 | elseif t == 'string' then 273 | return '"' .. JSON.escape(v) .. '"' 274 | elseif t == 'function' or t == 'thread' or t == 'userdata' then 275 | return '"' .. tostring(v) .. '"' 276 | elseif t == 'nil' then 277 | return 'null' 278 | end 279 | 280 | return tostring(v) 281 | end 282 | --- reader.lua 283 | 284 | --- io.linesを使ってソースコードの各行をテーブルに格納して返す. 285 | -- source: ソースコード 286 | -- ソースコードを格納したテーブル 287 | local function _lines(source) 288 | local lines = {} 289 | for l in io.lines(source) do 290 | table.insert(lines, l) 291 | end 292 | return lines 293 | end 294 | 295 | local Reader = { 296 | -- 同じファイルを何度も開いても良いようにキャッシュする 297 | cache = {}, 298 | } 299 | 300 | function Reader.create(source) 301 | local m = { 302 | source = source, 303 | } 304 | 305 | function m:lines() 306 | if Reader.cache[self.source] then 307 | return Reader.cache[self.source] 308 | end 309 | 310 | return _lines(self.source) 311 | end 312 | 313 | return m 314 | end 315 | --- writer.lua 316 | 317 | local Writer = {} 318 | 319 | Writer.TAG = { 320 | CALL_STACK = 'CALL_STACK', 321 | WATCHES = 'WATCHES', 322 | BREAK_POINTS = 'BREAK_POINTS', 323 | WARNING = 'WARNING', 324 | } 325 | 326 | function Writer.create() 327 | local m = {} 328 | 329 | function m:write(msg, tag) 330 | io.write(msg) 331 | end 332 | 333 | function m:writeln(msg, tag) 334 | msg = msg or '' 335 | self:write(msg .. '\n', tag) 336 | end 337 | 338 | return m 339 | end 340 | --- var_info.lua 341 | 342 | --- 変数情報を表すクラス. 343 | local VarInfo = {} 344 | 345 | --- VarInfoを作成する. 346 | -- name: 変数名 347 | -- value: 値 348 | -- index: 変数のインデックス 349 | -- is_upvalue: 上位値かどうか 350 | function VarInfo.create(name, value, index, is_upvalue) 351 | local m = { 352 | name = name, 353 | value_type = type(value), 354 | value = value, 355 | is_nil = (value == nil), 356 | index = index, 357 | new_value = value, 358 | is_upvalue = is_upvalue, 359 | } 360 | 361 | --- 指定したレベルで変数を更新する. 362 | -- level: レベル 363 | function m:update(level) 364 | self.value = self.new_value 365 | if is_upvalue then 366 | local func = debug.getinfo(level + 1, 'f').func 367 | debug.setupvalue(func, self.index, self.value) 368 | else 369 | debug.setlocal(level + 1, self.index, self.value) 370 | end 371 | end 372 | 373 | --- JSONに変換する. 374 | function m:toJSON() 375 | local t = { 376 | name = self.name, 377 | value_type = self.value_type, 378 | value = self.new_value, 379 | is_nil = self.is_nil, 380 | is_upvalue = is_upvalue, 381 | } 382 | return JSON.stringify(t) 383 | end 384 | 385 | return m 386 | end 387 | 388 | --- 指定したレベルのローカル変数をすべて取得する. 389 | -- level: レベル 390 | -- 指定したレベルで取得できる変数のVarInfoの配列 391 | function VarInfo.getlocal(level) 392 | 393 | local var_infoes = {} 394 | local defined_name = {} 395 | 396 | -- ローカル変数を取得する 397 | local local_index = 1 398 | while true do 399 | local name, value = debug.getlocal(level + 1, local_index) 400 | if not name then 401 | break 402 | end 403 | if name ~= '(*temporary)' then 404 | table.insert(var_infoes, VarInfo.create(name, value, local_index, false)) 405 | defined_name[name] = true 406 | end 407 | local_index = local_index + 1 408 | end 409 | 410 | -- 上位値を取得する 411 | local func = debug.getinfo(level + 1, 'f').func 412 | local up_index = 1 413 | while true do 414 | local name, value = debug.getupvalue(func, up_index) 415 | if not name then 416 | break 417 | end 418 | if _ENV == nil or value ~= _ENV then 419 | table.insert(var_infoes, VarInfo.create(name, value, up_index, true)) 420 | end 421 | up_index = up_index + 1 422 | end 423 | 424 | return var_infoes 425 | end 426 | --- context.lua 427 | 428 | --- デバッグコンテキスト 429 | local Context = { 430 | global_defined_lines = {} 431 | } 432 | 433 | for k, v in pairs(_G) do 434 | Context.global_defined_lines[k] = { 435 | source = 'Unknown', 436 | line = -1, 437 | } 438 | end 439 | 440 | --- Contextを作る. 441 | -- debug_info: デバッグ情報 442 | -- var_infoes: 変数情報 443 | function Context.create(debug_info, var_infoes) 444 | 445 | local var_defined_lines = {} 446 | 447 | -- ローカル変数の宣言箇所を記録 448 | -- とりあえず,この行にする 449 | for _, var_info in ipairs(var_infoes) do 450 | if not var_info.is_upvalue then 451 | var_defined_lines[var_info.name] = { 452 | source = utils:withoutPrefixSource(debug_info.source), 453 | line = debug_info.currentline, 454 | } 455 | end 456 | end 457 | 458 | local m = { 459 | var_infoes = var_infoes, 460 | var_defined_lines = var_defined_lines, 461 | name = (debug_info.name or ''), 462 | namewhat = debug_info.namewhat, 463 | what = debug_info.what, 464 | source = debug_info.source, 465 | currentline = debug_info.currentline, 466 | linedefined = debug_info.linedefined, 467 | lastlinedefined = debug_info.lastlinedefined, 468 | nups = debug_info.nups, 469 | nparams = debug_info.nparams, 470 | isvararg = debug_info.isvararg, 471 | istailcall = debug_info.istailcall, 472 | short_src = debug_info.short_src, 473 | func = debug_info.func, 474 | start_time_ms = os.clock() * 1000, 475 | start_memory_kB = collectgarbage('count') 476 | } 477 | 478 | --- コンテキストが作られた時からの経過時間と使用メモリを取得する. 479 | -- 経過時間[ms] 480 | -- 使用メモリ[kB] 481 | function m:record() 482 | local end_time_ms = os.clock() * 1000 483 | local duration_ms = end_time_ms - self.start_time_ms 484 | local use_memory_kB = collectgarbage('count') - self.start_memory_kB 485 | return duration_ms, use_memory_kB 486 | end 487 | 488 | --- 新しい情報に更新する. 489 | -- context: 更新する情報を持つコンテキスト 490 | function m:update(context) 491 | 492 | local warnings = {} 493 | 494 | -- グローバル変数の宣言箇所を記録 495 | for k, v in pairs(_G) do 496 | if not Context.global_defined_lines[k] then 497 | Context.global_defined_lines[k] = { 498 | source = utils:withoutPrefixSource(self.source), 499 | line = self.currentline, 500 | } 501 | end 502 | end 503 | 504 | -- 新しい変数の場合は宣言された場所を記録 505 | for name, var_defined_line in pairs(context.var_defined_lines) do 506 | if not self.var_defined_lines[name] then 507 | self.var_defined_lines[name] = { 508 | source = utils:withoutPrefixSource(self.source), 509 | line = self.currentline, 510 | } 511 | 512 | -- グローバル変数を上書いているか? 513 | if _G[name] then 514 | table.insert(warnings, string.format('local variable %s overwrites global variable', name)) 515 | end 516 | end 517 | end 518 | 519 | self.var_infoes = context.var_infoes 520 | self.name = context.name 521 | self.namewhat = context.namewhat 522 | self.what = context.what 523 | self.source = context.source 524 | self.currentline = context.currentline 525 | self.linedefined = context.linedefined 526 | self.lastlinedefined = context.lastlinedefined 527 | self.nups = context.nups 528 | self.nparams = context.nparams 529 | self.isvararg = context.isvararg 530 | self.istailcall = context.istailcall 531 | self.short_src = context.short_src 532 | self.func = context.func 533 | 534 | return warnings 535 | end 536 | 537 | --- JSONに変換する. 538 | function m:toJSON() 539 | local t = { 540 | var_infoes = self.var_infoes, 541 | var_defined_lines = self.var_defined_lines, 542 | name = self.name, 543 | namewhat = self.namewhat, 544 | what = self.what, 545 | source = utils:withoutPrefixSource(self.source), 546 | currentline = self.currentline, 547 | linedefined = self.linedefined, 548 | lastlinedefined = self.lastlinedefined, 549 | nups = self.nups, 550 | nparams = self.nparams, 551 | isvararg = self.isvararg, 552 | istailcall = self.istailcall or false, 553 | short_src = self.short_src, 554 | start_time_ms = self.start_time_ms, 555 | start_memory_kB = self.start_memory_kB, 556 | } 557 | 558 | -- グローバル変数も反映させておく 559 | for name, global_defined_line in pairs(Context.global_defined_lines) do 560 | if not self.var_defined_lines[name] and global_defined_line.line ~= -1 then 561 | self.var_defined_lines[name] = global_defined_line 562 | end 563 | end 564 | 565 | -- ない場合はnilにしておく 566 | if not next(t.var_defined_lines) then 567 | t.var_defined_lines = nil 568 | end 569 | 570 | return JSON.stringify(t) 571 | end 572 | 573 | return m 574 | end 575 | --- profiler.lua 576 | 577 | --- プロファイルを行うクラス. 578 | local Profiler = {} 579 | 580 | --- Profilerを作る. 581 | -- callback: 記録されるたびに呼ばれるコールバック 582 | function Profiler.create(callback) 583 | 584 | local m = { 585 | profiles = {}, 586 | callback = callback or function() end, 587 | } 588 | 589 | --- 関数ごとのIDを生成する. 590 | -- context: 元にするコンテキスト 591 | function m:id(context) 592 | local name = context.name or 'NO_NAME' 593 | local source = utils:withoutPrefixSource(context.source) 594 | local line = context.linedefined 595 | return string.format('%s(%s:%d)<%s>', name, source, line, tostring(context.func)) 596 | end 597 | 598 | --- プロファイルを記録する. 599 | -- context: 記録する情報 600 | function m:record(context) 601 | local duration_ms, use_memory_kB = context:record() 602 | local id = self:id(context) 603 | if not self.profiles[id] then 604 | self.profiles[id] = {} 605 | end 606 | local profile = { 607 | duration_ms = duration_ms, 608 | use_memory_kB = use_memory_kB, 609 | } 610 | table.insert(self.profiles[id], profile) 611 | self.callback(profile, self.profiles) 612 | end 613 | 614 | --- 集計を行う. 615 | -- 各関数ごと経過時間(平均と合計),使用メモリ(平均と合計),呼び出し回数 616 | function m:summary() 617 | local summary = {} 618 | 619 | for id, func_profiles in pairs(self.profiles) do 620 | local sum = { 621 | duration_ms = 0, 622 | use_memory_kB = 0.0 623 | } 624 | for _, profile in ipairs(func_profiles) do 625 | sum.duration_ms = sum.duration_ms + profile.duration_ms 626 | sum.use_memory_kB = sum.use_memory_kB + profile.use_memory_kB 627 | end 628 | 629 | local average = { 630 | duration_ms = sum.duration_ms / math.max(#func_profiles, 1), 631 | use_memory_kB = sum.use_memory_kB / math.max(#func_profiles, 1), 632 | } 633 | summary[id] = { 634 | sum = sum, 635 | average = average, 636 | count = #func_profiles, 637 | } 638 | end 639 | 640 | return summary 641 | end 642 | 643 | return m 644 | end 645 | -- evaluator.lua 646 | 647 | --- Lua 5.1用のeval関数. 648 | -- chunk: 実行するLuaコード 649 | -- lenv: 実行する環境 650 | local function eval51(chunk, lenv) 651 | setfenv(0, lenv) 652 | local fnc, err = loadstring(chunk) 653 | if err then 654 | return err 655 | end 656 | ok, err = pcall(fnc) 657 | setfenv(0, _G) 658 | if not ok then 659 | return err 660 | end 661 | return nil 662 | end 663 | 664 | --- Lua 5.2用のeval関数. 665 | -- chunk: 実行するLuaコード 666 | -- lenv: 実行する環境 667 | local function eval52(chunk, lenv) 668 | local fnc, err = load(chunk, 'eval', 't', lenv) 669 | if err then 670 | return err 671 | end 672 | local ok, err = pcall(fnc) 673 | if not ok then 674 | return err 675 | end 676 | return nil 677 | end 678 | 679 | --- 評価を行うクラス. 680 | local Evaluator = {} 681 | 682 | -- Luaのバージョンでチャンクを実行する関数が違う 683 | if _VERSION == 'Lua 5.2' then 684 | Evaluator.EVAL_FUNC = eval52 685 | else 686 | Evaluator.EVAL_FUNC = eval51 687 | end 688 | 689 | --- Evaluatorを作る. 690 | -- context: 評価に使用するコンテキスト 691 | function Evaluator.create(context) 692 | 693 | local m = { 694 | context = context, 695 | } 696 | 697 | --- 環境を作る. 698 | -- 環境 699 | -- 戻り値用の変数名 700 | function m:createLocalEnv() 701 | local lenv = utils:tableCopy(_G) 702 | for _, var_info in ipairs(self.context.var_infoes) do 703 | lenv[var_info.name] = var_info.new_value 704 | end 705 | 706 | -- 戻り値用の変数を用意 707 | local ret_key = '_ret' 708 | while lenv[ret_key] do 709 | ret_key = '_' .. ret_key 710 | end 711 | 712 | return lenv, ret_key 713 | end 714 | 715 | --- 指定したチャンクを評価する. 716 | --- is_ret_value を trueにした場合,指定できるチャンクは, 717 | --- 代入分の右辺値だけとなる. 718 | -- chunk: チャンク(Luaのコード) 719 | -- is_ret_value: 戻り値を必要とするか 720 | function m:eval(chunk, is_ret_value) 721 | local lenv, ret_key = self:createLocalEnv() 722 | 723 | -- 戻り値を戻り値用の変数に入れて取得できるようにする 724 | if is_ret_value then 725 | chunk = string.format('%s = (%s)', ret_key, chunk) 726 | end 727 | 728 | -- 実行 729 | local err = Evaluator.EVAL_FUNC(chunk, lenv) 730 | if err ~= nil then 731 | return nil, err 732 | end 733 | 734 | -- 変数の変更の反映 735 | for k, v in pairs(lenv) do 736 | 737 | -- ローカル変数に変更を反映 738 | local is_updated = false 739 | for _, var_info in ipairs(self.context.var_infoes) do 740 | if var_info.name == k then 741 | var_info.new_value = v 742 | is_updated = true 743 | break 744 | end 745 | end 746 | 747 | -- ローカル変数にない場合グローバル変数を更新 748 | if not is_updated and _G[k] then 749 | rawset(_G, k, v) 750 | end 751 | end 752 | 753 | -- 戻り値を取得 754 | local ret_val 755 | if is_ret_value then 756 | ret_val = lenv[ret_key] 757 | end 758 | 759 | return ret_val, nil 760 | end 761 | 762 | return m 763 | end 764 | --- break_point.lua 765 | 766 | --- ブレークポイントを表すクラス. 767 | local BreakPoint = {} 768 | 769 | function BreakPoint.create(source, line) 770 | 771 | local m = { 772 | source = source, 773 | line = line, 774 | } 775 | 776 | --- JSONにする. 777 | --- ソースを相対パスにする. 778 | function m:toJSON() 779 | local t = { 780 | source = utils:withoutPrefixSource(self.source), 781 | line = line, 782 | } 783 | 784 | return JSON.stringify(t) 785 | end 786 | 787 | return m 788 | end 789 | --- break_point_manager.lua 790 | 791 | --- ブレークポイントを管理するクラス. 792 | local BreakPointManager = {} 793 | 794 | --- BreakPointManagerを作る. 795 | function BreakPointManager.create() 796 | local m = { 797 | break_points = {}, 798 | } 799 | 800 | --- IDを取得する. 801 | -- source: ソースファイル 802 | -- line: 行番号 803 | function m:id(source, line) 804 | return string.format('%s:%d', source, line) 805 | end 806 | 807 | --- ブレークポイントを追加する. 808 | -- source: ソースファイル 809 | -- line: 行番号 810 | function m:add(source, line) 811 | local id = self:id(source, line) 812 | if self.break_points[id] then 813 | return 814 | end 815 | 816 | local break_point = BreakPoint.create(source, line) 817 | self.break_points[id] = break_point 818 | end 819 | 820 | --- 指定したブレークポイントを消す. 821 | -- source: ソースファイル 822 | -- line: 行番号 823 | function m:remove(source, line) 824 | local id = self:id(source, line) 825 | self.break_points[id] = nil 826 | end 827 | 828 | --- 設定されているブレークポイントをすべて消す. 829 | function m:clear() 830 | self.break_points = {} 831 | end 832 | 833 | --- 設定されているブレークポイントを取得する. 834 | -- すべてのブレークポイント. 835 | function m:getAll() 836 | local all_break_points = {} 837 | for _, break_point in pairs(self.break_points) do 838 | table.insert(all_break_points, break_point) 839 | end 840 | return all_break_points 841 | end 842 | 843 | --- 指定した箇所がブレークポイントかどうか取得する. 844 | -- source: ソースファイル 845 | -- line: 行番号 846 | function m:isBreakPoint(source, line) 847 | local id = self:id(source, line) 848 | return self.break_points[id] 849 | end 850 | 851 | --- ブレークポイントで止まるべきか取得する. 852 | -- call_stack: 現在のコールスタック 853 | function m:shouldStop(call_stack) 854 | local source = call_stack[1].source 855 | local currentline = call_stack[1].currentline 856 | return self:isBreakPoint(source, currentline) 857 | end 858 | 859 | return m 860 | end 861 | --- step_execute_manager.lua 862 | 863 | local StepExecuteManager = { 864 | MODE_STEP_OVER = 0, 865 | MODE_STEP_IN = 1, 866 | MODE_STEP_OUT = 2, 867 | } 868 | 869 | function StepExecuteManager.create() 870 | local m = { 871 | mode = nil, 872 | call_stack = nil, 873 | count = 0, 874 | } 875 | 876 | --- ステップオーバーを設定する. 877 | -- call_stack: コールスタック 878 | -- count: 実行するステップ数 879 | function m:setStepOver(call_stack, count) 880 | self.mode = StepExecuteManager.MODE_STEP_OVER 881 | self.call_stack = utils:tableCopy(call_stack) 882 | self.count = count 883 | end 884 | 885 | --- ステップインを設定する. 886 | -- call_stack: コールスタック 887 | -- count: 実行するステップ数 888 | function m:setStepIn(call_stack, count) 889 | self.mode = StepExecuteManager.MODE_STEP_IN 890 | self.call_stack = utils:tableCopy(call_stack) 891 | self.count = count 892 | end 893 | 894 | --- ステップアウトを設定する. 895 | -- call_stack: コールスタック 896 | -- count: 実行するステップ数 897 | function m:setStepOut(call_stack, count) 898 | self.mode = StepExecuteManager.MODE_STEP_OUT 899 | self.call_stack = utils:tableCopy(call_stack) 900 | self.count = count 901 | end 902 | 903 | --- ステップ実行をやめる. 904 | function m:clear() 905 | self.mode = nil 906 | self.call_stack = nil 907 | self.count = 0 908 | end 909 | 910 | --- 停止すべきか取得する. 911 | -- call_stack: コールスタック 912 | function m:shouldStop(call_stack) 913 | if not self.mode then 914 | return false 915 | end 916 | 917 | if self.mode == StepExecuteManager.MODE_STEP_OVER then 918 | return self:shouldStopStepOver(call_stack) 919 | end 920 | 921 | if self.mode == StepExecuteManager.MODE_STEP_IN then 922 | return self:shouldStopStepIn(call_stack) 923 | end 924 | 925 | if self.mode == StepExecuteManager.MODE_STEP_OUT then 926 | return self:shouldStopStepOut(call_stack) 927 | end 928 | end 929 | 930 | --- ステップオーバーで停止すべきか取得する. 931 | -- call_stack: コールスタック 932 | function m:shouldStopStepOver(call_stack) 933 | if #call_stack <= 0 or 934 | #self.call_stack <= 0 or 935 | utils:getLevelByFunc(self.call_stack, call_stack[1]) >= 1 then 936 | self.count = self.count - 1 937 | return self.count <= 0 938 | end 939 | return false 940 | end 941 | 942 | --- ステップインで停止すべきか取得する. 943 | -- call_stack: コールスタック 944 | function m:shouldStopStepIn(call_stack) 945 | self.count = self.count - 1 946 | return self.count <= 0 947 | end 948 | 949 | --- ステップアウトで停止すべきか取得する. 950 | -- call_stack: コールスタック 951 | function m:shouldStopStepOut(call_stack) 952 | if #call_stack <= 0 or 953 | #self.call_stack <= 0 or 954 | utils:getLevelByFunc(self.call_stack, call_stack[1]) > 1 then 955 | self.count = self.count - 1 956 | return self.count <= 0 957 | end 958 | return false 959 | end 960 | 961 | return m 962 | end 963 | --- watch_manager.lua 964 | 965 | --- ウォッチを管理するクラス. 966 | local WatchManager = {} 967 | 968 | --- WatchManagerを作る. 969 | function WatchManager.create() 970 | local m = { 971 | watches = {}, 972 | } 973 | 974 | --- ウォッチを追加する. 975 | --- ウォッチに指定できるチャンクは, 976 | --- 代入文の右辺値にできるものに限る. 977 | -- context: 現在のコンテキスト 978 | -- chunk: チャンク 979 | -- 不正なチャンクを指定した場合にエラーを返す. 980 | function m:add(context, chunk) 981 | 982 | if type(chunk) ~= 'string' then 983 | return 'chunk must be string' 984 | end 985 | 986 | local evaluator = Evaluator.create(context) 987 | local ret_val, err = evaluator:eval(chunk, true) 988 | 989 | if err ~= nil then 990 | return err 991 | end 992 | 993 | local watch = { 994 | chunk = chunk, 995 | value = ret_val, 996 | } 997 | 998 | table.insert(self.watches, watch) 999 | 1000 | return nil 1001 | end 1002 | 1003 | --- 指定したインデックスのウォッチ式を変更する. 1004 | -- index: インデックス 1005 | -- context: 現在のコンテキスト 1006 | -- chunk: チャンク 1007 | -- 不正なチャンクまたはインデックスを指定した場合にエラーを返す. 1008 | function m:set(index, context, chunk) 1009 | if type(chunk) ~= 'string' then 1010 | return 'chunk must be string' 1011 | end 1012 | 1013 | if index <= 0 or #self.watches < index then 1014 | return 'index is out of bounds' 1015 | end 1016 | 1017 | local evaluator = Evaluator.create(context) 1018 | local ret_val, err = evaluator:eval(chunk, true) 1019 | 1020 | if err ~= nil then 1021 | return err 1022 | end 1023 | 1024 | local watch = { 1025 | chunk = chunk, 1026 | value = ret_val, 1027 | } 1028 | table.insert(self.watches, index, watch) 1029 | 1030 | return nil 1031 | end 1032 | 1033 | --- 指定したインデックスのウォッチ式を削除する. 1034 | -- index: インデックス 1035 | function m:remove(index) 1036 | if self.watches[index] then 1037 | return table.remove(self.watches, index) 1038 | end 1039 | return nil 1040 | end 1041 | 1042 | --- 指定したコンテキストでウォッチ式の評価値を更新する. 1043 | -- context: コンテキスト 1044 | function m:update(context) 1045 | local evaluator = Evaluator.create(context) 1046 | for i, watch in ipairs(self.watches) do 1047 | local ret_val, err = evaluator:eval(watch.chunk, true) 1048 | if err ~= nil then 1049 | self.watches[i].value = tostring(err) 1050 | else 1051 | self.watches[i].value = ret_val 1052 | end 1053 | end 1054 | end 1055 | 1056 | return m 1057 | end 1058 | --- break_point_command_factory.lua 1059 | 1060 | local BreakPointCommandFactory = {} 1061 | 1062 | function BreakPointCommandFactory.create() 1063 | local m = {} 1064 | 1065 | --- ブレークポイントに関するコマンドを作る. 1066 | -- line: 入力された文字列 1067 | -- 入力された文字列から 1068 | -- ・ブレークポイントの追加 1069 | -- ・ブレークポイントの削除 1070 | -- ・ブレークポイントの一覧 1071 | -- のいずれかのコマンドを返す. 1072 | -- 上記のどれにも当てはまらなかったら,nil を返す. 1073 | function m:createCommand(line) 1074 | local cmd = utils:splitWords(line) 1075 | if not cmd and #cmd <= 0 then 1076 | return nil 1077 | end 1078 | 1079 | -- ブレークポイントの追加 1080 | if cmd[1] == 'addBreakPoint' or cmd[1] == 'ab' then 1081 | return function(debugger) 1082 | if cmd[3] then 1083 | debugger.break_point_manager:add(utils:withPrefixSource(cmd[2]), tonumber(cmd[3])) 1084 | else 1085 | debugger.break_point_manager:add(debugger.call_stack[1].source, tonumber(cmd[2])) 1086 | end 1087 | return true 1088 | end 1089 | end 1090 | 1091 | -- ブレークポイントの削除 1092 | if cmd[1] == 'removeBreakPoint' or cmd[1] == 'rb' then 1093 | return function(debugger) 1094 | if cmd[3] then 1095 | debugger.break_point_manager:remove(utils:withPrefixSource(cmd[2]), tonumber(cmd[3])) 1096 | else 1097 | debugger.break_point_manager:remove(debugger.call_stack[1].source, tonumber(cmd[2])) 1098 | end 1099 | return true 1100 | end 1101 | end 1102 | 1103 | -- ブレークポイントの一覧 1104 | if line == 'breakPointList' or line == 'bl' then 1105 | return function(debugger) 1106 | for id, _ in pairs(debugger.break_point_manager.break_points) do 1107 | debugger.writer:writeln(id) 1108 | end 1109 | return true 1110 | end 1111 | end 1112 | 1113 | return nil 1114 | end 1115 | 1116 | return m 1117 | end 1118 | --- defined_line_command_factory.lua 1119 | 1120 | local DefinedLineCommandFactory = {} 1121 | 1122 | function DefinedLineCommandFactory.create() 1123 | local m = {} 1124 | 1125 | --- listコマンドを作る. 1126 | -- line: 入力された文字列 1127 | -- 入力された文字列が definedLine コマンドに当てはまらなかった場合はnil 1128 | -- そうでない場合 definedLine コマンド 1129 | function m:createCommand(line) 1130 | local cmd = utils:splitWords(line) 1131 | if not cmd and #cmd <= 0 then 1132 | return nil 1133 | end 1134 | 1135 | if (cmd[1] == 'definedLine' or cmd[1] == 'd') and type(cmd[2]) == 'string' then 1136 | return function(debugger) 1137 | local var_name = cmd[2] 1138 | local defined_line 1139 | 1140 | for level = 1, #debugger.call_stack do 1141 | if debugger.call_stack[level].var_defined_lines[var_name] then 1142 | defined_line = debugger.call_stack[level].var_defined_lines[var_name] 1143 | break 1144 | end 1145 | end 1146 | 1147 | defined_line = defined_line or Context.global_defined_lines[var_name] 1148 | if defined_line then 1149 | local source = defined_line.source or '-' 1150 | local line = defined_line.line or -1 1151 | debugger.writer:writeln(string.format('%s:%d ', source, line)) 1152 | end 1153 | 1154 | return true 1155 | end 1156 | end 1157 | 1158 | return nil 1159 | end 1160 | 1161 | return m 1162 | end 1163 | --- eval_command_factory.lua 1164 | 1165 | --- Luaコードを実行するコマンドを作るファクトリクラス. 1166 | local EvalCommandFactory = {} 1167 | 1168 | --- EvalCommandFactoryを作る. 1169 | function EvalCommandFactory.create() 1170 | local m = {} 1171 | 1172 | --- evalコマンドを作る. 1173 | -- line: 入力された文字列 1174 | -- evalコマンド 1175 | function m:createCommand(line) 1176 | return function(debugger) 1177 | local context = debugger.call_stack[1] 1178 | local evaluator = Evaluator.create(context) 1179 | local _, err = evaluator:eval(line) 1180 | 1181 | if err then 1182 | debugger.writer:writeln("Command not found or given Lua chunk has error.") 1183 | debugger.writer:writeln('Eval ERROR: ' .. err) 1184 | end 1185 | 1186 | -- ウォッチ式の更新 1187 | debugger.watch_manager:update(context) 1188 | 1189 | return true 1190 | end 1191 | end 1192 | 1193 | return m 1194 | end 1195 | --- info_command_factory.lua 1196 | 1197 | local InfoCommandFactory = {} 1198 | 1199 | function InfoCommandFactory.create() 1200 | local m = {} 1201 | 1202 | --- infoコマンドを作る. 1203 | -- line: 入力された文字列 1204 | -- 入力された文字列が info コマンドに当てはまらなかった場合はnil 1205 | -- そうでない場合 info コマンド 1206 | function m:createCommand(line) 1207 | local cmd = utils:splitWords(line) 1208 | if not cmd and #cmd <= 0 then 1209 | return nil 1210 | end 1211 | 1212 | if cmd[1] == 'info' or cmd[1] == 'i' then 1213 | local cmd_index = 2 1214 | 1215 | -- コールスタック情報を表示する 1216 | local call_stack_cmd = function(debugger) 1217 | debugger.writer:writeln('call stack:') 1218 | if tonumber(cmd[cmd_index+1]) then 1219 | local call_stack = utils:slice(debugger.call_stack, tonumber(cmd[cmd_index+1])) 1220 | debugger.writer:writeln(JSON.stringify(call_stack), Writer.TAG.CALL_STACK) 1221 | cmd_index = cmd_index + 1 1222 | else 1223 | debugger.writer:writeln(JSON.stringify(debugger.call_stack), Writer.TAG.CALL_STACK) 1224 | end 1225 | debugger.writer:writeln() 1226 | end 1227 | 1228 | -- ブレークポイント情報を表示する 1229 | local break_points_cmd = function(debugger) 1230 | debugger.writer:writeln('break points:') 1231 | local break_points = debugger.break_point_manager.break_points 1232 | if next(break_points) then 1233 | debugger.writer:writeln(JSON.stringify(break_points), Writer.TAG.BREAK_POINTS) 1234 | else 1235 | debugger.writer:writeln('{}', Writer.TAG.BREAK_POINTS) 1236 | end 1237 | debugger.writer:writeln() 1238 | end 1239 | 1240 | -- ウォッチ情報を表示する 1241 | local watches_cmd = function(debugger) 1242 | debugger.writer:writeln('watches:') 1243 | debugger.writer:writeln(JSON.stringify(debugger.watch_manager.watches), Writer.TAG.WATCHES) 1244 | debugger.writer:writeln() 1245 | end 1246 | 1247 | return function(debugger) 1248 | -- 指定がない場合はすべて 1249 | if not cmd[cmd_index] then 1250 | call_stack_cmd(debugger) 1251 | break_points_cmd(debugger) 1252 | watches_cmd(debugger) 1253 | return true 1254 | end 1255 | 1256 | while cmd[cmd_index] do 1257 | if cmd[cmd_index] == 'call_stack' then 1258 | call_stack_cmd(debugger) 1259 | elseif cmd[cmd_index] == 'break_points' then 1260 | break_points_cmd(debugger) 1261 | elseif cmd[cmd_index] == 'watches' then 1262 | watches_cmd(debugger) 1263 | end 1264 | cmd_index = cmd_index + 1 1265 | end 1266 | 1267 | return true 1268 | end 1269 | end 1270 | return nil 1271 | end 1272 | 1273 | return m 1274 | end 1275 | --- list_command_factory.lua 1276 | 1277 | --- 現在の行の周辺の行を表示するコマンドを作るファクトリクラス. 1278 | local ListCommandFactory = { 1279 | -- デフォルトの表示する行数(現在の行の他に表示する上下の行数) 1280 | DEFAULT_NUM_LINES = 3, 1281 | } 1282 | 1283 | --- ListCommandFactoryを作る 1284 | function ListCommandFactory.create() 1285 | local m = {} 1286 | 1287 | --- listコマンドを作る. 1288 | -- line: 入力された文字列 1289 | -- 入力された文字列が list コマンドに当てはまらなかった場合はnil 1290 | -- そうでない場合 list コマンド 1291 | function m:createCommand(line) 1292 | local cmd = utils:splitWords(line) 1293 | if not cmd and #cmd <= 0 then 1294 | return nil 1295 | end 1296 | 1297 | if cmd[1] == 'list' or cmd[1] == 'l' then 1298 | return function(debugger) 1299 | local context = debugger.call_stack[1] 1300 | local num_lines = tonumber(cmd[2] or ListCommandFactory.DEFAULT_NUM_LINES) 1301 | local reader = Reader.create(utils:getSource(context)) 1302 | local lines = reader:lines() 1303 | 1304 | for i = math.max(context.currentline - num_lines, 1), math.min(context.currentline + num_lines, #lines) do 1305 | 1306 | -- 現在の行の場合は>を出す 1307 | if i == context.currentline then 1308 | debugger.writer:write('>') 1309 | else 1310 | debugger.writer:write(' ') 1311 | end 1312 | 1313 | -- ブレークポイントの場合は*を出す 1314 | if debugger.break_point_manager:isBreakPoint(context.source, i) then 1315 | debugger.writer:write('*') 1316 | else 1317 | debugger.writer:write(' ') 1318 | end 1319 | 1320 | local fmt = '%' .. tostring(utils:numDigits(#lines)) .. 'd: %s' 1321 | print(string.format(fmt, i, lines[i])) 1322 | end 1323 | 1324 | return true 1325 | end 1326 | end 1327 | return nil 1328 | end 1329 | 1330 | return m 1331 | 1332 | end 1333 | --- profile_command_factory.lua 1334 | 1335 | --- プロファイルを行うためのコマンド 1336 | local ProfileCommandFactory = {} 1337 | 1338 | --- ProfileCommandFactoryを作る. 1339 | function ProfileCommandFactory.create() 1340 | local m = { 1341 | last_profiler = nil 1342 | } 1343 | 1344 | --- プロファイリングに関するコマンドを作る. 1345 | -- line: 入力された文字列 1346 | -- 入力された文字列から 1347 | -- ・プロファイラの開始 1348 | -- ・プロファイル結果の出力 1349 | -- ・プロファイラの終了 1350 | -- のいずれかのコマンドを返す. 1351 | -- 上記のどれにも当てはまらなかったら,nil を返す. 1352 | function m:createCommand(line) 1353 | local cmd = utils:splitWords(line) 1354 | if not cmd and #cmd <= 0 then 1355 | return nil 1356 | end 1357 | 1358 | if cmd[1] == 'startProfile' or cmd[1] == 'sp' then 1359 | return function(debugger) 1360 | debugger:startProfile() 1361 | debugger.writer:writeln('start profiler') 1362 | return true 1363 | end 1364 | end 1365 | 1366 | if cmd[1] == 'profile' or cmd[1] == 'p' then 1367 | return function(debugger) 1368 | if not m.last_profiler then 1369 | debugger.writer:writeln('ERROR: profiler is running or does not start', 'ERROR') 1370 | return true 1371 | end 1372 | 1373 | local summary = m.last_profiler:summary() 1374 | if not next(summary) then 1375 | return true 1376 | end 1377 | 1378 | debugger.writer:writeln(JSON.stringify(summary)) 1379 | return true 1380 | end 1381 | end 1382 | 1383 | if cmd[1] == 'endProfile' or cmd[1] == 'ep' then 1384 | return function(debugger) 1385 | m.last_profiler = debugger.profiler 1386 | debugger:endProfile() 1387 | debugger.writer:writeln('stop profiler') 1388 | return true 1389 | end 1390 | end 1391 | 1392 | return nil 1393 | end 1394 | 1395 | return m 1396 | 1397 | end 1398 | --- run_command_factory.lua 1399 | 1400 | --- runコマンドを作成するファクトリクラス. 1401 | local RunCommandFactory = {} 1402 | 1403 | --- RunCommandFactoryを作る. 1404 | function RunCommandFactory.create() 1405 | local m = {} 1406 | 1407 | --- runコマンドを作る. 1408 | -- line: 入力された文字列 1409 | -- 入力された文字列が run コマンドに当てはまらなかった場合はnil 1410 | -- そうでない場合 run コマンド 1411 | function m:createCommand(line) 1412 | if line == 'run' or line == 'r' then 1413 | return function(debugger) 1414 | return false 1415 | end 1416 | end 1417 | return nil 1418 | end 1419 | 1420 | return m 1421 | end 1422 | -- step_command_factory.lua 1423 | 1424 | --- ステップ実行に関するコマンドを作るファクトリクラス. 1425 | local StepCommandFactory = {} 1426 | 1427 | --- StepCommandFactoryを作る. 1428 | function StepCommandFactory.create() 1429 | local m = {} 1430 | 1431 | --- ステップ実行に関するコマンドを作る. 1432 | -- line: 入力された文字列 1433 | -- 入力された文字列から 1434 | -- ・ステップオーバー 1435 | -- ・ステップイン 1436 | -- ・ステップアウト 1437 | -- のいずれかのコマンドを返す. 1438 | -- 上記のどれにも当てはまらなかったら,nil を返す. 1439 | function m:createCommand(line) 1440 | local cmd = utils:splitWords(line) 1441 | if not cmd and #cmd <= 0 then 1442 | return nil 1443 | end 1444 | 1445 | -- ステップオーバー 1446 | if cmd[1] == 'step' or cmd[1] == 's' then 1447 | return function(debugger) 1448 | debugger.step_execute_manager:setStepOver(debugger.call_stack, tonumber(cmd[2] or '1')) 1449 | return false 1450 | end 1451 | end 1452 | 1453 | -- ステップイン 1454 | if cmd[1] == 'stepIn' or cmd[1] == 'si' then 1455 | return function(debugger) 1456 | debugger.step_execute_manager:setStepIn(debugger.call_stack, tonumber(cmd[2] or '1')) 1457 | return false 1458 | end 1459 | end 1460 | 1461 | -- ステップアウト 1462 | if cmd[1] == 'stepOut' or cmd[1] == 'so' then 1463 | return function(debugger) 1464 | debugger.step_execute_manager:setStepOut(debugger.call_stack, tonumber(cmd[2] or '1')) 1465 | return false 1466 | end 1467 | end 1468 | 1469 | return nil 1470 | end 1471 | 1472 | return m 1473 | end 1474 | --- vars_command_factory.lua 1475 | 1476 | local VarsCommandFactory = {} 1477 | 1478 | function VarsCommandFactory.create() 1479 | local m = { 1480 | showDefinedLine = false, 1481 | } 1482 | 1483 | --- listコマンドを作る. 1484 | -- line: 入力された文字列 1485 | -- 入力された文字列が vars コマンドに当てはまらなかった場合はnil 1486 | -- そうでない場合 vars コマンド 1487 | function m:createCommand(line) 1488 | local cmd = utils:splitWords(line) 1489 | if not cmd and #cmd <= 0 then 1490 | return nil 1491 | end 1492 | 1493 | if cmd[1] == 'vars' or cmd[1] == 'v' then 1494 | return function(debugger) 1495 | local level = math.min(tonumber(cmd[2] or 1) or 1, #debugger.call_stack) 1496 | local var_infoes = debugger.call_stack[level].var_infoes 1497 | local var_defined_lines = debugger.call_stack[level].var_defined_lines 1498 | local show_level = tonumber(cmd[3]) -- nilの場合はデフォルト値で表示される 1499 | for _, var_info in ipairs(var_infoes) do 1500 | if self.showDefinedLine then 1501 | local source = var_defined_lines[var_info.name].source or '-' 1502 | local line = var_defined_lines[var_info.name].line or -1 1503 | debugger.writer:write(string.format('%s:%d ', source, line)) 1504 | end 1505 | debugger.writer:writeln(string.format('%s(%s): %s', var_info.name, var_info.value_type, utils:inspect(var_info.value, show_level))) 1506 | end 1507 | 1508 | return true 1509 | end 1510 | end 1511 | 1512 | return nil 1513 | end 1514 | 1515 | return m 1516 | end 1517 | --- watch_command_factory.lua 1518 | 1519 | local WatchCommandFactory = {} 1520 | 1521 | function WatchCommandFactory.create() 1522 | local m = {} 1523 | 1524 | --- ウォッチに関するコマンドを作る. 1525 | -- line: 入力された文字列 1526 | -- 入力された文字列から 1527 | -- ・ウォッチの追加 1528 | -- ・ウォッチの削除 1529 | -- ・ウォッチの一覧 1530 | -- のいずれかのコマンドを返す. 1531 | -- 上記のどれにも当てはまらなかったら,nil を返す. 1532 | function m:createCommand(line) 1533 | local cmd = utils:splitWords(line) 1534 | if not cmd and #cmd <= 0 then 1535 | return nil 1536 | end 1537 | 1538 | -- ウォッチ式の一覧 1539 | if cmd[1] == 'watch' or cmd[1] == 'w' then 1540 | return function(debugger) 1541 | local watches = debugger.watch_manager.watches 1542 | local fmt = '%' .. tostring(utils:numDigits(#watches)) .. 'd: %s = %s' 1543 | for i, watch in ipairs(watches) do 1544 | local str_value = utils:inspect(watch.value) 1545 | debugger.writer:writeln(string.format(fmt, i, watch.chunk, str_value)) 1546 | end 1547 | return true 1548 | end 1549 | end 1550 | 1551 | -- ウォッチ式の追加・更新 1552 | if cmd[1] == 'setWatch' or cmd[1] == 'sw' then 1553 | return function(debugger) 1554 | local context = debugger.call_stack[1] 1555 | local chunk, err 1556 | local index = tonumber(cmd[2]) 1557 | if not index then 1558 | chunk = utils:join(utils:slice(cmd, 2), " ") 1559 | err = debugger.watch_manager:add(context, chunk) 1560 | else 1561 | chunk = utils:join(utils:slice(cmd, 3), " ") 1562 | err = debugger.watch_manager:set(index, context, chunk) 1563 | end 1564 | 1565 | if err then 1566 | debugger.writer:writeln('ERROR: ' .. tostring(err)) 1567 | return true 1568 | end 1569 | 1570 | debugger.writer:writeln('add watch ' .. chunk) 1571 | return true 1572 | end 1573 | end 1574 | 1575 | -- ウォッチ式の削除 1576 | if cmd[1] == 'removeWatch' or cmd[1] == 'rw' then 1577 | local index = tonumber(cmd[2]) 1578 | if not index then 1579 | return nil 1580 | end 1581 | 1582 | return function(debugger) 1583 | local watch = debugger.watch_manager:remove(index) 1584 | if watch then 1585 | debugger.writer:writeln('remove watch ' .. watch.chunk) 1586 | end 1587 | return true 1588 | end 1589 | end 1590 | 1591 | return nil 1592 | end 1593 | 1594 | return m 1595 | end 1596 | --- prompt.lua 1597 | 1598 | --- プロンプトを扱うクラス. 1599 | local Prompt = {} 1600 | 1601 | --- Promptを作る. 1602 | function Prompt.create() 1603 | 1604 | local m = { 1605 | command_factories = { 1606 | StepCommandFactory.create(), -- ステップ実行 1607 | RunCommandFactory.create(), -- Run 1608 | BreakPointCommandFactory.create(), -- ブレークポイント 1609 | InfoCommandFactory.create(), -- デバッグ情報の出力 1610 | ListCommandFactory.create(), -- ソースコードの出力 1611 | VarsCommandFactory.create(), -- 変数の出力 1612 | DefinedLineCommandFactory.create(), -- 変数の宣言位置の出力 1613 | WatchCommandFactory.create(), -- ウォッチ式 1614 | ProfileCommandFactory.create(), -- プロファイラ 1615 | EvalCommandFactory.create(), -- 式の評価(最後にしないとダメ) 1616 | }, 1617 | } 1618 | 1619 | --- 処理を再開するコマンドを受け付けるまで, 1620 | --- コマンドの受付をループする. 1621 | -- debugger: デバッガ 1622 | function m:loop(debugger) 1623 | while true do 1624 | local is_loop = self:doCommand(debugger) 1625 | if not is_loop then 1626 | break 1627 | end 1628 | 1629 | -- ループする場合はコールバックを呼んでおく 1630 | debugger.callback(debugger) 1631 | end 1632 | end 1633 | 1634 | --- コマンドを受け付けて実行する. 1635 | -- debugger: デバッガ 1636 | function m:doCommand(debugger) 1637 | debugger.writer:write('LUPE>') 1638 | local line = io.read() 1639 | for _, command_factory in pairs(self.command_factories) do 1640 | local cmd = command_factory:createCommand(line) 1641 | if cmd then 1642 | return cmd(debugger) 1643 | end 1644 | end 1645 | 1646 | return true 1647 | end 1648 | 1649 | return m 1650 | end 1651 | --- coroutine_debugger.lua 1652 | 1653 | --- コルーチンをデバッグするための機能を提供するクラス. 1654 | local CoroutineDebugger = {} 1655 | 1656 | --- CoroutineDebuggerを作る. 1657 | -- debugger: デバッガ本体 1658 | function CoroutineDebugger.create(debugger) 1659 | 1660 | local m = { 1661 | debugger = debugger, 1662 | threads = {}, 1663 | } 1664 | 1665 | --- Lua5.1用にsethookする. 1666 | -- mode: hookのモード 1667 | function m.sethook51(mode) 1668 | local hook = debug.gethook() 1669 | if not hook and m.debugger.is_started then 1670 | debug.sethook(m.debugger.stop_callback, mode) 1671 | end 1672 | end 1673 | 1674 | --- Lua5.2用にsethookする. 1675 | -- mode: hookのモード 1676 | function m.sethook52(mode) 1677 | local th = coroutine.running() 1678 | local hook = debug.gethook(th) 1679 | if not hook and m.debugger.is_started then 1680 | debug.sethook(th, m.debugger.stop_callback, mode) 1681 | table.insert(m.threads, th) 1682 | end 1683 | end 1684 | 1685 | -- Luaのバージョンによって,sethookの方法を分ける 1686 | function m.sethook(mode) 1687 | if _VERSION == 'Lua 5.2' then 1688 | m.sethook52(mode) 1689 | else 1690 | m.sethook51(mode) 1691 | end 1692 | end 1693 | 1694 | --- コルーチンのデバッグを開始する. 1695 | --- coroutine.createとcoroutine.wrapを上書いているため注意する. 1696 | function m:start() 1697 | self.cocreate = coroutine.create 1698 | coroutine.create = function(func) 1699 | return self.cocreate(function(...) 1700 | m.sethook('crl') 1701 | return func(...) 1702 | end) 1703 | end 1704 | 1705 | self.cowrap = coroutine.wrap 1706 | coroutine.wrap = function(func) 1707 | return self.cowrap(function(...) 1708 | m.sethook('crl') 1709 | return func(...) 1710 | end) 1711 | end 1712 | end 1713 | 1714 | --- コルーチンのデバッグを停止する. 1715 | --- Lua 5.1では,debug.sethookにthreadを渡せないため,うまく動作しない. 1716 | function m:stop() 1717 | coroutine.create = self.cocreate or coroutine.create 1718 | coroutine.wrap = self.cowrap or coroutine.wrap 1719 | for _, th in ipairs(self.threads) do 1720 | debug.sethook(th) 1721 | end 1722 | end 1723 | 1724 | return m 1725 | end 1726 | --- lupe.lua 1727 | 1728 | --- デバッガの機能を提供するクラス. 1729 | local Lupe = {} 1730 | 1731 | --- Lupeを作る. 1732 | function Lupe.create() 1733 | local m = { 1734 | callback = function()end, 1735 | call_stack = {}, 1736 | top_level_context = nil, 1737 | prompt = Prompt.create(), 1738 | break_point_manager = BreakPointManager.create(), 1739 | step_execute_manager = StepExecuteManager.create(), 1740 | watch_manager = WatchManager.create(), 1741 | profiler = nil, 1742 | writer = Writer.create(), 1743 | is_called = false, 1744 | is_started = false, 1745 | -- 1746 | JSON = JSON, 1747 | } 1748 | 1749 | m.coroutine_debugger = CoroutineDebugger.create(m) 1750 | 1751 | --- sethookで呼ばれるコードバック 1752 | function m.stop_callback(t) 1753 | 1754 | local debug_info = debug.getinfo(2) 1755 | if not debug_info or utils:isLupe(debug_info) then 1756 | return 1757 | end 1758 | 1759 | local var_infoes = VarInfo.getlocal(2) 1760 | local context = Context.create(debug_info, var_infoes) 1761 | 1762 | -- 各行 1763 | if t == 'line' then 1764 | m:lineStop(context) 1765 | 1766 | -- 変数の変更があったら反映 1767 | for _, var_info in pairs(context.var_infoes) do 1768 | var_info:update(2) 1769 | end 1770 | 1771 | -- ウォッチ式の更新 1772 | m.watch_manager:update(context) 1773 | return 1774 | end 1775 | 1776 | -- 関数呼び出し 1777 | if t == 'call' or t == 'tail call' then 1778 | m:callStop(context) 1779 | return 1780 | end 1781 | 1782 | -- return 1783 | if t == 'return' or t == 'tail return' then 1784 | m:returnStop(context) 1785 | return 1786 | end 1787 | end 1788 | 1789 | local function __call() 1790 | m.is_called = true 1791 | end 1792 | 1793 | --- デバッグを開始する. 1794 | function m:start() 1795 | debug.sethook(self.stop_callback, 'crl') 1796 | self.coroutine_debugger:start() 1797 | self.is_started = true 1798 | end 1799 | 1800 | --- デバッガを停止する. 1801 | function m:stop() 1802 | debug.sethook() 1803 | self.coroutine_debugger:stop() 1804 | self.call_stack = {} 1805 | self.is_started = false 1806 | self.break_point_manager:clear() 1807 | end 1808 | 1809 | --- コールスタックを消します. 1810 | function m:clear() 1811 | self.call_stack = {} 1812 | end 1813 | 1814 | --- プロファイルを開始する. 1815 | function m:startProfile() 1816 | self.profiler = Profiler.create() 1817 | end 1818 | 1819 | --- プロファイルを停止する. 1820 | function m:endProfile() 1821 | self:dump(self.profiler:summary()) 1822 | self.profiler = nil 1823 | end 1824 | 1825 | --- infoコマンドを実行する. 1826 | -- サブコマンド 1827 | function m:info(sub_cmd) 1828 | local info_cmd_factory = InfoCommandFactory.create() 1829 | local line = 'info' 1830 | if sub_cmd then 1831 | line = line .. ' ' .. sub_cmd 1832 | end 1833 | local info_cmd = info_cmd_factory:createCommand(line) 1834 | info_cmd(self) 1835 | end 1836 | 1837 | --- 行ごとに呼ばれる. 1838 | --- この行で止まるべきか判断し,止まる場合はコマンドを受け付けるプロンプトを表示させる. 1839 | -- context: コンテキスト 1840 | function m:lineStop(context) 1841 | local is_top_level = false 1842 | if #self.call_stack <= 0 then 1843 | self.top_level_context = self.top_level_context or context 1844 | self.call_stack[1] = self.top_level_context 1845 | is_top_level = true 1846 | end 1847 | 1848 | -- コンテキストのアップデート 1849 | local warnings = self.call_stack[1]:update(context) 1850 | if warnings and #warnings > 0 then 1851 | self.writer:writeln('====== WARNING ======') 1852 | for _, warning in pairs(warnings) do 1853 | self.writer:writeln(tostring(warning), Writer.TAG.WARNING) 1854 | end 1855 | self.writer:writeln('====== WARNING ======') 1856 | end 1857 | 1858 | -- デバッグモードに入ったか,ブレークポイントか,ステップ実行で停止するか? 1859 | if self.is_called or 1860 | self.break_point_manager:shouldStop(self.call_stack) or 1861 | self.step_execute_manager:shouldStop(self.call_stack) then 1862 | 1863 | -- コールバックを呼ぶ 1864 | self.callback(self) 1865 | 1866 | self:showPrompt(context) 1867 | end 1868 | 1869 | if is_top_level then 1870 | table.remove(self.call_stack, 1) 1871 | end 1872 | end 1873 | 1874 | --- プロンプトを表示させる 1875 | -- context: コンテキスト 1876 | function m:showPrompt(context) 1877 | self.is_called = false 1878 | self.step_execute_manager:clear() 1879 | self.writer:writeln(string.format('stop at %s:%d', context.source, context.currentline)) 1880 | self.prompt:loop(self) 1881 | end 1882 | 1883 | --- スタックトレースを表示させる 1884 | -- msg: 一緒に表示するメッセージ 1885 | function m:traceback(msg) 1886 | if msg then 1887 | self.writer:writeln(tostring(msg)) 1888 | end 1889 | local count = 0 1890 | for _, context in ipairs(self.call_stack) do 1891 | if context.source ~= '=[C]' then 1892 | count = count + 1 1893 | for i = 1, count do 1894 | self.writer:write(' ') 1895 | end 1896 | self.writer:writeln(string.format('%s:%d %s', context.source, context.currentline, context.name)) 1897 | end 1898 | end 1899 | end 1900 | 1901 | --- 関数の呼び出し時に呼ばれる. 1902 | --- コールスタックをプッシュする. 1903 | -- context: コンテキスト 1904 | function m:callStop(context) 1905 | table.insert(self.call_stack, 1, context) 1906 | end 1907 | 1908 | --- 関数のreturn時に呼ばれる. 1909 | --- コールスタックをポップする. 1910 | -- context: コンテキスト 1911 | function m:returnStop(context) 1912 | -- Lua 5.2 では,tail call は return イベントが呼ばれない. 1913 | -- そのため, istailcall が true の間は pop し続ける. 1914 | while true do 1915 | local _context = table.remove(self.call_stack, 1) 1916 | 1917 | -- プロファイルを行う 1918 | if self.profiler then 1919 | self.profiler:record(_context) 1920 | end 1921 | 1922 | if not _context or not _context.istailcall then 1923 | break 1924 | end 1925 | end 1926 | end 1927 | 1928 | --- 値をダンプする. 1929 | -- value: ダンプする値 1930 | function m:dump(value, max_level) 1931 | self.writer:writeln(utils:inspect(value, max_level)) 1932 | end 1933 | 1934 | return setmetatable(m, {__call = __call}) 1935 | end 1936 | 1937 | --- export 1938 | rawset(_G, 'Lupe', Lupe.create()) 1939 | --- wrapper.lua 1940 | 1941 | --- assertが呼ばれたら停止する 1942 | local _assert = assert 1943 | assert = function(...) 1944 | local args = {...} 1945 | if not args[1] then 1946 | local debugger = rawget(_G, 'Lupe') 1947 | debugger.writer:writeln('====== STOP BY ASSERT ======') 1948 | debugger:traceback(args[2]) 1949 | local debug_info = debug.getinfo(2) 1950 | local var_infoes = VarInfo.getlocal(2) 1951 | local context = Context.create(debug_info, var_infoes) 1952 | if not debugger.call_stack[1] then 1953 | debugger.call_stack[1] = context 1954 | else 1955 | debugger.call_stack[1]:update(context) 1956 | end 1957 | debugger:showPrompt(debugger.call_stack[1]) 1958 | _assert(...) 1959 | end 1960 | end 1961 | --------------------------------------------------------------------------------