class DEBUGGER__ trap("INT") { DEBUGGER__::CONTEXT.interrupt } $DEBUG = true def initialize @break_points = [] @stop_next = 1 @frames = [nil] @last_file = nil @last = [nil, nil] @scripts = {} end DEBUG_LAST_CMD = [] def interrupt @stop_next = 1 end def debug_eval(str, binding) begin val = eval(str, binding) val rescue at = caller(0) STDOUT.printf "%s:%s\n", at.shift, $! for i in at break if i =~ /`debug_(eval|command)'$/ #` STDOUT.printf "\tfrom %s\n", i end end end def debug_command(file, line, id, binding) frame_pos = 0 binding_file = file binding_line = line previus_line = nil if (ENV['EMACS'] == 't') STDOUT.printf "\032\032%s:%d:\n", binding_file, binding_line else STDOUT.printf "%s:%d:%s", binding_file, binding_line, line_at(binding_file, binding_line) end @frames[0] = binding STDOUT.print "(rdb:-) " STDOUT.flush while input = STDIN.gets input.chop! if input == "" input = DEBUG_LAST_CMD[0] else DEBUG_LAST_CMD[0] = input end case input when /^b(?:reak)?\s+((?:[^:\n]+:)?.+)/ pos = $1 if pos.index ":" file, pos = pos.split(":") end file = File.basename(file) if pos =~ /^\d+$/ pname = pos pos = Integer(pos) else pname = pos = pos.intern.id2name end STDOUT.printf "Set breakpoint %d at %s:%s\n", @break_points.size, file, pname @break_points.push [file, pos] when /^b(?:reak)?$/, /^i(?:nfo) b(?:reak)?$/ n = 0 for f, p in @break_points STDOUT.printf "%d %s:%s\n", n, f, p n += 1 end when /^del(?:ete)?(?:\s+(\d+))?$/ pos = $1 unless pos STDOUT.print "clear all breakpoints? (y/n) " STDOUT.flush input = STDIN.gets.chop! if input == "y" for n in @break_points.indexes @break_points[n] = nil end end else pos = Integer(pos) if @break_points[pos] bp = @break_points[pos] STDOUT.printf "Clear breakpoint %d at %s:%s\n", pos, bp[0], bp[1] @break_points[pos] = nil else STDOUT.printf "Breakpoint %d is not defined\n", pos end end when /^c(?:ont)?$/ return when /^s(?:tep)?\s*(\d+)?$/ if $1 lev = Integer($1) else lev = 1 end @stop_next = lev return when /^n(?:ext)?\s*(\d+)?$/ if $1 lev = Integer($1) else lev = 1 end @stop_next = lev @no_step = @frames.size - frame_pos return when /^w(?:here)?$/, /^f(?:rame)?$/ at = caller(0) 0.upto( @frames.size - 1 ) do |n| if ( frame_pos == n ) STDOUT.printf "--> #%d %s\n", n, at[-(@frames.size - n)] else STDOUT.printf " #%d %s\n", n, at[-(@frames.size - n)] end end when /^l(?:ist)?(?:\s+(.+))?$/ if !$1 b = previus_line ? previus_line + 10 : binding_line - 5 e = b + 9 elsif $1 == '-' b = previus_line ? previus_line - 10 : binding_line - 5 e = b + 9 else b, e = $1.split(/[-,]/) if e b = Integer(b) e = Integer(e) else b = Integer(b) - 5 e = b + 9 end end previus_line = b STDOUT.printf "[%d, %d] in %s\n", b, e, binding_file line_at(binding_file, binding_line) if lines = @scripts[binding_file] and lines != true n = 0 b.upto(e) do |n| if n > 0 && lines[n-1] if ( n == binding_line ) STDOUT.printf "=> %d %s\n", n, lines[n-1].chomp else STDOUT.printf " %d %s\n", n, lines[n-1].chomp end end end else STDOUT.printf "no sourcefile available for %s\n", binding_file end when /^up\s*(\d+)?$/ previus_line = nil if $1 lev = Integer($1) else lev = 1 end frame_pos += lev if frame_pos >= @frames.size frame_pos = @frames.size - 1 STDOUT.print "at toplevel\n" end binding = @frames[frame_pos] info, binding_file, binding_line = frame_info(frame_pos) STDOUT.printf "#%d %s\n", frame_pos, info when /^down\s*(\d+)?$/ previus_line = nil if $1 lev = Integer($1) else lev = 1 end frame_pos -= lev if frame_pos < 0 frame_pos = 0 STDOUT.print "at stack bottom\n" end binding = @frames[frame_pos] info, binding_file, binding_line = frame_info(frame_pos) STDOUT.printf "#%d %s\n", frame_pos, info when /^fi(?:nish)?$/ @finish_pos = @frames.size - frame_pos frame_pos = 0 return when /^q(?:uit)?$/ STDOUT.print "really quit? (y/n) " STDOUT.flush input = STDIN.gets.chop! exit if input == "y" when /^p\s+/ p debug_eval($', binding) else v = debug_eval(input, binding) p v unless v == nil end STDOUT.print "(rdb:-) " STDOUT.flush end end def frame_info(pos = 0) info = caller(0)[-(@frames.size - pos)] info.sub( /:in `.*'$/, '' ) =~ /^(.*):(\d+)$/ #` [info, $1, $2.to_i] end def line_at(file, line) lines = @scripts[file] if lines return "\n" if lines == true line = lines[line-1] return "\n" unless line return line end save = $DEBUG begin $DEBUG = false f = open(file) lines = @scripts[file] = f.readlines rescue $DEBUG = save @scripts[file] = true return "\n" end line = lines[line-1] return "\n" unless line return line end def debug_funcname(id) if id == 0 "toplevel" else id.id2name end end def check_break_points(file, pos, binding, id) file = File.basename(file) if @break_points.include? [file, pos] index = @break_points.index([file, pos]) STDOUT.printf "Breakpoint %d, %s at %s:%s\n", index, debug_funcname(id), file, pos return true end return false end def excn_handle(file, line, id, binding) fs = @frames.size tb = caller(0)[-fs..-1] STDOUT.printf "%s\n", $! for i in tb STDOUT.printf "\tfrom %s\n", i end debug_command(file, line, id, binding) end def trace_func(event, file, line, id, binding) case event when 'line' if !@no_step or @frames.size == @no_step @stop_next -= 1 elsif @frames.size < @no_step @stop_next = 0 # break here before leaving... else # nothing to do. skipped. end if @stop_next == 0 if [file, line] == @last @stop_next = 1 else @no_step = nil debug_command(file, line, id, binding) @last = [file, line] end end if check_break_points(file, line, binding, id) debug_command(file, line, id, binding) end when 'call' @frames.unshift binding if check_break_points(file, id.id2name, binding, id) debug_command(file, line, id, binding) end when 'class' @frames.unshift binding when 'return', 'end' if @frames.size == @finish_pos @stop_next = 1 end @frames.shift when 'raise' excn_handle(file, line, id, binding) end @last_file = file end CONTEXT = new end set_trace_func proc{|event, file, line, id, binding,*rest| DEBUGGER__::CONTEXT.trace_func event, file, line, id, binding }