byebug 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. data/.gitignore +10 -0
  2. data/.travis.yml +8 -0
  3. data/AUTHORS +10 -0
  4. data/CHANGELOG.md +2 -0
  5. data/CONTRIBUTING.md +1 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE +20 -0
  8. data/README.md +5 -0
  9. data/Rakefile +28 -0
  10. data/bin/byebug +395 -0
  11. data/byebug.gemspec +29 -0
  12. data/doc/hanoi.rb +35 -0
  13. data/doc/primes.rb +28 -0
  14. data/doc/rdebug-emacs.texi +1030 -0
  15. data/doc/test-tri2.rb +18 -0
  16. data/doc/tri3.rb +8 -0
  17. data/doc/triangle.rb +12 -0
  18. data/ext/byebug/breakpoint.c +476 -0
  19. data/ext/byebug/byebug.c +512 -0
  20. data/ext/byebug/byebug.h +131 -0
  21. data/ext/byebug/context.c +424 -0
  22. data/ext/byebug/extconf.rb +21 -0
  23. data/ext/byebug/locker.c +53 -0
  24. data/lib/byebug.rb +404 -0
  25. data/lib/byebug/command.rb +232 -0
  26. data/lib/byebug/commands/breakpoints.rb +153 -0
  27. data/lib/byebug/commands/catchpoint.rb +56 -0
  28. data/lib/byebug/commands/condition.rb +49 -0
  29. data/lib/byebug/commands/continue.rb +38 -0
  30. data/lib/byebug/commands/control.rb +110 -0
  31. data/lib/byebug/commands/display.rb +122 -0
  32. data/lib/byebug/commands/edit.rb +48 -0
  33. data/lib/byebug/commands/enable.rb +202 -0
  34. data/lib/byebug/commands/eval.rb +176 -0
  35. data/lib/byebug/commands/finish.rb +43 -0
  36. data/lib/byebug/commands/frame.rb +303 -0
  37. data/lib/byebug/commands/help.rb +56 -0
  38. data/lib/byebug/commands/info.rb +462 -0
  39. data/lib/byebug/commands/irb.rb +123 -0
  40. data/lib/byebug/commands/jump.rb +66 -0
  41. data/lib/byebug/commands/kill.rb +51 -0
  42. data/lib/byebug/commands/list.rb +94 -0
  43. data/lib/byebug/commands/method.rb +84 -0
  44. data/lib/byebug/commands/quit.rb +39 -0
  45. data/lib/byebug/commands/reload.rb +40 -0
  46. data/lib/byebug/commands/save.rb +90 -0
  47. data/lib/byebug/commands/set.rb +210 -0
  48. data/lib/byebug/commands/show.rb +246 -0
  49. data/lib/byebug/commands/skip.rb +35 -0
  50. data/lib/byebug/commands/source.rb +36 -0
  51. data/lib/byebug/commands/stepping.rb +83 -0
  52. data/lib/byebug/commands/threads.rb +189 -0
  53. data/lib/byebug/commands/tmate.rb +36 -0
  54. data/lib/byebug/commands/trace.rb +56 -0
  55. data/lib/byebug/commands/variables.rb +199 -0
  56. data/lib/byebug/context.rb +58 -0
  57. data/lib/byebug/helper.rb +69 -0
  58. data/lib/byebug/interface.rb +223 -0
  59. data/lib/byebug/processor.rb +468 -0
  60. data/lib/byebug/version.rb +3 -0
  61. data/man/rdebug.1 +241 -0
  62. data/test/breakpoints_test.rb +357 -0
  63. data/test/conditions_test.rb +77 -0
  64. data/test/continue_test.rb +44 -0
  65. data/test/display_test.rb +141 -0
  66. data/test/edit_test.rb +56 -0
  67. data/test/eval_test.rb +92 -0
  68. data/test/examples/breakpoint1.rb +15 -0
  69. data/test/examples/breakpoint2.rb +7 -0
  70. data/test/examples/conditions.rb +4 -0
  71. data/test/examples/continue.rb +4 -0
  72. data/test/examples/display.rb +5 -0
  73. data/test/examples/edit.rb +3 -0
  74. data/test/examples/edit2.rb +3 -0
  75. data/test/examples/eval.rb +4 -0
  76. data/test/examples/finish.rb +20 -0
  77. data/test/examples/frame.rb +20 -0
  78. data/test/examples/frame_threads.rb +31 -0
  79. data/test/examples/help.rb +2 -0
  80. data/test/examples/info.rb +38 -0
  81. data/test/examples/info2.rb +3 -0
  82. data/test/examples/info_threads.rb +48 -0
  83. data/test/examples/irb.rb +6 -0
  84. data/test/examples/jump.rb +14 -0
  85. data/test/examples/kill.rb +2 -0
  86. data/test/examples/list.rb +12 -0
  87. data/test/examples/method.rb +15 -0
  88. data/test/examples/post_mortem.rb +19 -0
  89. data/test/examples/quit.rb +2 -0
  90. data/test/examples/reload.rb +6 -0
  91. data/test/examples/restart.rb +6 -0
  92. data/test/examples/save.rb +3 -0
  93. data/test/examples/set.rb +3 -0
  94. data/test/examples/set_annotate.rb +12 -0
  95. data/test/examples/settings.rb +1 -0
  96. data/test/examples/show.rb +2 -0
  97. data/test/examples/source.rb +3 -0
  98. data/test/examples/stepping.rb +21 -0
  99. data/test/examples/thread.rb +32 -0
  100. data/test/examples/tmate.rb +10 -0
  101. data/test/examples/trace.rb +7 -0
  102. data/test/examples/trace_threads.rb +20 -0
  103. data/test/examples/variables.rb +26 -0
  104. data/test/finish_test.rb +48 -0
  105. data/test/frame_test.rb +143 -0
  106. data/test/help_test.rb +50 -0
  107. data/test/info_test.rb +313 -0
  108. data/test/irb_test.rb +81 -0
  109. data/test/jump_test.rb +70 -0
  110. data/test/kill_test.rb +48 -0
  111. data/test/list_test.rb +145 -0
  112. data/test/method_test.rb +70 -0
  113. data/test/post_mortem_test.rb +27 -0
  114. data/test/quit_test.rb +56 -0
  115. data/test/reload_test.rb +44 -0
  116. data/test/restart_test.rb +164 -0
  117. data/test/save_test.rb +92 -0
  118. data/test/set_test.rb +177 -0
  119. data/test/show_test.rb +293 -0
  120. data/test/source_test.rb +45 -0
  121. data/test/stepping_test.rb +130 -0
  122. data/test/support/breakpoint.rb +13 -0
  123. data/test/support/context.rb +14 -0
  124. data/test/support/matchers.rb +67 -0
  125. data/test/support/mocha_extensions.rb +72 -0
  126. data/test/support/processor.rb +7 -0
  127. data/test/support/test_dsl.rb +206 -0
  128. data/test/support/test_interface.rb +68 -0
  129. data/test/test_helper.rb +10 -0
  130. data/test/tmate_test.rb +44 -0
  131. data/test/trace_test.rb +159 -0
  132. data/test/variables_test.rb +119 -0
  133. metadata +265 -0
