--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DebugClients/Ruby/DebugClientBaseModule.rb Mon Dec 28 16:03:33 2009 +0000 @@ -0,0 +1,1176 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2005 - 2009 Detlev Offenbach <detlev@die-offenbachs.de> +# + +=begin edoc +File implementing a debug client base module. +=end + +if RUBY_VERSION < "1.9" + $KCODE = 'UTF8' + require 'jcode' +end + +require 'socket' + +require 'DebugQuit' +require 'DebugProtocol' +require 'DebugClientCapabilities' +require 'AsyncFile' +require 'Config' +require 'Completer' + +$DebugClientInstance = nil +$debugging = false + +module DebugClientBase +=begin edoc +Module implementing the client side of the debugger. + +It provides access to the Ruby interpeter from a debugger running in another +process. + +The protocol between the debugger and the client assumes that there will be +a single source of debugger commands and a single source of Ruby +statements. Commands and statement are always exactly one line and may be +interspersed. + +The protocol is as follows. First the client opens a connection to the +debugger and then sends a series of one line commands. A command is either +>Load<, >Step<, >StepInto<, ... or a Ruby statement. +See DebugProtocol.rb for a listing of valid protocol tokens. + +A Ruby statement consists of the statement to execute, followed (in a +separate line) by >OK?<. If the statement was incomplete then the response +is >Continue<. If there was an exception then the response is >Exception<. +Otherwise the response is >OK<. The reason for the >OK?< part is to +provide a sentinal (ie. the responding >OK<) after any possible output as a +result of executing the command. + +The client may send any other lines at any other time which should be +interpreted as program output. + +If the debugger closes the session there is no response from the client. +The client may close the session at any time as a result of the script +being debugged closing or crashing. + +<b>Note</b>: This module is meant to be mixed in by individual DebugClient classes. +Do not use it directly. +=end + @@clientCapabilities = HasDebugger | HasInterpreter | HasShell | HasCompleter + + attr_accessor :passive, :traceRuby + + def initializeDebugClient +=begin edoc +Method to initialize the module +=end + + # The context to run the debugged program in. + @debugBinding = eval("def e4dc_DebugBinding; binding; end; e4dc_DebugBinding", + TOPLEVEL_BINDING, + __FILE__, + __LINE__ - 3) + + # The context to run the shell commands in. + @shellBinding = eval("def e4dc_ShellBinding; binding; end; e4dc_ShellBinding", + TOPLEVEL_BINDING, + __FILE__, + __LINE__ - 3) + + # stack frames + @frames = [] + @framenr = 0 + + # The list of complete lines to execute. + @buffer = '' + @lineno = 0 + + # The list of regexp objects to filter variables against + @globalsFilterObjects = [] + @localsFilterObjects = [] + + @pendingResponse = ResponseOK + @mainProcStr = nil # used for the passive mode + @passive = false # used to indicate the passive mode + @running = nil + + @readstream = nil + @writestream = nil + @errorstream = nil + + @variant = 'You should not see this' + + @completer = Completer.new(@shellBinding) + end + + def handleException +=begin edoc +Private method called in the case of an exception + +It ensures that the debug server is informed of the raised exception. +=end + @pendingResponse = ResponseException + end + + def sessionClose +=begin edoc +Privat method to close the session with the debugger and terminate. +=end + set_trace_func nil + if $debugging + $debugging = false + @running = nil + DEBUGGER__.context(DEBUGGER__.last_thread).step_quit() + end + + # clean up asyncio + disconnect() + + # make sure we close down our end of the socket + # might be overkill as normally stdin, stdout and stderr + # SHOULD be closed on exit, but it does not hurt to do it here + @readstream.close() + @writestream.close() + @errorstream.close() + + # Ok, go away. + exit() + end + + def unhandled_exception(exc) +=begin edoc +Private method to report an unhandled exception. + +@param exc the exception object +=end + if SystemExit === exc + if $debugging + $debugging = false + else + progTerminated(exc.status) + end + return + end + + # split the exception message + msgParts = exc.message.split(":", 3) + filename = File.expand_path(msgParts[0]) + linenr = msgParts[1].to_i + + if ScriptError === exc + msgParts = msgParts[2].split(":", 3) + filename = msgParts[0].sub(/in `require'/, "") + linenr = msgParts[1].to_i + exclist = [""] + exclist << [filename, linenr, 0] + write("%s%s\n" % [ResponseSyntax, exclist.inspect]) + return + end + + exclist = ["unhandled %s" % exc.class, msgParts[2].sub(/in /, "")] + exclist << [filename, linenr] + + # process the traceback + frList = exc.backtrace + frList.each do |frame| + next if frame =~ /DebugClientBaseModule/ + break if frame =~ /\(eval\)/ + frameParts = frame.split(":", 3) + filename = File.expand_path(frameParts[0]) + linenr = frameParts[1].to_i + next if [filename, linenr] == exclist[-1] + exclist << [filename, linenr] + end + + write("%s%s\n" % [ResponseException, exclist.inspect]) + end + + def handleLine(line) +=begin edoc +Private method to handle the receipt of a complete line. + +It first looks for a valid protocol token at the start of the line. Thereafter +it trys to execute the lines accumulated so far. + +@param line the received line +=end + + # Remove any newline + if line[-1] == "\n" + line = line[1...-1] + end + +## STDOUT << line << "\n" ## debug + + eoc = line.index("<") + + if eoc and eoc >= 0 and line[0,1] == ">" + # Get the command part and any argument + cmd = line[0..eoc] + arg = line[eoc+1...-1] + + case cmd + when RequestOK + write(@pendingResponse + "\n") + @pendingResponse = ResponseOK + return + + when RequestEnv + # convert a Python stringified hash to a Ruby stringified hash + arg.gsub!(/: u/, "=>") + arg.gsub!(/u'/, "'") + eval(arg).each do |key, value| + if key[-1..-1] == "+" + key = key[0..-2] + if ENV[key] + ENV[key] += value + else + ENV[key] = value + end + else + ENV[key] = value + end + end + return + + when RequestVariables + frmnr, scope, filter = eval("[%s]" % arg.gsub(/u'/, "'").gsub(/u"/,'"')) + dumpVariables(frmnr.to_i, scope.to_i, filter) + return + + when RequestVariable + var, frmnr, scope, filter = \ + eval("[%s]" % arg.gsub(/u'/, "'").gsub(/u"/,'"')) + dumpVariable(var, frmnr.to_i, scope.to_i, filter) + return + + when RequestStep + DEBUGGER__.context(DEBUGGER__.last_thread).stop_next() + @eventExit = true + return + + when RequestStepOver + DEBUGGER__.context(DEBUGGER__.last_thread).step_over() + @eventExit = true + return + + when RequestStepOut + DEBUGGER__.context(DEBUGGER__.last_thread).step_out() + @eventExit = true + return + + when RequestStepQuit + set_trace_func nil + wasDebugging = $debugging + $debugging = false + @running = nil + if @passive + progTerminated(42) + else + DEBUGGER__.context(DEBUGGER__.last_thread).step_quit() if wasDebugging + end + return + + when RequestContinue + special = arg.to_i + if special == 0 + DEBUGGER__.context(DEBUGGER__.last_thread).step_continue() + else + # special == 1 means a continue while doing a step over + # this occurs when an expception is raised doing a step over + DEBUGGER__.context(DEBUGGER__.last_thread).step_over() + end + @eventExit = true + return + + when RequestSetFilter + scope, filterString = eval("[%s]" % arg) + generateFilterObjects(scope.to_i, filterString) + return + + when RequestLoad + $debugging = true + ARGV.clear() + wd, fn, args, traceRuby = arg.split("|", -4) + @traceRuby = traceRuby.to_i == 1 ? true : false + ARGV.concat(eval(args.gsub(/u'/, "'").gsub(/u"/,'"'))) + $:.insert(0, File.dirname(fn)) + if wd == '' + Dir.chdir($:[0]) + else + Dir.chdir(wd) + end + @running = fn + command = "$0 = '%s'; require '%s'" % [fn, fn] + set_trace_func proc { |event, file, line, id, binding_, klass, *rest| + DEBUGGER__.context.trace_func(event, file, line, id, binding_, klass) + } + begin + eval(command, @debugBinding) + rescue DebugQuit + # just ignore it + rescue Exception => exc + unhandled_exception(exc) + ensure + set_trace_func(nil) + @running = nil + end + return + + when RequestRun + $debugging = false + ARGV.clear() + wd, fn, args = arg.split("|", -3) + ARGV.concat(eval(args.gsub(/u'/, "'").gsub(/u"/,'"'))) + $:.insert(0, File.dirname(fn)) + if wd == '' + Dir.chdir($:[0]) + else + Dir.chdir(wd) + end + command = "$0 = '%s'; require '%s'" % [fn, fn] + @frames = [] + set_trace_func proc { |event, file, line, id, binding_, klass, *rest| + trace_func(event, file, line, id, binding_, klass) + } + begin + eval(command, @debugBinding) + rescue SystemExit + # ignore it + rescue Exception => exc + unhandled_exception(exc) + ensure + set_trace_func(nil) + end + return + + when RequestShutdown + sessionClose() + return + + when RequestBreak + fn, line, temporary, set, cond = arg.split("@@") + line = line.to_i + set = set.to_i + temporary = temporary.to_i == 1 ? true : false + + if set == 1 + if cond == 'None' + cond = nil + end + DEBUGGER__.context(DEBUGGER__.last_thread)\ + .add_break_point(fn, line, temporary, cond) + else + DEBUGGER__.context(DEBUGGER__.last_thread)\ + .delete_break_point(fn, line) + end + return + + when RequestBreakEnable + fn, line, enable = arg.split(',') + line = line.to_i + enable = enable.to_i == 1 ? true : false + DEBUGGER__.context(DEBUGGER__.last_thread)\ + .enable_break_point(fn, line, enable) + return + + when RequestBreakIgnore + fn, line, count = arg.split(',') + line = line.to_i + count = count.to_i + DEBUGGER__.context(DEBUGGER__.last_thread)\ + .ignore_break_point(fn, line, count) + return + + when RequestWatch + cond, temporary, set = arg.split('@@') + set = set.to_i + temporary = temporary.to_i == 1 ? true : false + + if set == 1 + DEBUGGER__.context(DEBUGGER__.last_thread)\ + .add_watch_point(cond, temporary) + else + DEBUGGER__.context(DEBUGGER__.last_thread).delete_watch_point(cond) + end + return + + when RequestWatchEnable + cond, enable = arg.split(',') + enable = enable.to_i == 1 ? true : false + DEBUGGER__.context(DEBUGGER__.last_thread)\ + .enable_watch_point(cond, enable) + return + + when RequestWatchIgnore + cond, count = arg.split(',') + count = count.to_i + DEBUGGER__.context(DEBUGGER__.last_thread).ignore_watch_point(cond, count) + return + + when RequestEval, RequestExec + if not @running + binding_ = @shellBinding + else + binding_ = DEBUGGER__.context(DEBUGGER__.last_thread).current_binding + end + write("\n") + begin + value = eval(arg, binding_) + rescue Exception => exc + list = [] + list << "%s: %s\n" % \ + [exc.class, exc.to_s.sub(/stdin:\d+:(in `.*':?)?/, '')] + $@.each do |l| + break if l =~ /e4dc_ShellBinding/ or l =~ /e4dc_DebugBinding/ or \ + l =~ /:in `require'$/ + list << "%s\n" % l.sub(/\(eval\)/, "(e3dc)") + end + list.each do |entry| + write(entry) + end + else + write("=> #{value.inspect}\n") + write("#{ResponseOK}\n") + end + return + + when RequestBanner + version = "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" + write("%s('%s','%s','%s')\n" % \ + [ResponseBanner, version, Socket.gethostname(), @variant]) + return + + when RequestCapabilities + write("%s%d, 'Ruby'\n" % \ + [ResponseCapabilities, @@clientCapabilities]) + return + + when RequestCompletion + completionList(arg) + return + + else + puts "Got unsupported command %s.\n" % cmd + return + end + end + + if @buffer + @buffer << "\n" << line + @lineno += 1 + else + @buffer = line.dup + end + + # check for completeness + if not canEval? + @pendingResponse = ResponseContinue + else + command = @buffer.dup + @buffer = "" + begin + res = "=> " + if not @running + res << eval(command, @shellBinding, "stdin", @lineno).inspect << "\n" + else + res << eval(command, + DEBUGGER__.context(DEBUGGER__.last_thread).get_binding(@framenr), + "stdin", @lineno).inspect << "\n" + end + write(res) + rescue SystemExit => exc + progTerminated(exc.status) + rescue ScriptError, StandardError => exc + list = [] + list << "%s: %s\n" % \ + [exc.class, exc.to_s.sub(/stdin:\d+:(in `.*':?)?/, '')] + $@.each do |l| + break if l =~ /e4dc_ShellBinding/ or l =~ /e4dc_DebugBinding/ or \ + l =~ /:in `require'$/ + list << "%s\n" % l.sub(/\(eval\)/, "(e3dc)") + end + list.each do |entry| + write(entry) + end + handleException() + end + end + end + + def canEval? +=begin edoc +Private method to check if the buffer's contents can be evaluated. + +@return flag indicating if an eval might succeed (boolean) +=end + indent = 0 + if @buffer =~ /,\s*$/ + return false + end + + @buffer.split($/).each do |l| + if l =~ /^\s*(class|module|def|if|unless|case|while|until|for|begin)\b[^_]/ + indent += 1 + end + if l =~ /\s*do\s*(\|.*\|)?\s*$/ + indent += 1 + end + if l =~ /^\s*end\s*$|^\s*end\b[^_]/ + indent -= 1 + end + if l =~ /\{\s*(\|.*\|)?\s*$/ + indent += 1 + end + if l =~ /^\s*\}/ + indent -= 1 + end + end + + if indent > 0 + return false + end + return true + end + + def trace_func(event, file, line, id, binding_, klass) +=begin edoc +Method executed by the tracing facility. + +It is used to save the execution context of an exception. + +@param event the tracing event (String) +@param file the name of the file being traced (String) +@param line the line number being traced (int) +@param id object id +@param binding_ a binding object +@param klass name of a class +=end + case event + when 'line' + if @frames[0] + @frames[0] = binding_ + else + @frames.unshift binding_ + end + + when 'call' + @frames.unshift binding_ + + when 'class' + @frames.unshift binding_ + + when 'return', 'end' + @frames.shift + + when 'end' + @frames.shift + + when 'raise' + set_trace_func nil + end + end + + def write(s) +=begin edoc +Private method to write data to the output stream. + +@param s data to be written (string) +=end + @writestream.write(s) + @writestream.flush() + end + + def interact +=begin edoc +Private method to Interact with the debugger. +=end + setDescriptors(@readstream, @writestream) + $DebugClientInstance = self + + if not @passive + # At this point simulate an event loop. + eventLoop() + end + end + + def eventLoop +=begin edoc +Private method implementing our event loop. +=end + @eventExit = nil + + while @eventExit == nil + wrdy = [] + + if AsyncPendingWrite(@writestream) > 0 + wrdy << @writestream.getSock() + end + + if AsyncPendingWrite(@errorstream) > 0 + wrdy << @errorstream.getSock() + end + + rrdy, wrdy, xrdy = select([@readstream.getSock()], wrdy, []) + + if rrdy.include?(@readstream.getSock()) + readReady(@readstream.fileno()) + end + + if wrdy.include?(@writestream.getSock()) + writeReady(@writestream.fileno()) + end + + if wrdy.include?(@errorstream.getSock()) + writeReady(@errorstream.fileno()) + end + end + + @eventExit = nil + end + + def eventPoll +=begin edoc +Private method to poll for events like 'set break point'. +=end + + # the choice of a ~0.5 second poll interval is arbitrary. + lasteventpolltime = @lasteventpolltime ? @lasteventpolltime : Time.now + now = Time.now + if now - lasteventpolltime < 0.5 + @lasteventpolltime = lasteventpolltime + return + else + @lasteventpolltime = now + end + + wrdy = [] + + if AsyncPendingWrite(@writestream) > 0 + wrdy << @writestream.getSock() + end + + if AsyncPendingWrite(@errorstream) > 0 + wrdy << @errorstream.getSock() + end + + rrdy, wrdy, xrdy = select([@readstream.getSock()], wrdy, [], 0) + + if rrdy == nil + return + end + + if rrdy.include?(@readstream.getSock()) + readReady(@readstream.fileno()) + end + + if wrdy.include?(@writestream.getSock()) + writeReady(@writestream.fileno()) + end + + if wrdy.include?(@errorstream.getSock()) + writeReady(@errorstream.fileno()) + end + end + + def connectDebugger(port, remoteAddress=nil, redirect=true) +=begin edoc +Public method to establish a session with the debugger. + +It opens a network connection to the debugger, connects it to stdin, +stdout and stderr and saves these file objects in case the application +being debugged redirects them itself. + +@param port the port number to connect to (int) +@param remoteAddress the network address of the debug server host (string) +@param redirect flag indicating redirection of stdin, stdout and stderr (boolean) +=end + if remoteAddress == nil + sock = TCPSocket.new(DebugAddress, port) + else + sock = TCPSocket.new(remoteAddress, port) + end + + @readstream = AsyncFile.new(sock, "r", "stdin") + @writestream = AsyncFile.new(sock, "w", "stdout") + @errorstream = AsyncFile.new(sock, "w", "stderr") + + if redirect + $stdin = @readstream + $stdout = @writestream + $stderr = @errorstream + end + end + + def progTerminated(status) +=begin edoc +Private method to tell the debugger that the program has terminated. + +@param status the return status +=end + if status == nil + status = 0 + else + begin + Integer(status) + rescue + status = 1 + end + end + + set_trace_func(nil) + @running = nil + write("%s%d\n" % [ResponseExit, status]) + end + + def dumpVariables(frmnr, scope, filter) +=begin edoc +Private method to return the variables of a frame to the debug server. + +@param frmnr distance of frame reported on. 0 is the current frame (int) +@param scope 1 to report global variables, 0 for local variables (int) +@param filter the indices of variable types to be filtered (list of int) +=end + if $debugging + if scope == 0 + @framenr = frmnr + end + binding_, file, line, id = DEBUGGER__.context(DEBUGGER__.last_thread)\ + .get_frame(frmnr) + else + binding_ = @frames[frmnr] + end + varlist = [scope] + if scope >= 1 + # dump global variables + vlist = formatVariablesList(global_variables, binding_, scope, filter) + elsif scope == 0 + # dump local variables + vlist = formatVariablesList(eval("local_variables", binding_), + binding_, scope, filter) + end + varlist.concat(vlist) + write("%s%s\n" % [ResponseVariables, varlist.inspect]) + end + + def dumpVariable(var, frmnr, scope, filter) +=begin edoc +Private method to return the variables of a frame to the debug server. + +@param var list encoded name of the requested variable (list of strings) +@param frmnr distance of frame reported on. 0 is the current frame (int) +@param scope 1 to report global variables, 0 for local variables (int) +@param filter the indices of variable types to be filtered (list of int) +=end + if $debugging + binding_, file, line, id = DEBUGGER__.context(DEBUGGER__.last_thread)\ + .get_frame(frmnr) + else + binding_ = @frames[frmnr] + end + varlist = [scope, var] + i = 0 + obj = nil + keylist = nil + formatSequences = false + access = "" + isDict = false + + while i < var.length + if ["[]", "{}"].include?(var[i][-2..-1]) + if i+1 == var.length + keylist = [var[i][0..-3]] + formatSequences = true + if access.length == 0 + access = "#{var[i][0..-3]}" + else + access << "[#{var[i][0..-3]}]" + end + if var[i][-2..-1] == "{}" + isDict = true + end + break + else + if access.length == 0 + access = "#{var[i][0..-3]}" + else + access << "[#{var[i][0..-3]}]" + end + end + else + if access.length != 0 + access << "[#{var[i]}]" + obj = eval(access, binding_) + binding_ = obj.instance_eval{binding()} + access = "" + else + obj = eval(var[i], binding_) + binding_ = obj.instance_eval{binding()} + end + end + i += 1 + end + if formatSequences + bind = binding_ + else + bind = obj.instance_eval{binding()} + end + if not keylist + keylist = obj.instance_variables + access = nil + else + if access.length != 0 + obj = eval(access, bind) + end + if isDict + keylist = obj.keys() + else + keylist = Array.new(obj.length){|i| i} + end + end + vlist = formatVariablesList(keylist, bind, scope, filter, + excludeSelf=true, access=access) + varlist.concat(vlist) + write("%s%s\n" % [ResponseVariable, varlist.inspect]) + end + + def formatVariablesList(keylist, binding_, scope, filter = [], + excludeSelf = false, access = nil) +=begin edoc +Private method to produce a formated variables list. + +The binding passed in to it is scanned. Variables are +only added to the list, if their type is not contained +in the filter list and their name doesn't match any of the filter expressions. +The formated variables list (a list of lists of 3 values) is returned. + +@param keylist keys of the dictionary +@param binding_ the binding to be scanned +@param scope 1 to filter using the globals filter, 0 using the locals filter (int). + Variables are only added to the list, if their name do not match any of the + filter expressions. +@param filter the indices of variable types to be filtered. Variables are + only added to the list, if their type is not contained in the filter + list. +@param excludeSelf flag indicating if the self object should be excluded from + the listing (boolean) +@param access String specifying the access path to (String) +@return A list consisting of a list of formatted variables. Each variable + entry is a list of three elements, the variable name, its type and + value. +=end + varlist = [] + if scope >= 1 + patternFilterObjects = @globalsFilterObjects + else + patternFilterObjects = @localsFilterObjects + begin + obj = eval("self", binding_) + rescue StandardError, ScriptError + obj = nil + end + if not excludeSelf and obj.class != Object + keylist << "self" + end + end + + keylist.each do |key| + # filter based on the filter pattern + matched = false + patternFilterObjects.each do |pat| + if pat.match(key) + matched = true + break + end + end + next if matched + + begin + if access + if key.to_s == key + key = "'%s'" % key + else + key = key.to_s + end + k = "#{access}[%s]" % key + obj = eval(k, binding_) + else + obj = eval(key, binding_) + end + rescue NameError + next + end + + if obj or obj.class == NilClass or obj.class == FalseClass + otype = obj.class.to_s + if obj.inspect.nil? + otype = "" + oval = "" + else + oval = obj.inspect.gsub(/=>/,":") + end + else + otype = "" + oval = obj.inspect + end + + next if inFilter?(filter, otype, oval) + + if oval.index("#<") == 0 + addr = extractAddress(oval) + oval = "<#{otype} object at #{addr}>" + end + if obj + if obj.class == Array or obj.class == Hash + oval = "%d" % obj.length() + end + end + varlist << [key, otype, oval] + end + return varlist + end + + def extractAddress(var) +=begin edoc +Private method to extract the address part of an object description. + +@param var object description (String) +@return the address contained in the object description (String) +=end + m = var.match(/^#<.*?:([^:]*?) /) + if m + return m[1] + else + return "" + end + end + + def extractTypeAndAddress(var) +=begin edoc +Private method to extract the address and type parts of an object description. + +@param var object description (String) +@return list containing the type and address contained in the object + description (Array of two String) +=end + m = var.match(/^#<(.*?):(.*?) /) + if m + return [m[1], m[2]] + else + return ["", ""] + end + end + + def inFilter?(filter, otype, oval) +=begin edoc +Private method to check, if a variable is to be filtered based on its type. + +@param filter the indices of variable types to be filtered (Array of int. +@param otype type of the variable to be checked (String) +@param oval variable value to be checked (String) +@return flag indicating, whether the variable should be filtered (boolean) +=end + cindex = ConfigVarTypeStrings.index(otype) + if cindex == nil + if oval.index("#<") == 0 + if filter.include?(ConfigVarTypeStrings.index("instance")) + return true + else + return false + end + elsif ['FalseClass', 'TrueClass'].include?(otype) + if filter.include?(ConfigVarTypeStrings.index("bool")) + return true + else + return false + end + else + if filter.include?(ConfigVarTypeStrings.index("other")) + return true + else + return false + end + end + end + if filter.include?(cindex) + return true + end + return false + end + + def generateFilterObjects(scope, filterString) +=begin edoc +Private method to convert a filter string to a list of filter objects. + +@param scope 1 to generate filter for global variables, 0 for local variables (int) +@param filterString string of filter patterns separated by ';' +=end + patternFilterObjects = [] + for pattern in filterString.split(';') + patternFilterObjects << Regexp.compile('^%s$' % pattern) + end + if scope == 1 + @globalsFilterObjects = patternFilterObjects[0..-1] + else + @localsFilterObjects = patternFilterObjects[0..-1] + end + end + + def completionList(text) +=begin edoc +Method used to handle the command completion request + +@param text the text to be completed (string) +=end + completions = @completer.complete(text).compact.sort.uniq + write("%s%s||%s\n" % [ResponseCompletion, completions.inspect, text]) + end + + def startProgInDebugger(progargs, wd = '', host = nil, + port = nil, exceptions = true, traceRuby = false, redirect=true) +=begin edoc +Method used to start the remote debugger. + +@param progargs commandline for the program to be debugged + (list of strings) +@param wd working directory for the program execution (string) +@param host hostname of the debug server (string) +@param port portnumber of the debug server (int) +@param exceptions flag to enable exception reporting of the IDE (boolean) +@param traceRuby flag to enable tracing into the Ruby library +@param redirect flag indicating redirection of stdin, stdout and stderr (boolean) +=end + $debugging = true + + if host == nil + host = ENV.fetch('ERICHOST', 'localhost') + end + if port == nil + port = ENV.fetch('ERICPORT', 42424) + end + + connectDebugger(port, host, redirect) #TCPSocket.gethostbyname(host)[3]) + DEBUGGER__.attach(self) + + @traceRuby = traceRuby + + fn = progargs.shift + fn = File.expand_path(fn) + + ARGV.clear() + ARGV.concat(progargs) + $:.insert(0, File.dirname(fn)) + if wd == '' + Dir.chdir($:[0]) + else + Dir.chdir(wd) + end + @running = fn + + @passive = true + write("%s%s|%d\n" % [PassiveStartup, @running, exceptions ? 1 : 0]) + interact() + + command = "$0 = '%s'; require '%s'" % [fn, fn] + set_trace_func proc { |event, file, line, id, binding_, klass, *rest| + DEBUGGER__.context.trace_func(event, file, line, id, binding_, klass) + } + begin + eval(command, @debugBinding) + rescue DebugQuit + # just ignore it + rescue Exception => exc + unhandled_exception(exc) + ensure + set_trace_func(nil) + @running = nil + end + # just a short delay because client might shut down too quickly otherwise + sleep(1) + end + + def main +=begin edoc +Public method implementing the main method. +=end + if ARGV.include?('--') + args = ARGV[0..-1] + host = nil + port = nil + wd = '' + traceRuby = false + exceptions = true + redirect = true + while args[0] + if args[0] == '-h' + host = args[1] + args.shift + args.shift + elsif args[0] == '-p' + port = args[1] + args.shift + args.shift + elsif args[0] == '-w' + wd = args[1] + args.shift + args.shift + elsif args[0] == '-t' + traceRuby = true + args.shift + elsif args[0] == '-e' + exceptions = false + args.shift + elsif args[0] == '-n' + redirect = false + args.shift + elsif args[0] == '--' + args.shift + break + else # unknown option + args.shift + end + end + if args.length == 0 + STDOUT << "No program given. Aborting!" + else + startProgInDebugger(args, wd, host, port, + exceptions = exceptions, traceRuby = traceRuby, + redirect = redirect) + end + else + if ARGV[0] == '--no-encoding' + # just ignore it, it's here to be compatible with python debugger + ARGV.shift + end + + begin + port = ARGV[0].to_i + rescue + port = -1 + end + + begin + redirect = ARGV[1].to_i + redirect = redirect == 1 ? true : false + rescue + redirect = true + end + + begin + remoteAddress = ARGV[2] + rescue + remoteAddress = nil + end + + ARGV.clear() + $:.insert(0,"") + if port >= 0 + connectDebugger(port, remoteAddress, redirect) + DEBUGGER__.attach(self) + interact() + end + end + end +end