Permalink
Fetching contributors…
Cannot retrieve contributors at this time
273 lines (260 sloc) 8.12 KB
#!/usr/bin/ruby
# MacRuby Debugger.
#
# This file is covered by the Ruby license.
#
# Copyright (C) 2012, The MacRuby Team
# Copyright (C) 2010-2011, Apple Inc
require 'readline'
require 'rbconfig'
class Debugger
def initialize(args)
@terran_mode = args.delete('--terran')
internal = args.delete('--internal')
if args.empty?
$stderr.puts "Usage: #{__FILE__} <path-to-ruby-file> [args...]"
exit 1
end
path = internal ? './miniruby' : File.join(RbConfig::CONFIG['bindir'],
RbConfig::CONFIG['RUBY_INSTALL_NAME'])
@connector = ::MacRubyDebuggerConnector.alloc.initWithInterpreterPath(path,
arguments: args)
end
def run
$stdout.puts "Starting program."
@connector.startExecution
running = true
print_location = true
while true do
if @connector.location == nil and running
# Looks like the program terminated.
$stdout.puts "Program exited."
running = false
end
# Grab a command from the prompt.
command = prompt(print_location)
break unless command
command.strip!
next if command.empty?
print_location = true
case command
when 'c', 'continue'
if running
@connector.continueExecution
else
$stdout.puts "Program not running."
end
when 'n', 'next'
if running
@connector.stepExecution
else
$stdout.puts "Program not running."
end
when 'r', 'run'
if running
$stdout.print <<EOS.chomp
The program being debugged has been started already.
Start it from the beginning? (y or n)
EOS
if $stdin.gets.chomp == 'y'
@connector.stopExecution
@connector.startExecution
running = true
else
$stdout.puts "Program not restarted."
end
else
$stdout.puts "Starting program."
@connector.startExecution
running = true
end
when 'stop'
if running
@connector.stopExecution
else
$stdout.puts "Program not running."
end
when /^b(?:reak)?\s+(.+):(\d+)(?:\s+if\s+(.+))?/
if running
file = $1.strip
line = $2.to_i
cond = $3
bp = @connector.addBreakPointAtPath(file, line: line,
condition: nil)
if cond
cond.strip!
unless cond.empty?
@connector.setCondition(cond, forBreakPoint: bp)
end
end
$stdout.puts "Added breakpoint #{bp}."
else
$stdout.puts "Program not running."
end
when /^l(?:ist)?\s*(\d+)?/
if running
given_line = $1
file, line = current_file_line
if given_line
line = given_line.strip.to_i
end
lines = lines_in_file(file, line, 10)
if lines
index = line
lines.each do |l|
$stdout.puts "%d\t%s" % [index, l]
index += 1
end
else
$stdout.puts "No such line `#{line}' in file `#{file}'"
end
print_location = false
else
$stdout.puts "Program not running."
end
when /^enabled\s+(\d+)/
if running
bp = $1.to_i
@connector.enableBreakPoint(bp)
$stdout.puts "Enabled breakpoint #{bp}."
else
$stdout.puts "Program not running."
end
when /^disable\s+(\d+)/
if running
bp = $1.to_i
@connector.disableBreakPoint(bp)
$stdout.puts "Disabled breakpoint #{bp}."
else
$stdout.puts "Program not running."
end
when /^delete\s+(\d+)/
if running
bp = $1.to_i
@connector.deleteBreakPoint(bp)
$stdout.puts "Deleted breakpoint #{bp}."
else
$stdout.puts "Program not running."
end
when /^condition\s+(\d+)\s+(.+)/
if running
bp = $1.to_i
cond = $2.strip
@connector.setCondition(cond, forBreakPoint: bp)
else
$stdout.puts "Program not running."
end
when 'bt', 'backtrace'
if running
@connector.backtrace.each_with_index do |frame, n|
$stdout.puts "#%d\t%s" % [n, frame]
end
else
$stdout.puts "Program not running."
end
when /^fr(?:ame)?\s+(\d+)/
frid = $1.to_i
@connector.setFrame(frid)
when /^info\s+breakpoints/
if running
$stdout.puts "Program not running." and next unless running
bps = @connector.allBreakPoints
if bps.empty?
$stdout.puts "No breakpoint."
else
$stdout.puts "ID\t\tSTATE\t\tLOCATION"
bps.each do |bp|
$stdout.puts "%s\t\t%s\t\t%s" % [bp['id'],
bp['enabled'] == '1' ? "enabled" : "disabled",
bp['location']]
end
end
else
$stdout.puts "Program not running."
end
when /^info\s+threads/, /^thread\s+(\d+)/
$stdout.puts "Not yet implemented."
when /^p\s+(.+)/
if running
expr = $1.strip
$stdout.puts "=> #{@connector.evaluateExpression(expr)}"
else
$stdout.puts "Program not running."
end
when 'help'
help
when 'quit', 'exit'
@connector.stopExecution
exit
else
$stdout.puts "Unknown command `#{command}', type `help' for help."
end
end
end
private
def help
$stdout.puts <<EOS
break <file:line> Set up and enable a breakpoint at given file/line.
break <...> if <expr> Same as above but also set up a given condition.
delete <bp-id> Delete the given breakpoint.
disable <bp-id> Disable the given breakpoint.
enable <bp-id> Enable the given breakpoint.
condition <bp-id> <expr> Set up a condition for the given breakpoint.
info breakpoints List all breakpoints.
info threads List all threads.
next Go to the next expression.
continue Continue the program's execution.
run Start the program's execution.
stop Stop the program's execution.
list [line] Print the next 10 lines at given or current line.
backtrace Show current backtrace.
backtrace full Show a full backtrace (for every thread).
thread <thread-id> Switch to the given thread.
frame <frame-id> Switch to the given frame.
p <expression> Evaluate the given expression and print the result.
help Print this message.
quit Terminate the debugged program and exit.
EOS
end
PROMPTS = [
"Explorer reporting.",
"Ah, greetings command!",
"Transmit orders.",
"Receiving headquarters!",
"We have you on visual.",
"Let's roll!",
"Excellent!",
"Commencing!",
"Affirmative, sir."
]
def prompt(print_location)
if @terran_mode
$stdout.puts PROMPTS.sample
end
if print_location and @connector.location
path, line = current_file_line
lines = lines_in_file(path, line, 1)
if lines
$stdout.puts "%d\t%s" % [line, lines[0]]
end
end
Readline.readline("#{@connector.location}> ", true)
end
def current_file_line
loc = @connector.location
path, line = loc.scan(/^(.+):(\d+)$/)[0]
if path == nil or path.empty? or line == nil or line.empty?
raise "cannot determine current file/line from location #{loc}"
end
[path, line.to_i]
end
def lines_in_file(path, line, count)
@files ||= {}
data = @files[path]
unless data
@files[path] = data = File.read(path).split(/\n/)
end
data[line - 1, count]
end
end
Debugger.new(ARGV).run