DebugClients/Ruby/DebugClientBaseModule.rb

changeset 0
de9c2efb9d02
child 13
1af94a91f439
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2005 - 2009 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 =begin edoc
7 File implementing a debug client base module.
8 =end
9
10 if RUBY_VERSION < "1.9"
11 $KCODE = 'UTF8'
12 require 'jcode'
13 end
14
15 require 'socket'
16
17 require 'DebugQuit'
18 require 'DebugProtocol'
19 require 'DebugClientCapabilities'
20 require 'AsyncFile'
21 require 'Config'
22 require 'Completer'
23
24 $DebugClientInstance = nil
25 $debugging = false
26
27 module DebugClientBase
28 =begin edoc
29 Module implementing the client side of the debugger.
30
31 It provides access to the Ruby interpeter from a debugger running in another
32 process.
33
34 The protocol between the debugger and the client assumes that there will be
35 a single source of debugger commands and a single source of Ruby
36 statements. Commands and statement are always exactly one line and may be
37 interspersed.
38
39 The protocol is as follows. First the client opens a connection to the
40 debugger and then sends a series of one line commands. A command is either
41 &gt;Load&lt;, &gt;Step&lt;, &gt;StepInto&lt;, ... or a Ruby statement.
42 See DebugProtocol.rb for a listing of valid protocol tokens.
43
44 A Ruby statement consists of the statement to execute, followed (in a
45 separate line) by &gt;OK?&lt;. If the statement was incomplete then the response
46 is &gt;Continue&lt;. If there was an exception then the response is &gt;Exception&lt;.
47 Otherwise the response is &gt;OK&lt;. The reason for the &gt;OK?&lt; part is to
48 provide a sentinal (ie. the responding &gt;OK&lt;) after any possible output as a
49 result of executing the command.
50
51 The client may send any other lines at any other time which should be
52 interpreted as program output.
53
54 If the debugger closes the session there is no response from the client.
55 The client may close the session at any time as a result of the script
56 being debugged closing or crashing.
57
58 <b>Note</b>: This module is meant to be mixed in by individual DebugClient classes.
59 Do not use it directly.
60 =end
61 @@clientCapabilities = HasDebugger | HasInterpreter | HasShell | HasCompleter
62
63 attr_accessor :passive, :traceRuby
64
65 def initializeDebugClient
66 =begin edoc
67 Method to initialize the module
68 =end
69
70 # The context to run the debugged program in.
71 @debugBinding = eval("def e4dc_DebugBinding; binding; end; e4dc_DebugBinding",
72 TOPLEVEL_BINDING,
73 __FILE__,
74 __LINE__ - 3)
75
76 # The context to run the shell commands in.
77 @shellBinding = eval("def e4dc_ShellBinding; binding; end; e4dc_ShellBinding",
78 TOPLEVEL_BINDING,
79 __FILE__,
80 __LINE__ - 3)
81
82 # stack frames
83 @frames = []
84 @framenr = 0
85
86 # The list of complete lines to execute.
87 @buffer = ''
88 @lineno = 0
89
90 # The list of regexp objects to filter variables against
91 @globalsFilterObjects = []
92 @localsFilterObjects = []
93
94 @pendingResponse = ResponseOK
95 @mainProcStr = nil # used for the passive mode
96 @passive = false # used to indicate the passive mode
97 @running = nil
98
99 @readstream = nil
100 @writestream = nil
101 @errorstream = nil
102
103 @variant = 'You should not see this'
104
105 @completer = Completer.new(@shellBinding)
106 end
107
108 def handleException
109 =begin edoc
110 Private method called in the case of an exception
111
112 It ensures that the debug server is informed of the raised exception.
113 =end
114 @pendingResponse = ResponseException
115 end
116
117 def sessionClose
118 =begin edoc
119 Privat method to close the session with the debugger and terminate.
120 =end
121 set_trace_func nil
122 if $debugging
123 $debugging = false
124 @running = nil
125 DEBUGGER__.context(DEBUGGER__.last_thread).step_quit()
126 end
127
128 # clean up asyncio
129 disconnect()
130
131 # make sure we close down our end of the socket
132 # might be overkill as normally stdin, stdout and stderr
133 # SHOULD be closed on exit, but it does not hurt to do it here
134 @readstream.close()
135 @writestream.close()
136 @errorstream.close()
137
138 # Ok, go away.
139 exit()
140 end
141
142 def unhandled_exception(exc)
143 =begin edoc
144 Private method to report an unhandled exception.
145
146 @param exc the exception object
147 =end
148 if SystemExit === exc
149 if $debugging
150 $debugging = false
151 else
152 progTerminated(exc.status)
153 end
154 return
155 end
156
157 # split the exception message
158 msgParts = exc.message.split(":", 3)
159 filename = File.expand_path(msgParts[0])
160 linenr = msgParts[1].to_i
161
162 if ScriptError === exc
163 msgParts = msgParts[2].split(":", 3)
164 filename = msgParts[0].sub(/in `require'/, "")
165 linenr = msgParts[1].to_i
166 exclist = [""]
167 exclist << [filename, linenr, 0]
168 write("%s%s\n" % [ResponseSyntax, exclist.inspect])
169 return
170 end
171
172 exclist = ["unhandled %s" % exc.class, msgParts[2].sub(/in /, "")]
173 exclist << [filename, linenr]
174
175 # process the traceback
176 frList = exc.backtrace
177 frList.each do |frame|
178 next if frame =~ /DebugClientBaseModule/
179 break if frame =~ /\(eval\)/
180 frameParts = frame.split(":", 3)
181 filename = File.expand_path(frameParts[0])
182 linenr = frameParts[1].to_i
183 next if [filename, linenr] == exclist[-1]
184 exclist << [filename, linenr]
185 end
186
187 write("%s%s\n" % [ResponseException, exclist.inspect])
188 end
189
190 def handleLine(line)
191 =begin edoc
192 Private method to handle the receipt of a complete line.
193
194 It first looks for a valid protocol token at the start of the line. Thereafter
195 it trys to execute the lines accumulated so far.
196
197 @param line the received line
198 =end
199
200 # Remove any newline
201 if line[-1] == "\n"
202 line = line[1...-1]
203 end
204
205 ## STDOUT << line << "\n" ## debug
206
207 eoc = line.index("<")
208
209 if eoc and eoc >= 0 and line[0,1] == ">"
210 # Get the command part and any argument
211 cmd = line[0..eoc]
212 arg = line[eoc+1...-1]
213
214 case cmd
215 when RequestOK
216 write(@pendingResponse + "\n")
217 @pendingResponse = ResponseOK
218 return
219
220 when RequestEnv
221 # convert a Python stringified hash to a Ruby stringified hash
222 arg.gsub!(/: u/, "=>")
223 arg.gsub!(/u'/, "'")
224 eval(arg).each do |key, value|
225 if key[-1..-1] == "+"
226 key = key[0..-2]
227 if ENV[key]
228 ENV[key] += value
229 else
230 ENV[key] = value
231 end
232 else
233 ENV[key] = value
234 end
235 end
236 return
237
238 when RequestVariables
239 frmnr, scope, filter = eval("[%s]" % arg.gsub(/u'/, "'").gsub(/u"/,'"'))
240 dumpVariables(frmnr.to_i, scope.to_i, filter)
241 return
242
243 when RequestVariable
244 var, frmnr, scope, filter = \
245 eval("[%s]" % arg.gsub(/u'/, "'").gsub(/u"/,'"'))
246 dumpVariable(var, frmnr.to_i, scope.to_i, filter)
247 return
248
249 when RequestStep
250 DEBUGGER__.context(DEBUGGER__.last_thread).stop_next()
251 @eventExit = true
252 return
253
254 when RequestStepOver
255 DEBUGGER__.context(DEBUGGER__.last_thread).step_over()
256 @eventExit = true
257 return
258
259 when RequestStepOut
260 DEBUGGER__.context(DEBUGGER__.last_thread).step_out()
261 @eventExit = true
262 return
263
264 when RequestStepQuit
265 set_trace_func nil
266 wasDebugging = $debugging
267 $debugging = false
268 @running = nil
269 if @passive
270 progTerminated(42)
271 else
272 DEBUGGER__.context(DEBUGGER__.last_thread).step_quit() if wasDebugging
273 end
274 return
275
276 when RequestContinue
277 special = arg.to_i
278 if special == 0
279 DEBUGGER__.context(DEBUGGER__.last_thread).step_continue()
280 else
281 # special == 1 means a continue while doing a step over
282 # this occurs when an expception is raised doing a step over
283 DEBUGGER__.context(DEBUGGER__.last_thread).step_over()
284 end
285 @eventExit = true
286 return
287
288 when RequestSetFilter
289 scope, filterString = eval("[%s]" % arg)
290 generateFilterObjects(scope.to_i, filterString)
291 return
292
293 when RequestLoad
294 $debugging = true
295 ARGV.clear()
296 wd, fn, args, traceRuby = arg.split("|", -4)
297 @traceRuby = traceRuby.to_i == 1 ? true : false
298 ARGV.concat(eval(args.gsub(/u'/, "'").gsub(/u"/,'"')))
299 $:.insert(0, File.dirname(fn))
300 if wd == ''
301 Dir.chdir($:[0])
302 else
303 Dir.chdir(wd)
304 end
305 @running = fn
306 command = "$0 = '%s'; require '%s'" % [fn, fn]
307 set_trace_func proc { |event, file, line, id, binding_, klass, *rest|
308 DEBUGGER__.context.trace_func(event, file, line, id, binding_, klass)
309 }
310 begin
311 eval(command, @debugBinding)
312 rescue DebugQuit
313 # just ignore it
314 rescue Exception => exc
315 unhandled_exception(exc)
316 ensure
317 set_trace_func(nil)
318 @running = nil
319 end
320 return
321
322 when RequestRun
323 $debugging = false
324 ARGV.clear()
325 wd, fn, args = arg.split("|", -3)
326 ARGV.concat(eval(args.gsub(/u'/, "'").gsub(/u"/,'"')))
327 $:.insert(0, File.dirname(fn))
328 if wd == ''
329 Dir.chdir($:[0])
330 else
331 Dir.chdir(wd)
332 end
333 command = "$0 = '%s'; require '%s'" % [fn, fn]
334 @frames = []
335 set_trace_func proc { |event, file, line, id, binding_, klass, *rest|
336 trace_func(event, file, line, id, binding_, klass)
337 }
338 begin
339 eval(command, @debugBinding)
340 rescue SystemExit
341 # ignore it
342 rescue Exception => exc
343 unhandled_exception(exc)
344 ensure
345 set_trace_func(nil)
346 end
347 return
348
349 when RequestShutdown
350 sessionClose()
351 return
352
353 when RequestBreak
354 fn, line, temporary, set, cond = arg.split("@@")
355 line = line.to_i
356 set = set.to_i
357 temporary = temporary.to_i == 1 ? true : false
358
359 if set == 1
360 if cond == 'None'
361 cond = nil
362 end
363 DEBUGGER__.context(DEBUGGER__.last_thread)\
364 .add_break_point(fn, line, temporary, cond)
365 else
366 DEBUGGER__.context(DEBUGGER__.last_thread)\
367 .delete_break_point(fn, line)
368 end
369 return
370
371 when RequestBreakEnable
372 fn, line, enable = arg.split(',')
373 line = line.to_i
374 enable = enable.to_i == 1 ? true : false
375 DEBUGGER__.context(DEBUGGER__.last_thread)\
376 .enable_break_point(fn, line, enable)
377 return
378
379 when RequestBreakIgnore
380 fn, line, count = arg.split(',')
381 line = line.to_i
382 count = count.to_i
383 DEBUGGER__.context(DEBUGGER__.last_thread)\
384 .ignore_break_point(fn, line, count)
385 return
386
387 when RequestWatch
388 cond, temporary, set = arg.split('@@')
389 set = set.to_i
390 temporary = temporary.to_i == 1 ? true : false
391
392 if set == 1
393 DEBUGGER__.context(DEBUGGER__.last_thread)\
394 .add_watch_point(cond, temporary)
395 else
396 DEBUGGER__.context(DEBUGGER__.last_thread).delete_watch_point(cond)
397 end
398 return
399
400 when RequestWatchEnable
401 cond, enable = arg.split(',')
402 enable = enable.to_i == 1 ? true : false
403 DEBUGGER__.context(DEBUGGER__.last_thread)\
404 .enable_watch_point(cond, enable)
405 return
406
407 when RequestWatchIgnore
408 cond, count = arg.split(',')
409 count = count.to_i
410 DEBUGGER__.context(DEBUGGER__.last_thread).ignore_watch_point(cond, count)
411 return
412
413 when RequestEval, RequestExec
414 if not @running
415 binding_ = @shellBinding
416 else
417 binding_ = DEBUGGER__.context(DEBUGGER__.last_thread).current_binding
418 end
419 write("\n")
420 begin
421 value = eval(arg, binding_)
422 rescue Exception => exc
423 list = []
424 list << "%s: %s\n" % \
425 [exc.class, exc.to_s.sub(/stdin:\d+:(in `.*':?)?/, '')]
426 $@.each do |l|
427 break if l =~ /e4dc_ShellBinding/ or l =~ /e4dc_DebugBinding/ or \
428 l =~ /:in `require'$/
429 list << "%s\n" % l.sub(/\(eval\)/, "(e3dc)")
430 end
431 list.each do |entry|
432 write(entry)
433 end
434 else
435 write("=> #{value.inspect}\n")
436 write("#{ResponseOK}\n")
437 end
438 return
439
440 when RequestBanner
441 version = "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
442 write("%s('%s','%s','%s')\n" % \
443 [ResponseBanner, version, Socket.gethostname(), @variant])
444 return
445
446 when RequestCapabilities
447 write("%s%d, 'Ruby'\n" % \
448 [ResponseCapabilities, @@clientCapabilities])
449 return
450
451 when RequestCompletion
452 completionList(arg)
453 return
454
455 else
456 puts "Got unsupported command %s.\n" % cmd
457 return
458 end
459 end
460
461 if @buffer
462 @buffer << "\n" << line
463 @lineno += 1
464 else
465 @buffer = line.dup
466 end
467
468 # check for completeness
469 if not canEval?
470 @pendingResponse = ResponseContinue
471 else
472 command = @buffer.dup
473 @buffer = ""
474 begin
475 res = "=> "
476 if not @running
477 res << eval(command, @shellBinding, "stdin", @lineno).inspect << "\n"
478 else
479 res << eval(command,
480 DEBUGGER__.context(DEBUGGER__.last_thread).get_binding(@framenr),
481 "stdin", @lineno).inspect << "\n"
482 end
483 write(res)
484 rescue SystemExit => exc
485 progTerminated(exc.status)
486 rescue ScriptError, StandardError => exc
487 list = []
488 list << "%s: %s\n" % \
489 [exc.class, exc.to_s.sub(/stdin:\d+:(in `.*':?)?/, '')]
490 $@.each do |l|
491 break if l =~ /e4dc_ShellBinding/ or l =~ /e4dc_DebugBinding/ or \
492 l =~ /:in `require'$/
493 list << "%s\n" % l.sub(/\(eval\)/, "(e3dc)")
494 end
495 list.each do |entry|
496 write(entry)
497 end
498 handleException()
499 end
500 end
501 end
502
503 def canEval?
504 =begin edoc
505 Private method to check if the buffer's contents can be evaluated.
506
507 @return flag indicating if an eval might succeed (boolean)
508 =end
509 indent = 0
510 if @buffer =~ /,\s*$/
511 return false
512 end
513
514 @buffer.split($/).each do |l|
515 if l =~ /^\s*(class|module|def|if|unless|case|while|until|for|begin)\b[^_]/
516 indent += 1
517 end
518 if l =~ /\s*do\s*(\|.*\|)?\s*$/
519 indent += 1
520 end
521 if l =~ /^\s*end\s*$|^\s*end\b[^_]/
522 indent -= 1
523 end
524 if l =~ /\{\s*(\|.*\|)?\s*$/
525 indent += 1
526 end
527 if l =~ /^\s*\}/
528 indent -= 1
529 end
530 end
531
532 if indent > 0
533 return false
534 end
535 return true
536 end
537
538 def trace_func(event, file, line, id, binding_, klass)
539 =begin edoc
540 Method executed by the tracing facility.
541
542 It is used to save the execution context of an exception.
543
544 @param event the tracing event (String)
545 @param file the name of the file being traced (String)
546 @param line the line number being traced (int)
547 @param id object id
548 @param binding_ a binding object
549 @param klass name of a class
550 =end
551 case event
552 when 'line'
553 if @frames[0]
554 @frames[0] = binding_
555 else
556 @frames.unshift binding_
557 end
558
559 when 'call'
560 @frames.unshift binding_
561
562 when 'class'
563 @frames.unshift binding_
564
565 when 'return', 'end'
566 @frames.shift
567
568 when 'end'
569 @frames.shift
570
571 when 'raise'
572 set_trace_func nil
573 end
574 end
575
576 def write(s)
577 =begin edoc
578 Private method to write data to the output stream.
579
580 @param s data to be written (string)
581 =end
582 @writestream.write(s)
583 @writestream.flush()
584 end
585
586 def interact
587 =begin edoc
588 Private method to Interact with the debugger.
589 =end
590 setDescriptors(@readstream, @writestream)
591 $DebugClientInstance = self
592
593 if not @passive
594 # At this point simulate an event loop.
595 eventLoop()
596 end
597 end
598
599 def eventLoop
600 =begin edoc
601 Private method implementing our event loop.
602 =end
603 @eventExit = nil
604
605 while @eventExit == nil
606 wrdy = []
607
608 if AsyncPendingWrite(@writestream) > 0
609 wrdy << @writestream.getSock()
610 end
611
612 if AsyncPendingWrite(@errorstream) > 0
613 wrdy << @errorstream.getSock()
614 end
615
616 rrdy, wrdy, xrdy = select([@readstream.getSock()], wrdy, [])
617
618 if rrdy.include?(@readstream.getSock())
619 readReady(@readstream.fileno())
620 end
621
622 if wrdy.include?(@writestream.getSock())
623 writeReady(@writestream.fileno())
624 end
625
626 if wrdy.include?(@errorstream.getSock())
627 writeReady(@errorstream.fileno())
628 end
629 end
630
631 @eventExit = nil
632 end
633
634 def eventPoll
635 =begin edoc
636 Private method to poll for events like 'set break point'.
637 =end
638
639 # the choice of a ~0.5 second poll interval is arbitrary.
640 lasteventpolltime = @lasteventpolltime ? @lasteventpolltime : Time.now
641 now = Time.now
642 if now - lasteventpolltime < 0.5
643 @lasteventpolltime = lasteventpolltime
644 return
645 else
646 @lasteventpolltime = now
647 end
648
649 wrdy = []
650
651 if AsyncPendingWrite(@writestream) > 0
652 wrdy << @writestream.getSock()
653 end
654
655 if AsyncPendingWrite(@errorstream) > 0
656 wrdy << @errorstream.getSock()
657 end
658
659 rrdy, wrdy, xrdy = select([@readstream.getSock()], wrdy, [], 0)
660
661 if rrdy == nil
662 return
663 end
664
665 if rrdy.include?(@readstream.getSock())
666 readReady(@readstream.fileno())
667 end
668
669 if wrdy.include?(@writestream.getSock())
670 writeReady(@writestream.fileno())
671 end
672
673 if wrdy.include?(@errorstream.getSock())
674 writeReady(@errorstream.fileno())
675 end
676 end
677
678 def connectDebugger(port, remoteAddress=nil, redirect=true)
679 =begin edoc
680 Public method to establish a session with the debugger.
681
682 It opens a network connection to the debugger, connects it to stdin,
683 stdout and stderr and saves these file objects in case the application
684 being debugged redirects them itself.
685
686 @param port the port number to connect to (int)
687 @param remoteAddress the network address of the debug server host (string)
688 @param redirect flag indicating redirection of stdin, stdout and stderr (boolean)
689 =end
690 if remoteAddress == nil
691 sock = TCPSocket.new(DebugAddress, port)
692 else
693 sock = TCPSocket.new(remoteAddress, port)
694 end
695
696 @readstream = AsyncFile.new(sock, "r", "stdin")
697 @writestream = AsyncFile.new(sock, "w", "stdout")
698 @errorstream = AsyncFile.new(sock, "w", "stderr")
699
700 if redirect
701 $stdin = @readstream
702 $stdout = @writestream
703 $stderr = @errorstream
704 end
705 end
706
707 def progTerminated(status)
708 =begin edoc
709 Private method to tell the debugger that the program has terminated.
710
711 @param status the return status
712 =end
713 if status == nil
714 status = 0
715 else
716 begin
717 Integer(status)
718 rescue
719 status = 1
720 end
721 end
722
723 set_trace_func(nil)
724 @running = nil
725 write("%s%d\n" % [ResponseExit, status])
726 end
727
728 def dumpVariables(frmnr, scope, filter)
729 =begin edoc
730 Private method to return the variables of a frame to the debug server.
731
732 @param frmnr distance of frame reported on. 0 is the current frame (int)
733 @param scope 1 to report global variables, 0 for local variables (int)
734 @param filter the indices of variable types to be filtered (list of int)
735 =end
736 if $debugging
737 if scope == 0
738 @framenr = frmnr
739 end
740 binding_, file, line, id = DEBUGGER__.context(DEBUGGER__.last_thread)\
741 .get_frame(frmnr)
742 else
743 binding_ = @frames[frmnr]
744 end
745 varlist = [scope]
746 if scope >= 1
747 # dump global variables
748 vlist = formatVariablesList(global_variables, binding_, scope, filter)
749 elsif scope == 0
750 # dump local variables
751 vlist = formatVariablesList(eval("local_variables", binding_),
752 binding_, scope, filter)
753 end
754 varlist.concat(vlist)
755 write("%s%s\n" % [ResponseVariables, varlist.inspect])
756 end
757
758 def dumpVariable(var, frmnr, scope, filter)
759 =begin edoc
760 Private method to return the variables of a frame to the debug server.
761
762 @param var list encoded name of the requested variable (list of strings)
763 @param frmnr distance of frame reported on. 0 is the current frame (int)
764 @param scope 1 to report global variables, 0 for local variables (int)
765 @param filter the indices of variable types to be filtered (list of int)
766 =end
767 if $debugging
768 binding_, file, line, id = DEBUGGER__.context(DEBUGGER__.last_thread)\
769 .get_frame(frmnr)
770 else
771 binding_ = @frames[frmnr]
772 end
773 varlist = [scope, var]
774 i = 0
775 obj = nil
776 keylist = nil
777 formatSequences = false
778 access = ""
779 isDict = false
780
781 while i < var.length
782 if ["[]", "{}"].include?(var[i][-2..-1])
783 if i+1 == var.length
784 keylist = [var[i][0..-3]]
785 formatSequences = true
786 if access.length == 0
787 access = "#{var[i][0..-3]}"
788 else
789 access << "[#{var[i][0..-3]}]"
790 end
791 if var[i][-2..-1] == "{}"
792 isDict = true
793 end
794 break
795 else
796 if access.length == 0
797 access = "#{var[i][0..-3]}"
798 else
799 access << "[#{var[i][0..-3]}]"
800 end
801 end
802 else
803 if access.length != 0
804 access << "[#{var[i]}]"
805 obj = eval(access, binding_)
806 binding_ = obj.instance_eval{binding()}
807 access = ""
808 else
809 obj = eval(var[i], binding_)
810 binding_ = obj.instance_eval{binding()}
811 end
812 end
813 i += 1
814 end
815 if formatSequences
816 bind = binding_
817 else
818 bind = obj.instance_eval{binding()}
819 end
820 if not keylist
821 keylist = obj.instance_variables
822 access = nil
823 else
824 if access.length != 0
825 obj = eval(access, bind)
826 end
827 if isDict
828 keylist = obj.keys()
829 else
830 keylist = Array.new(obj.length){|i| i}
831 end
832 end
833 vlist = formatVariablesList(keylist, bind, scope, filter,
834 excludeSelf=true, access=access)
835 varlist.concat(vlist)
836 write("%s%s\n" % [ResponseVariable, varlist.inspect])
837 end
838
839 def formatVariablesList(keylist, binding_, scope, filter = [],
840 excludeSelf = false, access = nil)
841 =begin edoc
842 Private method to produce a formated variables list.
843
844 The binding passed in to it is scanned. Variables are
845 only added to the list, if their type is not contained
846 in the filter list and their name doesn't match any of the filter expressions.
847 The formated variables list (a list of lists of 3 values) is returned.
848
849 @param keylist keys of the dictionary
850 @param binding_ the binding to be scanned
851 @param scope 1 to filter using the globals filter, 0 using the locals filter (int).
852 Variables are only added to the list, if their name do not match any of the
853 filter expressions.
854 @param filter the indices of variable types to be filtered. Variables are
855 only added to the list, if their type is not contained in the filter
856 list.
857 @param excludeSelf flag indicating if the self object should be excluded from
858 the listing (boolean)
859 @param access String specifying the access path to (String)
860 @return A list consisting of a list of formatted variables. Each variable
861 entry is a list of three elements, the variable name, its type and
862 value.
863 =end
864 varlist = []
865 if scope >= 1
866 patternFilterObjects = @globalsFilterObjects
867 else
868 patternFilterObjects = @localsFilterObjects
869 begin
870 obj = eval("self", binding_)
871 rescue StandardError, ScriptError
872 obj = nil
873 end
874 if not excludeSelf and obj.class != Object
875 keylist << "self"
876 end
877 end
878
879 keylist.each do |key|
880 # filter based on the filter pattern
881 matched = false
882 patternFilterObjects.each do |pat|
883 if pat.match(key)
884 matched = true
885 break
886 end
887 end
888 next if matched
889
890 begin
891 if access
892 if key.to_s == key
893 key = "'%s'" % key
894 else
895 key = key.to_s
896 end
897 k = "#{access}[%s]" % key
898 obj = eval(k, binding_)
899 else
900 obj = eval(key, binding_)
901 end
902 rescue NameError
903 next
904 end
905
906 if obj or obj.class == NilClass or obj.class == FalseClass
907 otype = obj.class.to_s
908 if obj.inspect.nil?
909 otype = ""
910 oval = ""
911 else
912 oval = obj.inspect.gsub(/=>/,":")
913 end
914 else
915 otype = ""
916 oval = obj.inspect
917 end
918
919 next if inFilter?(filter, otype, oval)
920
921 if oval.index("#<") == 0
922 addr = extractAddress(oval)
923 oval = "<#{otype} object at #{addr}>"
924 end
925 if obj
926 if obj.class == Array or obj.class == Hash
927 oval = "%d" % obj.length()
928 end
929 end
930 varlist << [key, otype, oval]
931 end
932 return varlist
933 end
934
935 def extractAddress(var)
936 =begin edoc
937 Private method to extract the address part of an object description.
938
939 @param var object description (String)
940 @return the address contained in the object description (String)
941 =end
942 m = var.match(/^#<.*?:([^:]*?) /)
943 if m
944 return m[1]
945 else
946 return ""
947 end
948 end
949
950 def extractTypeAndAddress(var)
951 =begin edoc
952 Private method to extract the address and type parts of an object description.
953
954 @param var object description (String)
955 @return list containing the type and address contained in the object
956 description (Array of two String)
957 =end
958 m = var.match(/^#<(.*?):(.*?) /)
959 if m
960 return [m[1], m[2]]
961 else
962 return ["", ""]
963 end
964 end
965
966 def inFilter?(filter, otype, oval)
967 =begin edoc
968 Private method to check, if a variable is to be filtered based on its type.
969
970 @param filter the indices of variable types to be filtered (Array of int.
971 @param otype type of the variable to be checked (String)
972 @param oval variable value to be checked (String)
973 @return flag indicating, whether the variable should be filtered (boolean)
974 =end
975 cindex = ConfigVarTypeStrings.index(otype)
976 if cindex == nil
977 if oval.index("#<") == 0
978 if filter.include?(ConfigVarTypeStrings.index("instance"))
979 return true
980 else
981 return false
982 end
983 elsif ['FalseClass', 'TrueClass'].include?(otype)
984 if filter.include?(ConfigVarTypeStrings.index("bool"))
985 return true
986 else
987 return false
988 end
989 else
990 if filter.include?(ConfigVarTypeStrings.index("other"))
991 return true
992 else
993 return false
994 end
995 end
996 end
997 if filter.include?(cindex)
998 return true
999 end
1000 return false
1001 end
1002
1003 def generateFilterObjects(scope, filterString)
1004 =begin edoc
1005 Private method to convert a filter string to a list of filter objects.
1006
1007 @param scope 1 to generate filter for global variables, 0 for local variables (int)
1008 @param filterString string of filter patterns separated by ';'
1009 =end
1010 patternFilterObjects = []
1011 for pattern in filterString.split(';')
1012 patternFilterObjects << Regexp.compile('^%s$' % pattern)
1013 end
1014 if scope == 1
1015 @globalsFilterObjects = patternFilterObjects[0..-1]
1016 else
1017 @localsFilterObjects = patternFilterObjects[0..-1]
1018 end
1019 end
1020
1021 def completionList(text)
1022 =begin edoc
1023 Method used to handle the command completion request
1024
1025 @param text the text to be completed (string)
1026 =end
1027 completions = @completer.complete(text).compact.sort.uniq
1028 write("%s%s||%s\n" % [ResponseCompletion, completions.inspect, text])
1029 end
1030
1031 def startProgInDebugger(progargs, wd = '', host = nil,
1032 port = nil, exceptions = true, traceRuby = false, redirect=true)
1033 =begin edoc
1034 Method used to start the remote debugger.
1035
1036 @param progargs commandline for the program to be debugged
1037 (list of strings)
1038 @param wd working directory for the program execution (string)
1039 @param host hostname of the debug server (string)
1040 @param port portnumber of the debug server (int)
1041 @param exceptions flag to enable exception reporting of the IDE (boolean)
1042 @param traceRuby flag to enable tracing into the Ruby library
1043 @param redirect flag indicating redirection of stdin, stdout and stderr (boolean)
1044 =end
1045 $debugging = true
1046
1047 if host == nil
1048 host = ENV.fetch('ERICHOST', 'localhost')
1049 end
1050 if port == nil
1051 port = ENV.fetch('ERICPORT', 42424)
1052 end
1053
1054 connectDebugger(port, host, redirect) #TCPSocket.gethostbyname(host)[3])
1055 DEBUGGER__.attach(self)
1056
1057 @traceRuby = traceRuby
1058
1059 fn = progargs.shift
1060 fn = File.expand_path(fn)
1061
1062 ARGV.clear()
1063 ARGV.concat(progargs)
1064 $:.insert(0, File.dirname(fn))
1065 if wd == ''
1066 Dir.chdir($:[0])
1067 else
1068 Dir.chdir(wd)
1069 end
1070 @running = fn
1071
1072 @passive = true
1073 write("%s%s|%d\n" % [PassiveStartup, @running, exceptions ? 1 : 0])
1074 interact()
1075
1076 command = "$0 = '%s'; require '%s'" % [fn, fn]
1077 set_trace_func proc { |event, file, line, id, binding_, klass, *rest|
1078 DEBUGGER__.context.trace_func(event, file, line, id, binding_, klass)
1079 }
1080 begin
1081 eval(command, @debugBinding)
1082 rescue DebugQuit
1083 # just ignore it
1084 rescue Exception => exc
1085 unhandled_exception(exc)
1086 ensure
1087 set_trace_func(nil)
1088 @running = nil
1089 end
1090 # just a short delay because client might shut down too quickly otherwise
1091 sleep(1)
1092 end
1093
1094 def main
1095 =begin edoc
1096 Public method implementing the main method.
1097 =end
1098 if ARGV.include?('--')
1099 args = ARGV[0..-1]
1100 host = nil
1101 port = nil
1102 wd = ''
1103 traceRuby = false
1104 exceptions = true
1105 redirect = true
1106 while args[0]
1107 if args[0] == '-h'
1108 host = args[1]
1109 args.shift
1110 args.shift
1111 elsif args[0] == '-p'
1112 port = args[1]
1113 args.shift
1114 args.shift
1115 elsif args[0] == '-w'
1116 wd = args[1]
1117 args.shift
1118 args.shift
1119 elsif args[0] == '-t'
1120 traceRuby = true
1121 args.shift
1122 elsif args[0] == '-e'
1123 exceptions = false
1124 args.shift
1125 elsif args[0] == '-n'
1126 redirect = false
1127 args.shift
1128 elsif args[0] == '--'
1129 args.shift
1130 break
1131 else # unknown option
1132 args.shift
1133 end
1134 end
1135 if args.length == 0
1136 STDOUT << "No program given. Aborting!"
1137 else
1138 startProgInDebugger(args, wd, host, port,
1139 exceptions = exceptions, traceRuby = traceRuby,
1140 redirect = redirect)
1141 end
1142 else
1143 if ARGV[0] == '--no-encoding'
1144 # just ignore it, it's here to be compatible with python debugger
1145 ARGV.shift
1146 end
1147
1148 begin
1149 port = ARGV[0].to_i
1150 rescue
1151 port = -1
1152 end
1153
1154 begin
1155 redirect = ARGV[1].to_i
1156 redirect = redirect == 1 ? true : false
1157 rescue
1158 redirect = true
1159 end
1160
1161 begin
1162 remoteAddress = ARGV[2]
1163 rescue
1164 remoteAddress = nil
1165 end
1166
1167 ARGV.clear()
1168 $:.insert(0,"")
1169 if port >= 0
1170 connectDebugger(port, remoteAddress, redirect)
1171 DEBUGGER__.attach(self)
1172 interact()
1173 end
1174 end
1175 end
1176 end

eric ide

mercurial