@@ -0,0 +1,58 @@
1
+ module Byebug
2
+
3
+ class << self
4
+
5
+ # interface modules provide +handler+ object
6
+ attr_accessor :handler
7
+
8
+ end
9
+
10
+ class Context
11
+
12
+ def frame_locals(frame_no=0)
13
+ result = {}
14
+ binding = frame_binding(frame_no)
15
+ locals = eval("local_variables", binding)
16
+ locals.each {|local| result[local.to_s] = eval(local.to_s, binding)}
17
+ result
18
+ end
19
+
20
+ def frame_class(frame_no=0)
21
+ frame_self(frame_no).class
22
+ end
23
+
24
+ def frame_args_info(frame_no=0)
25
+ nil
26
+ end
27
+
28
+ def interrupt
29
+ self.stop_next = 1
30
+ end
31
+
32
+ def handler
33
+ Byebug.handler or raise 'No interface loaded'
34
+ end
35
+
36
+ def at_breakpoint(breakpoint)
37
+ handler.at_breakpoint(self, breakpoint)
38
+ end
39
+
40
+ def at_catchpoint(excpt)
41
+ handler.at_catchpoint(self, excpt)
42
+ end
43
+
44
+ def at_tracing(file, line)
45
+ @tracing_started = File.identical?(file, Byebug::PROG_SCRIPT)
46
+ handler.at_tracing(self, file, line) if @tracing_started
47
+ end
48
+
49
+ def at_line(file, line)
50
+ handler.at_line(self, file, line)
51
+ end
52
+
53
+ def at_return(file, line)
54
+ handler.at_return(self, file, line)
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,69 @@
1
+ module Byebug
2
+
3
+ module ParseFunctions
4
+ Position_regexp = '(?:(\d+)|(.+?)[:.#]([^.:\s]+))'
5
+
6
+ # Parse 'str' of command 'cmd' as an integer between
7
+ # min and max. If either min or max is nil, that
8
+ # value has no bound.
9
+ def get_int(str, cmd, min=nil, max=nil, default=1)
10
+ return default unless str
11
+ begin
12
+ int = Integer(str)
13
+ if min and int < min
14
+ print "%s argument '%s' needs to at least %s.\n" % [cmd, str, min]
15
+ return nil
16
+ elsif max and int > max
17
+ print "%s argument '%s' needs to at most %s.\n" % [cmd, str, max]
18
+ return nil
19
+ end
20
+ return int
21
+ rescue
22
+ print "%s argument '%s' needs to be a number.\n" % [cmd, str]
23
+ return nil
24
+ end
25
+ end
26
+
27
+ # Return true if arg is 'on' or 1 and false arg is 'off' or 0.
28
+ # Any other value raises RuntimeError.
29
+ def get_onoff(arg, default=nil, print_error=true)
30
+ if arg.nil? or arg == ''
31
+ if default.nil?
32
+ if print_error
33
+ print "Expecting 'on', 1, 'off', or 0. Got nothing.\n"
34
+ raise RuntimeError
35
+ end
36
+ return default
37
+ end
38
+ end
39
+ case arg.downcase
40
+ when '1', 'on'
41
+ return true
42
+ when '0', 'off'
43
+ return false
44
+ else
45
+ if print_error
46
+ print "Expecting 'on', 1, 'off', or 0. Got: %s.\n" % arg.to_s
47
+ raise RuntimeError
48
+ end
49
+ end
50
+ end
51
+
52
+ # Return 'on' or 'off' for supplied parameter. The parmeter should
53
+ # be true, false or nil.
54
+ def show_onoff(bool)
55
+ if not [TrueClass, FalseClass, NilClass].member?(bool.class)
56
+ return "??"
57
+ end
58
+ return bool ? 'on' : 'off'
59
+ end
60
+
61
+ # Return true if code is syntactically correct for Ruby.
62
+ def syntax_valid?(code)
63
+ eval("BEGIN {return true}\n#{code}", nil, "", 0)
64
+ rescue Exception
65
+ false
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,223 @@
1
+ module Byebug
2
+
3
+ class Interface # :nodoc:
4
+ attr_writer :have_readline
5
+
6
+ def initialize
7
+ @have_readline = false
8
+ end
9
+
10
+ # Common routine for reporting byebug error messages.
11
+ # Derived classed may want to override this to capture output.
12
+ def errmsg(*args)
13
+ if Byebug.annotate.to_i > 2
14
+ aprint 'error-begin'
15
+ print(*args)
16
+ aprint ''
17
+ else
18
+ print '*** '
19
+ print(*args)
20
+ end
21
+ end
22
+
23
+ # Format msg with gdb-style annotation header
24
+ def afmt(msg, newline="\n")
25
+ "\032\032#{msg}#{newline}"
26
+ end
27
+
28
+ def aprint(msg)
29
+ print afmt(msg)
30
+ end
31
+
32
+ end
33
+
34
+ class LocalInterface < Interface # :nodoc:
35
+ attr_accessor :command_queue, :history_length, :history_save, :histfile
36
+ attr_accessor :restart_file
37
+
38
+ unless defined?(FILE_HISTORY)
39
+ FILE_HISTORY = ".rdebug_hist"
40
+ end
41
+ def initialize()
42
+ super
43
+ @command_queue = []
44
+ @have_readline = false
45
+ @history_save = true
46
+ # take gdb's default
47
+ @history_length = ENV["HISTSIZE"] ? ENV["HISTSIZE"].to_i : 256
48
+ @histfile = File.join(ENV["HOME"]||ENV["HOMEPATH"]||".",
49
+ FILE_HISTORY)
50
+ open(@histfile, 'r') do |file|
51
+ file.each do |line|
52
+ line.chomp!
53
+ Readline::HISTORY << line
54
+ end
55
+ end if File.exist?(@histfile)
56
+ @restart_file = nil
57
+ end
58
+
59
+ def read_command(prompt)
60
+ readline(prompt, true)
61
+ end
62
+
63
+ def confirm(prompt)
64
+ readline(prompt, false)
65
+ end
66
+
67
+ def print(*args)
68
+ STDOUT.printf(*args)
69
+ end
70
+
71
+ def close
72
+ end
73
+
74
+ # Things to do before quitting
75
+ def finalize
76
+ if Byebug.method_defined?("annotate") and Byebug.annotate.to_i > 2
77
+ print "\032\032exited\n\n"
78
+ end
79
+ if Byebug.respond_to?(:save_history)
80
+ Byebug.save_history
81
+ end
82
+ end
83
+
84
+ def readline_support?
85
+ @have_readline
86
+ end
87
+
88
+ private
89
+
90
+ begin
91
+ require 'readline'
92
+ class << Byebug
93
+ @have_readline = true
94
+ define_method(:save_history) do
95
+ iface = self.handler.interface
96
+ iface.histfile ||= File.join(ENV["HOME"]||ENV["HOMEPATH"]||".",
97
+ FILE_HISTORY)
98
+ open(iface.histfile, 'w') do |file|
99
+ Readline::HISTORY.to_a.last(iface.history_length).each do |line|
100
+ file.puts line unless line.strip.empty?
101
+ end if defined?(iface.history_save) and iface.history_save
102
+ end rescue nil
103
+ end
104
+ public :save_history
105
+ end
106
+
107
+ def readline(prompt, hist)
108
+ Readline::readline(prompt, hist)
109
+ end
110
+ rescue LoadError
111
+ def readline(prompt, hist)
112
+ @histfile = ''
113
+ @hist_save = false
114
+ STDOUT.print prompt
115
+ STDOUT.flush
116
+ line = STDIN.gets
117
+ exit unless line
118
+ line.chomp!
119
+ line
120
+ end
121
+ end
122
+ end
123
+
124
+ class RemoteInterface < Interface # :nodoc:
125
+ attr_accessor :command_queue, :history_length, :history_save, :histfile
126
+ attr_accessor :restart_file
127
+
128
+ def initialize(socket)
129
+ @command_queue = []
130
+ @socket = socket
131
+ @history_save = false
132
+ @history_length = 256
133
+ @histfile = ''
134
+ # Do we read the histfile?
135
+ # open(@histfile, 'r') do |file|
136
+ # file.each do |line|
137
+ # line.chomp!
138
+ # Readline::HISTORY << line
139
+ # end
140
+ # end if File.exist?(@histfile)
141
+ @restart_file = nil
142
+ end
143
+
144
+ def close
145
+ @socket.close
146
+ rescue Exception
147
+ end
148
+
149
+ def confirm(prompt)
150
+ send_command "CONFIRM #{prompt}"
151
+ end
152
+
153
+ def finalize
154
+ end
155
+
156
+ def read_command(prompt)
157
+ send_command "PROMPT #{prompt}"
158
+ end
159
+
160
+ def readline_support?
161
+ false
162
+ end
163
+
164
+ def print(*args)
165
+ @socket.printf(*args)
166
+ end
167
+
168
+ private
169
+
170
+ def send_command(msg)
171
+ @socket.puts msg
172
+ result = @socket.gets
173
+ raise IOError unless result
174
+ result.chomp
175
+ end
176
+ end
177
+
178
+ class ScriptInterface < Interface # :nodoc:
179
+ attr_accessor :command_queue, :history_length, :history_save, :histfile
180
+ attr_accessor :restart_file
181
+
182
+ def initialize(file, out, verbose=false)
183
+ super()
184
+ @command_queue = []
185
+ @file = file.respond_to?(:gets) ? file : open(file)
186
+ @out = out
187
+ @verbose = verbose
188
+ @history_save = false
189
+ @history_length = 256 # take gdb default
190
+ @histfile = ''
191
+ end
192
+
193
+ def finalize
194
+ end
195
+
196
+ def read_command(prompt)
197
+ while result = @file.gets
198
+ puts "# #{result}" if @verbose
199
+ next if result =~ /^\s*#/
200
+ next if result.strip.empty?
201
+ break
202
+ end
203
+ raise IOError unless result
204
+ result.chomp!
205
+ end
206
+
207
+ def readline_support?
208
+ false
209
+ end
210
+
211
+ def confirm(prompt)
212
+ 'y'
213
+ end
214
+
215
+ def print(*args)
216
+ @out.printf(*args)
217
+ end
218
+
219
+ def close
220
+ @file.close
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,468 @@
1
+ require_relative 'interface'
2
+ require_relative 'command'
3
+
4
+ module Byebug
5
+
6
+ # Should this be a mixin?
7
+ class Processor # :nodoc
8
+ attr_accessor :interface
9
+
10
+ # Format msg with gdb-style annotation header
11
+ def afmt(msg, newline="\n")
12
+ "\032\032#{msg}#{newline}"
13
+ end
14
+
15
+ def aprint(msg)
16
+ print afmt(msg) if Byebug.annotate.to_i > 2
17
+ end
18
+
19
+ # FIXME: use delegate?
20
+ def errmsg(*args)
21
+ @interface.errmsg(*args)
22
+ end
23
+
24
+ # Callers of this routine should make sure to use comma to separate format
25
+ # argments rather than %. Otherwise it seems that if the string you want to
26
+ # print has format specifier, which could happen if you are trying to show
27
+ # say a source-code line with "puts" or "print" in it, this print routine
28
+ # will give an error saying it is looking for more arguments.
29
+ def print(*args)
30
+ @interface.print(*args)
31
+ end
32
+
33
+ end
34
+
35
+ class CommandProcessor < Processor # :nodoc:
36
+ attr_reader :display
37
+
38
+ # FIXME: get from Command regexp method.
39
+ @@Show_breakpoints_postcmd = [
40
+ /^\s*b(?:reak)?/,
41
+ /^\s* cond(?:ition)? (?:\s+(\d+)\s*(.*))?$/ix,
42
+ /^\s*del(?:ete)?(?:\s+(.*))?$/ix,
43
+ /^\s* dis(?:able)? (?:\s+(.*))?$/ix,
44
+ /^\s* en(?:able)? (?:\s+(.*))?$/ix
45
+ # "tbreak", "clear",
46
+ ]
47
+ @@Show_annotations_run = [
48
+ /^\s*c(?:ont(?:inue)?)?(?:\s+(.*))?$/,
49
+ /^\s*fin(?:ish)?$/,
50
+ /^\s*n(?:ext)?([+-])?(?:\s+(.*))?$/,
51
+ /^\s*s(?:tep)?([+-])?(?:\s+(.*))?$/
52
+ ]
53
+
54
+ @@Show_annotations_postcmd = [
55
+ /^\s* down (?:\s+(.*))? .*$/x,
56
+ /^\s* f(?:rame)? (?:\s+ (.*))? \s*$/x,
57
+ /^\s* u(?:p)? (?:\s+(.*))?$/x
58
+ ]
59
+
60
+ def initialize(interface = LocalInterface.new)
61
+ @interface = interface
62
+ @display = []
63
+
64
+ @mutex = Mutex.new
65
+ @last_cmd = nil
66
+ @last_file = nil # Filename the last time we stopped
67
+ @last_line = nil # line number the last time we stopped
68
+ @byebug_breakpoints_were_empty = false # Show breakpoints 1st time
69
+ @byebug_displays_were_empty = true # No display 1st time
70
+ @byebug_context_was_dead = true # Assume we haven't started.
71
+ end
72
+
73
+ def interface=(interface)
74
+ @mutex.synchronize do
75
+ @interface.close if @interface
76
+ @interface = interface
77
+ end
78
+ end
79
+
80
+ require 'pathname' # For cleanpath
81
+
82
+ # Regularize file name.
83
+ # This is also used as a common funnel place if basename is
84
+ # desired or if we are working remotely and want to change the
85
+ # basename. Or we are eliding filenames.
86
+ def self.canonic_file(filename)
87
+ # For now we want resolved filenames
88
+ if Command.settings[:basename]
89
+ File.basename(filename)
90
+ else
91
+ # Cache this?
92
+ Pathname.new(filename).cleanpath.to_s
93
+ end
94
+ end
95
+
96
+ def self.print_location_and_text(file, line)
97
+ file_line = "%s:%s\n%s" % [canonic_file(file), line,
98
+ Byebug.line_at(file, line)]
99
+ # FIXME: use annotations routines
100
+ if Byebug.annotate.to_i > 2
101
+ file_line = "\032\032source #{file_line}"
102
+ elsif ENV['EMACS']
103
+ file_line = "\032\032#{file_line}"
104
+ end
105
+ print file_line
106
+ end
107
+
108
+ def self.protect(mname)
109
+ alias_method "__#{mname}", mname
110
+ module_eval %{
111
+ def #{mname}(*args)
112
+ @mutex.synchronize do
113
+ return unless @interface
114
+ __#{mname}(*args)
115
+ end
116
+ rescue IOError, Errno::EPIPE
117
+ self.interface = nil
118
+ rescue SignalException
119
+ raise
120
+ rescue Exception
121
+ print "INTERNAL ERROR!!! #\{$!\}\n" rescue nil
122
+ print $!.backtrace.map{|l| "\t#\{l\}"}.join("\n") rescue nil
123
+ end
124
+ }
125
+ end
126
+
127
+ def at_breakpoint(context, breakpoint)
128
+ aprint 'stopped' if Byebug.annotate.to_i > 2
129
+ n = Byebug.breakpoints.index(breakpoint) + 1
130
+ file = CommandProcessor.canonic_file(breakpoint.source)
131
+ line = breakpoint.pos
132
+ if Byebug.annotate.to_i > 2
133
+ print afmt("source #{file}:#{line}")
134
+ end
135
+ print "Breakpoint %d at %s:%s\n", n, file, line
136
+ end
137
+ protect :at_breakpoint
138
+
139
+ def at_catchpoint(context, excpt)
140
+ aprint 'stopped' if Byebug.annotate.to_i > 2
141
+ file = CommandProcessor.canonic_file(context.frame_file(0))
142
+ line = context.frame_line(0)
143
+ print afmt("%s:%d" % [file, line]) if ENV['EMACS']
144
+ print "Catchpoint at %s:%d: `%s' (%s)\n", file, line, excpt, excpt.class
145
+ fs = context.stack_size
146
+ tb = caller(0)[-fs..-1]
147
+ if tb
148
+ for i in tb
149
+ print "\tfrom %s\n", i
150
+ end
151
+ end
152
+ end
153
+ protect :at_catchpoint
154
+
155
+ def at_tracing(context, file, line)
156
+ return if defined?(Byebug::RDEBUG_FILE) &&
157
+ Byebug::RDEBUG_FILE == file # Don't trace ourself
158
+ @last_file = CommandProcessor.canonic_file(file)
159
+ file = CommandProcessor.canonic_file(file)
160
+ unless file == @last_file and @last_line == line and
161
+ Command.settings[:tracing_plus]
162
+ print "Tracing:%s:%s %s", file, line, Byebug.line_at(file, line)
163
+ @last_file = file
164
+ @last_line = line
165
+ end
166
+ always_run(context, file, line, 2)
167
+ end
168
+ protect :at_tracing
169
+
170
+ def at_line(context, file, line)
171
+ process_commands(context, file, line)
172
+ end
173
+ protect :at_line
174
+
175
+ def at_return(context, file, line)
176
+ context.stop_frame = -1
177
+ process_commands(context, file, line)
178
+ end
179
+
180
+ private
181
+
182
+ # The prompt shown before reading a command.
183
+ def prompt(context)
184
+ p = '(rdb:%s) ' % (context.dead? ? 'post-mortem' : context.thnum)
185
+ p = afmt("pre-prompt")+p+"\n"+afmt("prompt") if Byebug.annotate.to_i > 2
186
+ return p
187
+ end
188
+
189
+ # Run these commands, for example display commands or possibly the list or
190
+ # irb in an "autolist" or "autoirb". We return a list of commands that are
191
+ # acceptable to run bound to the current state.
192
+ def always_run(context, file, line, run_level)
193
+ event_cmds = Command.commands.select{|cmd| cmd.event }
194
+
195
+ # Remove some commands in post-mortem
196
+ event_cmds = event_cmds.find_all do |cmd|
197
+ cmd.allow_in_post_mortem
198
+ end if context.dead?
199
+
200
+ state = State.new do |s|
201
+ s.context = context
202
+ s.file = file
203
+ s.line = line
204
+ s.binding = context.frame_binding(0)
205
+ s.display = display
206
+ s.interface = interface
207
+ s.commands = event_cmds
208
+ end
209
+ @interface.state = state if @interface.respond_to?('state=')
210
+
211
+ # Bind commands to the current state.
212
+ commands = event_cmds.map{|cmd| cmd.new(state)}
213
+
214
+ commands.select do |cmd|
215
+ cmd.class.always_run >= run_level
216
+ end.each {|cmd| cmd.execute}
217
+ return state, commands
218
+ end
219
+
220
+ # Handle byebug commands
221
+ def process_commands(context, file, line)
222
+ state, commands = always_run(context, file, line, 1)
223
+ $rdebug_state = state if Command.settings[:byebugtesting]
224
+ splitter = lambda do |str|
225
+ str.split(/;/).inject([]) do |m, v|
226
+ if m.empty?
227
+ m << v
228
+ else
229
+ if m.last[-1] == ?\\
230
+ m.last[-1,1] = ''
231
+ m.last << ';' << v
232
+ else
233
+ m << v
234
+ end
235
+ end
236
+ m
237
+ end
238
+ end
239
+
240
+ preloop(commands, context)
241
+ CommandProcessor.print_location_and_text(file, line)
242
+ while !state.proceed?
243
+ input = if @interface.command_queue.empty?
244
+ @interface.read_command(prompt(context))
245
+ else
246
+ @interface.command_queue.shift
247
+ end
248
+ break unless input
249
+ catch(:debug_error) do
250
+ if input == ""
251
+ next unless @last_cmd
252
+ input = @last_cmd
253
+ else
254
+ @last_cmd = input
255
+ end
256
+ splitter[input].each do |cmd|
257
+ one_cmd(commands, context, cmd)
258
+ postcmd(commands, context, cmd)
259
+ end
260
+ end
261
+ end
262
+ postloop(commands, context)
263
+ end # process_commands
264
+
265
+ def one_cmd(commands, context, input)
266
+ if cmd = commands.find{ |c| c.match(input) }
267
+ if context.dead? && cmd.class.need_context
268
+ p cmd
269
+ print "Command is unavailable\n"
270
+ else
271
+ cmd.execute
272
+ end
273
+ else
274
+ unknown_cmd = commands.find{ |c| c.class.unknown }
275
+ if unknown_cmd
276
+ unknown_cmd.execute
277
+ else
278
+ errmsg "Unknown command: \"#{input}\". Try \"help\".\n"
279
+ end
280
+ end
281
+ end
282
+
283
+ def preloop(commands, context)
284
+ aprint('stopped') if Byebug.annotate.to_i > 2
285
+ if context.dead?
286
+ unless @byebug_context_was_dead
287
+ if Byebug.annotate.to_i > 2
288
+ aprint('exited')
289
+ print "The program finished.\n"
290
+ end
291
+ @byebug_context_was_dead = true
292
+ end
293
+ end
294
+
295
+ if Byebug.annotate.to_i > 2
296
+ # if we are here, the stack frames have changed outside the command
297
+ # loop (e.g. after a "continue" command), so we show the annotations
298
+ # again
299
+ breakpoint_annotations(commands, context)
300
+ display_annotations(commands, context)
301
+ annotation('stack', commands, context, "where")
302
+ annotation('variables', commands, context, "info variables") unless
303
+ context.dead?
304
+ end
305
+ end
306
+
307
+ def postcmd(commands, context, cmd)
308
+ if Byebug.annotate.to_i > 2
309
+ cmd = @last_cmd unless cmd
310
+ breakpoint_annotations(commands, context) if
311
+ @@Show_breakpoints_postcmd.find{|pat| cmd =~ pat}
312
+ display_annotations(commands, context)
313
+ if @@Show_annotations_postcmd.find{|pat| cmd =~ pat}
314
+ annotation('stack', commands, context, "where") if
315
+ context.stack_size > 0
316
+ annotation('variables', commands, context, "info variables") unless
317
+ context.dead?
318
+ end
319
+ if not context.dead? and @@Show_annotations_run.find{|pat| cmd =~ pat}
320
+ aprint 'starting' if Byebug.annotate.to_i > 2
321
+
322
+ @byebug_context_was_dead = false
323
+ end
324
+ end
325
+ end
326
+
327
+ def postloop(commands, context)
328
+ end
329
+
330
+ def annotation(label, commands, context, cmd)
331
+ print afmt(label)
332
+ one_cmd(commands, context, cmd)
333
+ end
334
+
335
+ def breakpoint_annotations(commands, context)
336
+ unless Byebug.breakpoints.empty? and @byebug_breakpoints_were_empty
337
+ annotation('breakpoints', commands, context, "info breakpoints")
338
+ @byebug_breakpoints_were_empty = Byebug.breakpoints.empty?
339
+ end
340
+ end
341
+
342
+ def display_annotations(commands, context)
343
+ return if display.empty?
344
+ # have_display = display.find{|d| d[0]}
345
+ # return unless have_display and @byebug_displays_were_empty
346
+ # @byebug_displays_were_empty = have_display
347
+ annotation('display', commands, context, "display")
348
+ end
349
+
350
+ class State # :nodoc:
351
+ attr_accessor :binding, :commands, :context, :display, :file, :frame_pos
352
+ attr_accessor :interface, :line, :previous_line
353
+
354
+ def initialize
355
+ super()
356
+ @frame_pos = 0
357
+ @previous_line = nil
358
+ @proceed = false
359
+ yield self
360
+ end
361
+
362
+ # FIXME: use delegate?
363
+ def errmsg(*args)
364
+ @interface.errmsg(*args)
365
+ end
366
+
367
+ def print(*args)
368
+ @interface.print(*args)
369
+ end
370
+
371
+ def confirm(*args)
372
+ @interface.confirm(*args)
373
+ end
374
+
375
+ def proceed?
376
+ @proceed
377
+ end
378
+
379
+ def proceed
380
+ @proceed = true
381
+ end
382
+ end
383
+
384
+ end # end class CommandProcessor
385
+
386
+ class ControlCommandProcessor < Processor # :nodoc:
387
+ def initialize(interface)
388
+ super()
389
+ @interface = interface
390
+ @byebug_context_was_dead = true # Assume we haven't started.
391
+ end
392
+
393
+ def process_commands(verbose=false)
394
+ control_cmds = Command.commands.select do |cmd|
395
+ cmd.allow_in_control
396
+ end
397
+ state = State.new(@interface, control_cmds)
398
+ commands = control_cmds.map{|cmd| cmd.new(state) }
399
+
400
+ unless @byebug_context_was_dead
401
+ if Byebug.annotate.to_i > 2
402
+ aprint 'exited'
403
+ print "The program finished.\n"
404
+ end
405
+ @byebug_context_was_dead = true
406
+ end
407
+
408
+ while input = @interface.read_command(prompt(nil))
409
+ print "+#{input}" if verbose
410
+ catch(:debug_error) do
411
+ if cmd = commands.find{|c| c.match(input) }
412
+ cmd.execute
413
+ else
414
+ errmsg "Unknown command\n"
415
+ end
416
+ end
417
+ end
418
+ rescue IOError, Errno::EPIPE
419
+ rescue Exception
420
+ print "INTERNAL ERROR!!! #{$!}\n" rescue nil
421
+ print $!.backtrace.map{|l| "\t#{l}"}.join("\n") rescue nil
422
+ ensure
423
+ @interface.close
424
+ end
425
+
426
+ # The prompt shown before reading a command.
427
+ # Note: have an unused 'context' parameter to match the local interface.
428
+ def prompt(context)
429
+ p = '(rdb:ctrl) '
430
+ p = afmt("pre-prompt")+p+"\n"+afmt("prompt") if
431
+ Byebug.annotate.to_i > 2
432
+ return p
433
+ end
434
+
435
+ class State # :nodoc:
436
+ attr_reader :commands, :interface
437
+
438
+ def initialize(interface, commands)
439
+ @interface = interface
440
+ @commands = commands
441
+ end
442
+
443
+ def proceed
444
+ end
445
+
446
+ def errmsg(*args)
447
+ @interface.print(*args)
448
+ end
449
+
450
+ def print(*args)
451
+ @interface.print(*args)
452
+ end
453
+
454
+ def confirm(*args)
455
+ 'y'
456
+ end
457
+
458
+ def context
459
+ nil
460
+ end
461
+
462
+ def file
463
+ errmsg "No filename given.\n"
464
+ throw :debug_error
465
+ end
466
+ end # State
467
+ end
468
+ end