From: Florian Gross Date: 2004-08-30T00:05:30+09:00 Subject: Re: Typos (Was: performance comparison) --------------020209090904010300070809 Content-Type: text/plain; charset=us-ascii; format=flowed Content-Transfer-Encoding: 7bit Alexey Verkhovsky wrote: > To me, the biggest problem with using irb in this mode is the "set up > some context" part. > > Believe it or not, but it is quite easy to do with Eclipse/Java: one can > set a breakpoint, execute some test, wait till debugger stops on the > breakpoint, and then use Eclipse "Display" view to evaluate arbitrary > Java expressions in the current scope. > > So far, I don't know any easy way to do the same with Ruby, so I end up > inserting things like "puts .inspect" and rerunning the > test. Which is not as nice as an interactive shell. Does anyone have a > better way? I've done a quick implementation of breakpoints that will spawn an irb session at the point of the break point in your application yesterday. I've attached it. Regards, Florian Gross --------------020209090904010300070809 Content-Type: text/plain; name="breakpoint.rb" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="breakpoint.rb" require 'irb' require 'binding_of_caller' module IRB def IRB.start(ap_path = nil, main_context = nil) $0 = File::basename(ap_path, ".rb") if ap_path # suppress some warnings about redefined constants old_verbose, $VERBOSE = $VERBOSE, nil IRB.setup(ap_path) $VERBOSE = old_verbose if @CONF[:SCRIPT] irb = Irb.new(main_context, @CONF[:SCRIPT]) else irb = Irb.new(main_context) end @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC] @CONF[:MAIN_CONTEXT] = irb.context trap("SIGINT") do irb.signal_handle end catch(:IRB_EXIT) do irb.eval_input end end end # This will pop up an interactive ruby session at a # pre-defined break point in a Ruby application. In # this session you can examine the environment of # the break point. # # You can get a list of variables in the context using # local_variables via +local_variables+. You can then # examine their values by typing their names. # # You can have a look at the call stack via +caller+. # # breakpoints can also return a value. They will execute # a supplied block for getting a default return value. # A custom value can be returned from the session by doing # +throw(:debug_return, value)+. # # You can also give names to break points which will be # used in the message that is displayed upon execution # of them. # # Here's a sample of how breakpoints should be placed: # # class Person # def initialize(name, age) # @name, @age = name, age # breakpoint("Person#initialize") # end # # attr_reader :age # def name # breakpoint("Person#name") { @name } # end # end # # person = Person.new("Random Person", 23) # puts "Name: #{person.name}" # # And here is a sample debug session: # # Executing break point "Person#initialize" near file.rb:14 # irb(#):001:0> local_variables # => ["name", "age", "_", "__"] # irb(#):002:0> [name, age] # => ["Random Person", 23] # irb(#):003:0> [@name, @age] # => ["Random Person", 23] # irb(#):004:0> self # => # # irb(#):005:0> @age += 1; self # => # # irb(#):006:0> @age += 1; self # Executing break point "Person#name" near file.rb:14 # irb(#):001:0> throw(:debug_return, "Overriden name") # Name: Overriden name def breakpoint(id = nil, &block) Binding.of_caller do |context, extra_data| file, line = extra_data[1], extra_data[2] puts "Executing break point #{"#{id.inspect} " if id}near #{file}:#{line}" catch(:debug_return) do |value| IRB.start(nil, IRB::WorkSpace.new(context)) block.call if block end end end alias :break_point :breakpoint --------------020209090904010300070809 Content-Type: text/plain; name="binding_of_caller.rb" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="binding_of_caller.rb" begin require 'simplecc' rescue LoadError def Continuation.create(*args, &block) cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?} result ||= args return *[cc, *result] end end # This method returns the binding of the method that called your # method. It will raise an Exception when you're not inside a method. # # It's used like this: # def inc_counter # Binding.of_caller do |binding| # eval("counter += 1", binding) # end # end # counter = 0 # 2.times { inc_counter } # counter # => 2 # # You will have to put the whole rest of your method into the # block that you pass into this method. If you don't do this # an Exception will be raised. Because of the way that this is # implemented it has to be done this way. If you don't do it # like this it will raise an Exception. def Binding.of_caller(&block) old_critical = Thread.critical Thread.critical = true count = 0 cc, result, error, extra_data = Continuation.create(nil, nil) error.call if error tracer = lambda do |*args| type, context, extra_data = args[0], args[4], args if type == "return" count += 1 # First this method and then calling one will return -- # the trace event of the second event gets the context # of the method which called the method that called this # method. if count == 2 # It would be nice if we could restore the trace_func # that was set before we swapped in our own one, but # this is impossible without overloading set_trace_func # in current Ruby. set_trace_func(nil) cc.call(eval("binding", context), nil, extra_data) end elsif type != "line" set_trace_func(nil) error_msg = "Binding.of_caller used in non-method context or " + "trailing statements of method using it aren't in the block." cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil) end end unless result set_trace_func(tracer) return nil else Thread.critical = old_critical case block.arity when 1 then yield(result) else yield(result, extra_data) end end end --------------020209090904010300070809--