trepanning 0.1.0 → 0.1.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 (181) hide show
  1. data/.gitignore +4 -0
  2. data/ChangeLog +1279 -235
  3. data/Makefile +13 -0
  4. data/NEWS +30 -0
  5. data/Rakefile +50 -14
  6. data/app/.gitignore +1 -0
  7. data/app/breakpoint.rb +7 -2
  8. data/app/brkptmgr.rb +12 -0
  9. data/app/cmd_parse.citrus +167 -0
  10. data/app/cmd_parse.kpeg +221 -0
  11. data/app/cmd_parse.rb +201 -0
  12. data/app/cmd_parser.rb +1914 -0
  13. data/app/complete.rb +79 -0
  14. data/app/condition.rb +1 -1
  15. data/app/core.rb +7 -11
  16. data/app/default.rb +1 -1
  17. data/app/disassemble.rb +3 -2
  18. data/app/file.rb +12 -36
  19. data/app/frame.rb +3 -2
  20. data/app/irb.rb +9 -5
  21. data/app/iseq.rb +46 -0
  22. data/app/options.rb +6 -30
  23. data/app/run.rb +5 -2
  24. data/app/util.rb +1 -2
  25. data/app/yarv.rb +11 -1
  26. data/bin/.gitignore +1 -0
  27. data/bin/trepan +6 -6
  28. data/data/.gitignore +1 -0
  29. data/interface/.gitignore +1 -0
  30. data/interface/base_intf.rb +9 -5
  31. data/interface/comcodes.rb +10 -8
  32. data/interface/user.rb +76 -17
  33. data/io/.gitignore +1 -0
  34. data/io/input.rb +39 -15
  35. data/io/tcpclient.rb +7 -1
  36. data/io/tcpfns.rb +5 -3
  37. data/io/tcpserver.rb +13 -10
  38. data/lib/.gitignore +1 -0
  39. data/lib/trepanning.rb +50 -13
  40. data/processor/.gitignore +1 -0
  41. data/processor/Makefile +7 -0
  42. data/processor/breakpoint.rb +7 -2
  43. data/processor/command/.gitignore +1 -0
  44. data/processor/command/Makefile +7 -0
  45. data/processor/command/alias.rb +2 -2
  46. data/processor/command/backtrace.rb +4 -0
  47. data/processor/command/base/cmd.rb +45 -2
  48. data/processor/command/base/subcmd.rb +4 -2
  49. data/processor/command/base/submgr.rb +23 -19
  50. data/processor/command/base/subsubcmd.rb +23 -1
  51. data/processor/command/base/subsubmgr.rb +13 -0
  52. data/processor/command/break.rb +34 -29
  53. data/processor/command/complete.rb +37 -0
  54. data/processor/command/condition.rb +2 -0
  55. data/processor/command/continue.rb +15 -18
  56. data/processor/command/disassemble.rb +5 -0
  57. data/processor/command/down.rb +1 -1
  58. data/processor/command/eval.rb +70 -0
  59. data/processor/command/exit.rb +4 -1
  60. data/processor/command/finish.rb +6 -4
  61. data/processor/command/frame.rb +6 -3
  62. data/processor/command/help.rb +97 -54
  63. data/processor/command/help/.gitignore +1 -0
  64. data/processor/command/help/README +10 -0
  65. data/processor/command/help/command.txt +48 -0
  66. data/processor/command/help/filename.txt +40 -0
  67. data/processor/command/help/location.txt +37 -0
  68. data/processor/command/info_subcmd/.gitignore +1 -0
  69. data/processor/command/info_subcmd/breakpoints.rb +9 -9
  70. data/processor/command/info_subcmd/{file.rb → files.rb} +92 -27
  71. data/processor/command/info_subcmd/frame.rb +41 -15
  72. data/processor/command/info_subcmd/iseq.rb +39 -17
  73. data/processor/command/info_subcmd/program.rb +2 -8
  74. data/processor/command/info_subcmd/registers.rb +12 -10
  75. data/processor/command/info_subcmd/registers_subcmd/.gitignore +1 -0
  76. data/processor/command/info_subcmd/ruby.rb +60 -0
  77. data/processor/command/irb.rb +26 -3
  78. data/processor/command/kill.rb +21 -10
  79. data/processor/command/list.rb +1 -1
  80. data/processor/command/macro.rb +37 -23
  81. data/processor/command/pr.rb +1 -1
  82. data/processor/command/reload.rb +4 -0
  83. data/processor/command/reload_subcmd/.gitignore +1 -0
  84. data/processor/command/restart.rb +9 -9
  85. data/processor/command/save.rb +29 -36
  86. data/processor/command/set_subcmd/.gitignore +1 -0
  87. data/processor/command/set_subcmd/auto_subcmd/.gitignore +1 -0
  88. data/processor/command/set_subcmd/confirm.rb +23 -0
  89. data/processor/command/set_subcmd/debug_subcmd/.gitignore +1 -0
  90. data/processor/command/set_subcmd/different.rb +2 -0
  91. data/processor/command/set_subcmd/events.rb +2 -0
  92. data/processor/command/set_subcmd/max.rb +9 -12
  93. data/processor/command/set_subcmd/max_subcmd/.gitignore +1 -0
  94. data/processor/command/set_subcmd/substitute_subcmd/.gitignore +1 -0
  95. data/processor/command/set_subcmd/trace.rb +7 -13
  96. data/processor/command/set_subcmd/trace_subcmd/.gitignore +1 -0
  97. data/processor/command/set_subcmd/trace_subcmd/buffer.rb +12 -27
  98. data/processor/command/set_subcmd/trace_subcmd/print.rb +10 -8
  99. data/processor/command/set_subcmd/trace_subcmd/var.rb +6 -10
  100. data/processor/command/show.rb +12 -1
  101. data/processor/command/show_subcmd/.gitignore +1 -0
  102. data/processor/command/show_subcmd/alias.rb +11 -15
  103. data/processor/command/show_subcmd/auto_subcmd/.gitignore +1 -0
  104. data/processor/command/show_subcmd/basename.rb +1 -9
  105. data/processor/command/show_subcmd/confirm.rb +25 -0
  106. data/processor/command/show_subcmd/debug_subcmd/.gitignore +1 -0
  107. data/processor/command/show_subcmd/macro.rb +32 -14
  108. data/processor/command/show_subcmd/max_subcmd/.gitignore +1 -0
  109. data/processor/command/show_subcmd/trace_subcmd/.gitignore +1 -0
  110. data/processor/command/show_subcmd/trace_subcmd/buffer.rb +11 -31
  111. data/processor/command/show_subcmd/trace_subcmd/print.rb +4 -20
  112. data/processor/command/source.rb +7 -1
  113. data/processor/command/up.rb +7 -4
  114. data/processor/default.rb +3 -1
  115. data/processor/eval.rb +13 -0
  116. data/processor/eventbuf.rb +3 -2
  117. data/processor/frame.rb +19 -0
  118. data/processor/help.rb +20 -0
  119. data/processor/load_cmds.rb +143 -24
  120. data/processor/location.rb +61 -10
  121. data/processor/main.rb +30 -11
  122. data/processor/mock.rb +5 -3
  123. data/processor/msg.rb +17 -0
  124. data/processor/running.rb +1 -1
  125. data/processor/subcmd.rb +3 -2
  126. data/processor/validate.rb +173 -185
  127. data/sample/.gitignore +1 -0
  128. data/sample/list-terminal-colors.rb +139 -0
  129. data/sample/rocky-dot-trepanrc +14 -0
  130. data/sample/rocky-trepan-colors.rb +47 -0
  131. data/test/Makefile +7 -0
  132. data/test/data/.gitignore +1 -0
  133. data/test/data/debugger-stop.cmd +3 -0
  134. data/test/data/debugger-stop.right +5 -0
  135. data/test/data/fname-with-blank.right +0 -3
  136. data/test/data/quit.right +0 -1
  137. data/test/data/quit2.cmd +6 -0
  138. data/test/data/quit2.right +3 -0
  139. data/test/data/testing.cmd +1 -0
  140. data/test/example/.gitignore +1 -0
  141. data/test/example/debugger-stop.rb +14 -0
  142. data/test/functional/.gitignore +2 -0
  143. data/test/functional/fn_helper.rb +7 -9
  144. data/test/functional/test-break-long.rb +7 -7
  145. data/test/functional/test-break.rb +7 -7
  146. data/test/functional/test-condition.rb +4 -4
  147. data/test/functional/test-delete.rb +6 -5
  148. data/test/functional/test-eval.rb +115 -0
  149. data/test/functional/test-raise.rb +1 -1
  150. data/test/functional/test-return.rb +1 -1
  151. data/test/integration/.gitignore +2 -0
  152. data/test/integration/helper.rb +6 -3
  153. data/test/integration/test-debugger-stop.rb +22 -0
  154. data/test/integration/test-quit.rb +8 -0
  155. data/test/unit/.gitignore +1 -0
  156. data/test/unit/Makefile +7 -0
  157. data/test/unit/test-app-brkpt.rb +0 -1
  158. data/test/unit/test-app-cmd_parse.rb +107 -0
  159. data/test/unit/test-app-cmd_parser.rb +22 -0
  160. data/test/unit/test-app-complete.rb +38 -0
  161. data/test/unit/test-app-condition.rb +20 -0
  162. data/test/unit/test-app-iseq.rb +31 -0
  163. data/test/unit/test-app-options.rb +9 -1
  164. data/test/unit/test-app-util.rb +0 -1
  165. data/test/unit/test-base-cmd.rb +46 -0
  166. data/test/unit/test-base-subcmd.rb +11 -2
  167. data/test/unit/test-base-submgr.rb +23 -0
  168. data/test/unit/test-base-subsubcmd.rb +20 -0
  169. data/test/unit/test-cmd-break.rb +22 -23
  170. data/test/unit/test-cmd-help.rb +4 -0
  171. data/test/unit/test-completion.rb +43 -0
  172. data/test/unit/test-io-tcpclient.rb +3 -2
  173. data/test/unit/test-proc-load_cmds.rb +10 -1
  174. data/test/unit/test-proc-location.rb +39 -0
  175. data/test/unit/test-proc-main.rb +1 -1
  176. data/test/unit/test-proc-validate.rb +47 -31
  177. data/trepanning.gemspec +45 -0
  178. metadata +247 -179
  179. data/app/core.rb-consider +0 -198
  180. data/test/functional/tmp/b3.rb +0 -5
  181. data/test/functional/tmp/immediate-bug1.rb +0 -9
@@ -1,5 +1,6 @@
1
1
  # Copyright (C) 2010, 2011 Rocky Bernstein <rockyb@rubyforge.net>
2
2
  require 'linecache'
3
+ require 'pathname' # For cleanpath
3
4
  require_relative 'msg'
4
5
  require_relative '../app/frame'
5
6
  class Trepan
@@ -19,7 +20,18 @@ class Trepan
19
20
  # For now we want resolved filenames
20
21
  @settings[:basename] ? File.basename(filename) :
21
22
  # Cache this?
22
- Pathname.new(filename).cleanpath.to_s
23
+ File.expand_path(Pathname.new(filename).cleanpath.to_s)
24
+ end
25
+
26
+ # Return the text to the current source line.
27
+ # FIXME: loc_and_text should call this rather than the other
28
+ # way around.
29
+ def current_source_text
30
+ opts = {:reload_on_change => @reload_on_change}
31
+ junk1, junk2, text, found_line =
32
+ loc_and_text('', frame, frame.source_location[0],
33
+ frame.source_container, opts)
34
+ text
23
35
  end
24
36
 
25
37
  def resolve_file_with_dir(path_suffix)
@@ -41,11 +53,11 @@ class Trepan
41
53
 
42
54
  # Get line +line_number+ from file named +filename+. Return ''
43
55
  # if there was a problem. Leading blanks are stripped off.
44
- def line_at(filename, line_number) # :nodoc:
45
- opts = {
46
- :reload_on_change => @reload_on_change,
47
- :output => @settings[:highlight]
48
- }
56
+ def line_at(filename, line_number,
57
+ opts = {
58
+ :reload_on_change => @reload_on_change,
59
+ :output => @settings[:highlight]
60
+ })
49
61
  line = LineCache::getline(filename, line_number, opts)
50
62
 
51
63
  unless line
@@ -61,12 +73,16 @@ class Trepan
61
73
  line ? line.lstrip.chomp : line
62
74
  end
63
75
 
64
- def loc_and_text(loc, frame, line_no, source_container)
76
+ def loc_and_text(loc, frame, line_no, source_container,
77
+ opts = {
78
+ :reload_on_change => @reload_on_change,
79
+ :output => @settings[:highlight]
80
+ })
65
81
  found_line = true
66
82
  ## FIXME: condition is too long.
67
83
  if source_container[0] == 'string' && frame.iseq && frame.iseq.eval_source
68
84
  file = LineCache::map_iseq(frame.iseq)
69
- text = LineCache::getline(frame.iseq, line_no)
85
+ text = LineCache::getline(frame.iseq, line_no, opts)
70
86
  loc += " remapped #{canonic_file(file)}:#{line_no}"
71
87
  elsif source_container[0] != 'file'
72
88
  via = loc
@@ -78,7 +94,7 @@ class Trepan
78
94
  line_no = frame.source_location[0]
79
95
  filename = source_container[1]
80
96
  loc += " via #{canonic_file(filename)}:#{line_no}"
81
- text = line_at(filename, line_no)
97
+ text = line_at(filename, line_no, opts)
82
98
  found_line = false
83
99
  end
84
100
  else
@@ -88,7 +104,7 @@ class Trepan
88
104
  loc += " remapped #{canonic_file(map_file)}:#{map_line}"
89
105
  end
90
106
 
91
- text = line_at(container, line_no)
107
+ text = line_at(container, line_no, opts)
92
108
  end
93
109
  [loc, line_no, text, found_line]
94
110
  end
@@ -154,3 +170,38 @@ class Trepan
154
170
 
155
171
  end
156
172
  end
173
+
174
+ if __FILE__ == $0 && caller.size == 0 && ARGV.size > 0
175
+ # Demo it.
176
+ require 'thread_frame'
177
+ require_relative 'frame'
178
+ require_relative '../app/mock'
179
+ require_relative 'main' # Have to include before defining CmdProcessor!
180
+ # FIXME
181
+ class Trepan::CmdProcessor
182
+ def errmsg(msg)
183
+ puts msg
184
+ end
185
+ def print_location
186
+ puts "#{@frame.source_container} #{frame.source_location[0]}"
187
+ end
188
+ end
189
+
190
+ proc = Trepan::CmdProcessor.new(Trepan::MockCore.new())
191
+ proc.instance_variable_set('@settings', {})
192
+ proc.frame_initialize
193
+ proc.frame_setup(RubyVM::ThreadFrame.current)
194
+ proc.frame_initialize
195
+
196
+ proc.location_initialize
197
+ puts proc.canonic_file(__FILE__)
198
+ proc.instance_variable_set('@settings', {:basename => true})
199
+ puts proc.canonic_file(__FILE__)
200
+ puts proc.current_source_text
201
+ xx = eval <<-END
202
+ proc.frame_initialize
203
+ proc.frame_setup(RubyVM::ThreadFrame.current)
204
+ proc.location_initialize
205
+ proc.current_source_text
206
+ END
207
+ end
data/processor/main.rb CHANGED
@@ -3,7 +3,6 @@
3
3
  # command class and debugger command objects are pulled in from here.
4
4
 
5
5
  require 'set'
6
- require 'pathname' # For cleanpath
7
6
 
8
7
  %w(default breakpoint display eventbuf eval load_cmds location frame hook msg
9
8
  running validate).each do
@@ -33,6 +32,8 @@ class Trepan
33
32
  # next stop while settings is the default
34
33
  # value to use.
35
34
  attr_accessor :event # Stop event. Same as @core.event
35
+ attr_reader :intf # Current interface
36
+ # Trepan::Core instance)
36
37
  attr_accessor :leave_cmd_loop # Commands set this to signal to leave
37
38
  # the command loop (which often continues to
38
39
  # run the debugged program).
@@ -63,6 +64,8 @@ class Trepan
63
64
  'c-call' => 'C>',
64
65
  'c-return' => '<C',
65
66
  'call' => '->',
67
+ 'send' => '=>',
68
+ 'leave' => '<=',
66
69
  'class' => '::',
67
70
  'coverage' => '[]',
68
71
  'debugger-call' => ':o',
@@ -75,6 +78,7 @@ class Trepan
75
78
  'unknown' => '?!',
76
79
  'vm' => 'VM',
77
80
  'vm-insn' => '..',
81
+ 'yield' => '<>',
78
82
  }
79
83
  # These events are important enough event that we always want to
80
84
  # stop on them.
@@ -138,6 +142,10 @@ class Trepan
138
142
 
139
143
  end
140
144
 
145
+ def finalize
146
+ breakpoint_finalize
147
+ end
148
+
141
149
  # Check that we meed the criteria that cmd specifies it needs
142
150
  def ok_for_running(cmd, name, nargs)
143
151
  # TODO check execution_set against execution status.
@@ -170,13 +178,14 @@ class Trepan
170
178
  # Run one debugger command. True is returned if we want to quit.
171
179
  def process_command_and_quit?()
172
180
  intf_size = @dbgr.intf.size
173
- intf = @dbgr.intf[-1]
174
- return true if intf.input_eof? && intf_size == 1
175
- while intf_size > 1 || !intf.input_eof?
181
+ @intf = @dbgr.intf[-1]
182
+ return true if @intf.input_eof? && intf_size == 1
183
+ while intf_size > 1 || !@intf.input_eof?
176
184
  begin
177
185
  @current_command =
178
186
  if @cmd_queue.empty?
179
- read_command.strip
187
+ # Leave trailing blanks on for the "complete" command
188
+ read_command.chomp
180
189
  else
181
190
  @cmd_queue.shift
182
191
  end
@@ -193,7 +202,7 @@ class Trepan
193
202
  if intf_size > 1
194
203
  @dbgr.intf.pop
195
204
  intf_size = @dbgr.intf.size
196
- intf = @dbgr.intf[-1]
205
+ @intf = @dbgr.intf[-1]
197
206
  @last_command = nil
198
207
  print_location
199
208
  else
@@ -205,6 +214,9 @@ class Trepan
205
214
  end
206
215
  end
207
216
  run_command(@current_command)
217
+
218
+ # Save it to the history.
219
+ @intf.history_io.puts @last_command if @last_command && @intf.history_io
208
220
  end
209
221
 
210
222
  # This is the main entry point.
@@ -240,7 +252,12 @@ class Trepan
240
252
  @dbgr.stop
241
253
  raise
242
254
  rescue Exception => exc
243
- errmsg("Internal debugger error: #{exc.inspect}")
255
+ # If we are inside the script interface errmsg may fail.
256
+ begin
257
+ errmsg("Internal debugger error: #{exc.inspect}")
258
+ rescue
259
+ $stderr.puts "Internal debugger error: #{exc.inspect}"
260
+ end
244
261
  exception_dump(exc, @settings[:debugexcept], $!.backtrace)
245
262
  end
246
263
  end
@@ -269,12 +286,13 @@ class Trepan
269
286
  macro_cmd_name = args[0]
270
287
  return false if args.size == 0
271
288
  break unless @macros.member?(macro_cmd_name)
272
- current_command = @macros[macro_cmd_name].call(*args[1..-1])
273
- msg current_command if settings[:debugmacro]
274
- # FIXME: should handle nested Array as a new command.
289
+ current_command = @macros[macro_cmd_name][0].call(*args[1..-1])
290
+ msg current_command.inspect if settings[:debugmacro]
275
291
  if current_command.is_a?(Array) &&
276
292
  current_command.all? {|val| val.is_a?(String)}
277
- args = current_command
293
+ args = (first=current_command.shift).split
294
+ @cmd_queue += current_command
295
+ current_command = first
278
296
  elsif current_command.is_a?(String)
279
297
  args = current_command.split
280
298
  else
@@ -337,6 +355,7 @@ if __FILE__ == $0
337
355
  cmdproc.errmsg('Whoa!')
338
356
  cmds = cmdproc.commands
339
357
  p cmdproc.aliases
358
+ p cmdproc.commands.keys.sort
340
359
  cmd_name, cmd_obj = cmds.first
341
360
  puts cmd_obj.class.const_get(:HELP)
342
361
  puts cmd_obj.class.const_get(:SHORT_HELP)
data/processor/mock.rb CHANGED
@@ -70,13 +70,13 @@ module MockDebugger
70
70
  def cmd.confirm(prompt, default)
71
71
  true
72
72
  end
73
- def cmd.errmsg(message)
73
+ def cmd.errmsg(message, opts={})
74
74
  puts "Error: #{message}"
75
75
  end
76
- def cmd.msg(message)
76
+ def cmd.msg(message, opts={})
77
77
  puts message
78
78
  end
79
- def cmd.msg_nocr(message)
79
+ def cmd.msg_nocr(message, opts={})
80
80
  print message
81
81
  end
82
82
  def cmd.section(message, opts={})
@@ -91,6 +91,7 @@ module MockDebugger
91
91
  sub_name = sub_class.const_get('PREFIX')
92
92
  dbgr, cmd = setup(sub_name[0], false)
93
93
  cmd.proc.frame_setup(RubyVM::ThreadFrame::current.prev)
94
+ cmd.proc.event = 'debugger-call'
94
95
  sub_cmd = sub_class.new(cmd)
95
96
  sub_cmd.summary_help(sub_cmd.name)
96
97
  puts
@@ -103,6 +104,7 @@ module MockDebugger
103
104
  subsub_name = subsub_class.const_get('PREFIX')
104
105
  dbgr, cmd = setup(subsub_name[0], false)
105
106
  cmd.proc.frame_setup(RubyVM::ThreadFrame::current.prev)
107
+ cmd.proc.event = 'debugger-call'
106
108
  sub_cmd = sub_class.new(dbgr.core.processor, cmd)
107
109
  subsub_cmd = subsub_class.new(cmd.proc, sub_cmd, subsub_name.join(''))
108
110
  subsub_cmd.summary_help(subsub_cmd.name)
data/processor/msg.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require_relative '../app/util'
4
4
  class Trepan
5
5
  class CmdProcessor
6
+ attr_accessor :ruby_highlighter
7
+
6
8
  def errmsg(message, opts={})
7
9
  message = safe_rep(message) unless opts[:unlimited]
8
10
  if @settings[:highlight] && defined?(Term::ANSIColor)
@@ -26,6 +28,20 @@ class Trepan
26
28
  @dbgr.intf[-1].read_command(@prompt)
27
29
  end
28
30
 
31
+ def ruby_format(text)
32
+ return text unless settings[:highlight]
33
+ unless @ruby_highlighter
34
+ begin
35
+ require 'coderay'
36
+ require 'term/ansicolor'
37
+ @ruby_highlighter = CodeRay::Duo[:ruby, :term]
38
+ rescue LoadError
39
+ return text
40
+ end
41
+ end
42
+ return @ruby_highlighter.encode(text)
43
+ end
44
+
29
45
  def safe_rep(str)
30
46
  Util::safe_repr(str, @settings[:maxstring])
31
47
  end
@@ -38,5 +54,6 @@ class Trepan
38
54
  end
39
55
  @dbgr.intf[-1].msg(message)
40
56
  end
57
+
41
58
  end
42
59
  end
data/processor/running.rb CHANGED
@@ -33,7 +33,7 @@ class Trepan
33
33
  step(0, opts)
34
34
  @next_level = @frame.stack_size - level_count
35
35
  @next_thread = Thread.current
36
- @stop_events = Set.new(%w(return))
36
+ @stop_events = Set.new(%w(return leave yield))
37
37
 
38
38
  # Try high-speed (run-time-assisted) method
39
39
  @frame.trace_off = true # No more tracing in this frame
data/processor/subcmd.rb CHANGED
@@ -1,10 +1,11 @@
1
- # Copyright (C) 2010 Rocky Bernstein <rockyb@rubyforge.net>
1
+ # Copyright (C) 2010, 2011 Rocky Bernstein <rockyb@rubyforge.net>
2
2
  # gdb-like subcommand processing.
3
3
 
4
4
  class Trepan
5
5
  class Subcmd
6
6
 
7
- attr_reader :subcmds
7
+ attr_reader :subcmds # Hash of subcommands. Key is the subcommand name.
8
+ # the value is the subcommand object to run.
8
9
  def initialize(cmd)
9
10
  @cmd = cmd
10
11
  @subcmds = {}
@@ -1,10 +1,11 @@
1
- # Copyright (C) 2010 Rocky Bernstein <rockyb@rubyforge.net>
1
+ # Copyright (C) 2010, 2011 Rocky Bernstein <rockyb@rubyforge.net>
2
2
 
3
3
  # Trepan command input validation routines. A String type is
4
4
  # usually passed in as the argument to validation routines.
5
5
 
6
6
  require_relative '../app/condition'
7
7
  require_relative '../app/file'
8
+ require_relative '../app/cmd_parse'
8
9
  require_relative '../app/thread'
9
10
 
10
11
  require_relative 'location' # for resolve_file_with_dir
@@ -21,7 +22,7 @@ class Trepan
21
22
  include Trepan::Condition
22
23
 
23
24
  def confirm(msg, default)
24
- @dbgr.intf[-1].confirm(msg, default)
25
+ @settings[:confirm] ? @dbgr.intf[-1].confirm(msg, default) : true
25
26
  end
26
27
 
27
28
  # Like cmdfns.get_an_int(), but if there's a stack frame use that
@@ -74,10 +75,10 @@ class Trepan
74
75
  end
75
76
 
76
77
  if val < opts[:min_value]
77
- if cmdname
78
+ if opts[:cmdname]
78
79
  errmsg(("Command '%s' expects an integer at least" +
79
80
  ' %d; got: %d.') %
80
- [cmdname, opts[:min_value], opts[:default]])
81
+ [opts[:cmdname], opts[:min_value], opts[:default]])
81
82
  else
82
83
  errmsg(("Expecting a positive integer at least" +
83
84
  ' %d; got: %d') %
@@ -135,92 +136,101 @@ class Trepan
135
136
  # FIXME: do something if there is more than one.
136
137
  if iseqs.size == 1
137
138
  iseqs[-1]
138
- elsif debug_eval_no_errmsg("#{object_string}.respond_to?('iseq')")
139
- debug_eval_no_errmsg("#{object_string}.iseq")
139
+ elsif meth = method?(object_string)
140
+ meth.iseq
140
141
  else
141
- parts = object_string.split(/[.]/)
142
- string =
143
- if parts.size < 2
144
- "method(\"#{object_string}\").iseq"
145
- else
146
- parts[0..-2].join('.')+".method(\"#{parts[-1]}\").iseq"
147
- end
148
- debug_eval_no_errmsg(string)
142
+ nil
149
143
  end
150
144
  rescue
151
145
  nil
152
146
  end
153
147
 
154
- def parse_num_or_offset(position_str)
155
- opts = {
156
- :msg_on_error =>
157
- "argument '%s' does not seem to eval to a method or an integer." %
158
- position_str,
159
- :min_value => 0
160
- }
161
- use_offset =
162
- if position_str.size > 0 && position_str[0].downcase == 'o'
163
- position_str[0] = ''
164
- true
148
+ def position_to_line_and_offset(iseq, filename, position, offset_type)
149
+ case offset_type
150
+ when :line
151
+ if ary = iseq.lineoffsets[position]
152
+ # Normally the first offset is a trace instruction and doesn't
153
+ # register as the given line, so we need to take the next instruction
154
+ # after the first one, when available.
155
+ vm_offset = ary.size > 1 ? ary[1] : ary[0]
156
+ line_no = position
157
+ elsif found_iseq = find_iseqs_with_lineno(filename, position)
158
+ return position_to_line_and_offset(found_iseq, filename, position,
159
+ offset_type)
160
+ elsif found_iseq = find_iseq_with_line_from_iseq(iseq, position)
161
+ return position_to_line_and_offset(found_iseq, filename, position,
162
+ offset_type)
165
163
  else
166
- false
164
+ errmsg("Unable to find offset for line #{position}\n\t" +
165
+ "in #{iseq.name} of file #{filename}")
166
+ return [nil, nil]
167
167
  end
168
- position = get_an_int(position_str, opts)
169
- [position, use_offset]
168
+ when :offset
169
+ if ary=iseq.offset2lines(position)
170
+ line_no = ary.first
171
+ vm_offset = position
172
+ else
173
+ errmsg "Unable to find line for offset #{position} in #{iseq}"
174
+ return [nil, nil]
175
+ end
176
+ when nil
177
+ vm_offset = 0
178
+ line_no = iseq.offset2lines(vm_offset).first
179
+ else
180
+ errmsg "Bad parse offset_type: #{offset_type.inspect}"
181
+ return [nil, nil]
182
+ end
183
+ return [iseq, line_no, vm_offset]
170
184
  end
171
185
 
172
- # Parse a breakpoint position. Return
173
- # - the position - a Fixnum
174
- # - the instruction sequence to use
175
- # - whether the position is an offset or a line number
176
- # - the condition (by default 'true') to use for this breakpoint
177
- def breakpoint_position(args)
178
- first = args.shift
179
- name, container, position = parse_position(first, nil, true)
180
- if container && position
181
- iseq = find_iseqs_with_lineno(container[1], position) || object_iseq(first)
182
- unless iseq
183
- if @frame.iseq &&
184
- File.basename(@frame.iseq.source_container[1]) ==
185
- File.basename(container[1])
186
- iseq = @frame.iseq
187
- else
188
- errmsg("Unable to find instruction sequence for" +
189
- " position #{position} in #{container[1]}")
190
- return [nil, nil, nil, true]
186
+ # Parse a breakpoint position. On success return:
187
+ # - the instruction sequence to use
188
+ # - the line number - a Fixnum
189
+ # - vm_offset - a Fixnum
190
+ # - the condition (by default 'true') to use for this breakpoint
191
+ # - true condition should be negated. Used in *condition* if/unless
192
+ def breakpoint_position(position_str, allow_condition)
193
+ break_cmd_parse = if allow_condition
194
+ parse_breakpoint(position_str)
195
+ else
196
+ parse_breakpoint_no_condition(position_str)
197
+ end
198
+ return [nil] * 5 unless break_cmd_parse
199
+ tail = [break_cmd_parse.condition, break_cmd_parse.negate]
200
+ meth_or_frame, file, position, offset_type =
201
+ parse_position(break_cmd_parse.position)
202
+ if meth_or_frame
203
+ if iseq = meth_or_frame.iseq
204
+ iseq, line_no, vm_offset =
205
+ position_to_line_and_offset(iseq, file, position, offset_type)
206
+ if vm_offset && line_no
207
+ return [iseq, line_no, vm_offset] + tail
191
208
  end
192
- end
193
- if args.empty? || 'if' == args[0]
194
- use_offset = false
195
209
  else
196
- position, use_offset = parse_num_or_offset(args[0])
210
+ errmsg("Unable to set breakpoint in #{meth_or_frame}")
197
211
  end
198
- else
199
- iseq = object_iseq(first)
200
- position_str =
212
+ elsif file && position
213
+ if :line == offset_type
214
+ iseq = find_iseqs_with_lineno(file, position)
201
215
  if iseq
202
- # Got name and possibly position
203
- name = first
204
- if args.empty?
205
- # FIXME: *Still* have a bug stopping at offset 0.
206
- # So stop at next offset after 0.
207
- # 'o0'
208
- "o#{@frame.iseq.offsetlines.keys.sort[1]}"
209
- else
210
- args.shift
211
- end
216
+ junk, line_no, vm_offset =
217
+ position_to_line_and_offset(iseq, file, position, offset_type)
218
+ return [@frame.iseq, line_no, vm_offset] + tail
212
219
  else
213
- iseq = @frame.iseq unless container
214
- first
220
+ errmsg("Unable to find instruction sequence for" +
221
+ " position #{position} in #{file}")
215
222
  end
216
- position, use_offset = parse_num_or_offset(position_str)
217
- end
218
- condition = 'true'
219
- if args.size > 0 && 'if' == args[0]
220
- condition_try = args[1..-1].join(' ')
221
- condition = condition_try if valid_condition?(condition_try)
223
+ else
224
+ errmsg "Come back later..."
225
+ end
226
+ elsif @frame.iseq.source_container[1] == file
227
+ line_no, vm_offset = position_to_line_and_offset(@frame.iseq, position,
228
+ offset_type)
229
+ return [@frame.iseq, line_no, vm_offset] + tail
230
+ else
231
+ errmsg("Unable to parse breakpoint position #{position_str}")
222
232
  end
223
- return [position, iseq, use_offset, condition, name]
233
+ return [nil] * 5
224
234
  end
225
235
 
226
236
  # Return true if arg is 'on' or 1 and false arg is 'off' or 0.
@@ -244,101 +254,76 @@ class Trepan
244
254
  raise TypeError
245
255
  end
246
256
 
247
- def method?(method_string)
248
- obj, type, meth =
249
- if method_string =~ /(.+)(#|::|\.)(.+)/
250
- [$1, $2, $3]
251
- else
252
- ['self', '.', method_string]
253
- end
254
- ret = debug_eval_no_errmsg("#{obj}.method(#{meth.inspect})")
255
- return true if ret
256
- return debug_eval_no_errmsg("#{obj}.is_a?(Class)") &&
257
- debug_eval_no_errmsg("#{obj}.method_defined?(#{meth.inspect})")
258
- end
257
+ include CmdParser
259
258
 
260
- # parse_position(self, arg)->(fn, container, lineno)
261
- #
262
- # Parse arg as [filename:]lineno | function | module
263
- # Make sure it works for C:\foo\bar.py:12
264
- def parse_position(arg, old_mod=nil, allow_offset = false)
265
- colon = arg.rindex(':')
266
- if colon
267
- # First handle part before the colon
268
- arg1 = arg[0...colon].rstrip
269
- lineno_str = arg[colon+1..-1].lstrip
270
- mf, container, lineno = parse_position_one_arg(arg1, old_mod, false, allow_offset)
271
- return nil, nil, nil unless container
272
- filename = canonic_file(arg1)
273
- # Next handle part after the colon
274
- val = get_an_int(lineno_str)
275
- lineno = val if val
259
+ def get_method(meth)
260
+ start_binding =
261
+ begin
262
+ @frame.binding
263
+ rescue
264
+ binding
265
+ end
266
+ if meth.kind_of?(String)
267
+ meth_for_string(meth, start_binding)
276
268
  else
277
- mf, container, lineno = parse_position_one_arg(arg, old_mod, true, allow_offset)
269
+ begin
270
+ meth_for_parse_struct(meth, start_binding)
271
+ rescue NameError
272
+ errmsg("Can't evalute #{meth.name} to get a method")
273
+ return nil
274
+ end
278
275
  end
279
-
280
- return mf, container, lineno
281
276
  end
282
277
 
283
- # parse_position_one_arg(self,arg)->(module/function, container, lineno)
284
- #
285
- # See if arg is a line number, function name, or module name.
286
- # Return what we've found. nil can be returned as a value in
287
- # the triple.
288
- def parse_position_one_arg(arg, old_mod=nil, show_errmsg=true, allow_offset=false)
289
- name, filename = nil, nil, nil
290
- begin
291
- # First see if argument is an integer
292
- lineno = Integer(arg)
293
- rescue
294
- else
295
- container = frame_container(@frame, false)
296
- filename = container[1] unless old_mod
297
- return nil, [container[0], canonic_file(filename)], lineno
298
- end
278
+ # FIXME: this is a ? method but we return
279
+ # the method value.
280
+ def method?(meth)
281
+ get_method(meth)
282
+ end
299
283
 
300
- # Next see if argument is a file name
301
- found =
302
- if arg[0..0] == File::SEPARATOR
303
- LineCache::cached?(arg)
284
+ # parse_position(self, arg)->(meth, filename, offset, offset_type)
285
+ # See app/cmd_parser.kpeg for the syntax of a position which
286
+ # should include things like:
287
+ # Parse arg as [filename:]lineno | function | module
288
+ # Make sure it works for C:\foo\bar.py:12
289
+ def parse_position(info)
290
+ info = parse_location(info) if info.kind_of?(String)
291
+ case info.container_type
292
+ when :fn
293
+ if meth = method?(info.container)
294
+ return [meth, meth.iseq.source_container[1], info.position,
295
+ info.position_type]
304
296
  else
305
- resolve_file_with_dir(arg)
297
+ return [nil] * 4
306
298
  end
307
- if found
308
- return nil, [container && container[0], canonic_file(arg)], 1
309
- else
310
- matches = find_scripts(arg)
311
- if matches.size > 1
312
- if show_errmsg
313
- errmsg "#{arg} is matches several files:"
314
- errmsg Columnize::columnize(matches.sort,
315
- @settings[:width], ' ' * 4,
316
- true, true, ' ' * 2).chomp
317
- end
318
- return nil, nil, nil
319
- elsif matches.size == 1
320
- LineCache::cache(matches[0])
321
- return nil, ['file', matches[0]], 1
322
- end
323
- end
324
-
325
- # How about a method name with an instruction sequence?
326
- iseq = object_iseq(arg)
327
- if iseq && iseq.source_container[0] == 'file'
328
- filename = iseq.source_container[1]
329
- line_no = iseq.offsetlines.values.flatten.min
330
- return arg, ['file', canonic_file(filename)], line_no
331
- end
332
-
333
- if show_errmsg
334
- unless (allow_offset && arg.size > 0 && arg[0].downcase == 'o')
335
- errmsg("#{arg} is not a line number, read-in filename or method " +
336
- "we can get location information about")
299
+ when :file
300
+ filename = canonic_file(info.container)
301
+ # ?? Try to look up method here?
302
+ container = frame_container(@frame, false)
303
+ try_filename = container[1]
304
+ frame = (canonic_file(try_filename) == filename) ? @frame : nil
305
+ # else
306
+ # LineCache.compiled_method(filename)
307
+ # end
308
+ return frame, filename, info.position, info.position_type
309
+ when nil
310
+ if [:line, :offset].member?(info.position_type)
311
+ container = frame_container(@frame, false)
312
+ filename = container[1]
313
+ return @frame, canonic_file(filename), info.position, info.position_type
314
+ elsif !info.position_type
315
+ errmsg "Can't parse #{arg} as a position"
316
+ return [nil] * 4
317
+ else
318
+ errmsg "Unknown position type #{info.position_type} for location #{arg}"
319
+ return [nil] * 4
337
320
  end
321
+ else
322
+ errmsg "Unknown container type #{info.container_type} for location #{arg}"
323
+ return [nil] * 4
338
324
  end
339
- return nil, nil, nil
340
325
  end
341
-
326
+
342
327
  def validate_initialize
343
328
  ## top_srcdir = File.expand_path(File.join(File.dirname(__FILE__), '..'))
344
329
  ## @dbgr_script_iseqs, @dbgr_iseqs = filter_scripts(top_srcdir)
@@ -359,52 +344,55 @@ if __FILE__ == $0
359
344
  require_relative 'main' # Have to include before defining CmdProcessor!
360
345
  # FIXME
361
346
 
362
- proc = Trepan::CmdProcessor.new(Trepan::MockCore.new())
363
- proc.frame_initialize
364
- proc.instance_variable_set('@settings',
347
+ cmdproc = Trepan::CmdProcessor.new(Trepan::MockCore.new())
348
+ cmdproc.frame_initialize
349
+ cmdproc.instance_variable_set('@settings',
365
350
  Trepan::CmdProcessor::DEFAULT_SETTINGS)
366
- proc.frame_setup(RubyVM::ThreadFrame.current)
351
+ cmdproc.frame_setup(RubyVM::ThreadFrame.current)
367
352
  onoff = %w(1 0 on off)
368
- onoff.each { |val| puts "onoff(#{val}) = #{proc.get_onoff(val)}" }
353
+ onoff.each { |val| puts "onoff(#{val}) = #{cmdproc.get_onoff(val)}" }
369
354
  %w(1 1E bad 1+1 -5).each do |val|
370
- puts "get_int_noerr(#{val}) = #{proc.get_int_noerr(val).inspect}"
355
+ puts "get_int_noerr(#{val}) = #{cmdproc.get_int_noerr(val).inspect}"
371
356
  end
372
357
  def foo; 5 end
373
- def proc.errmsg(msg)
358
+ def cmdproc.errmsg(msg)
374
359
  puts msg
375
360
  end
376
- puts proc.object_iseq('food').inspect
377
- puts proc.object_iseq('foo').inspect
361
+ # puts cmdproc.object_iseq('food').inspect
362
+ # puts cmdproc.object_iseq('foo').inspect
378
363
 
379
- puts proc.object_iseq('foo@validate.rb').inspect
380
- puts proc.object_iseq('proc.object_iseq').inspect
364
+ # puts cmdproc.object_iseq('foo@validate.rb').inspect
365
+ # puts cmdproc.object_iseq('cmdproc.object_iseq').inspect
381
366
 
382
- puts proc.parse_position_one_arg('tmpdir.rb').inspect
367
+ puts cmdproc.parse_position(__FILE__).inspect
368
+ puts cmdproc.parse_position('@8').inspect
369
+ puts cmdproc.parse_position('8').inspect
370
+ puts cmdproc.parse_position("#{__FILE__} #{__LINE__}").inspect
383
371
 
384
372
  puts '=' * 40
385
- ['Array#map', 'Trepan::CmdProcessor.new',
386
- 'foo', 'proc.errmsg'].each do |str|
387
- puts "#{str} should be true: #{proc.method?(str).inspect}"
373
+ ['Array.map', 'Trepan::CmdProcessor.new',
374
+ 'foo', 'cmdproc.errmsg'].each do |str|
375
+ puts "#{str} should be method: #{!!cmdproc.method?(str)}"
388
376
  end
389
377
  puts '=' * 40
390
- # require_relative '../lib/trepanning'
391
- # dbgr = Trepan.new
392
- # dbgr.debugger
393
378
 
394
379
  # FIXME:
395
- # Array#foo should be false: true
396
- # Trepan::CmdProcessor.allocate should be false: true
380
+ puts "Trepan::CmdProcessor.allocate is: #{cmdproc.get_method('Trepan::CmdProcessor.allocate')}"
397
381
 
398
382
  ['food', '.errmsg'].each do |str|
399
- puts "#{str} should be false: #{proc.method?(str).inspect}"
383
+ puts "#{str} should be false: #{cmdproc.method?(str).to_s}"
400
384
  end
401
385
  puts '-' * 20
402
- p proc.breakpoint_position(%w(O0))
403
- p proc.breakpoint_position(%w(1))
404
- p proc.breakpoint_position(%w(2 if a > b))
405
- p proc.get_int_list(%w(1+0 3-1 3))
406
- require 'trepanning'
407
- debugger
408
- p proc.get_int_list(%w(a 2 3))
386
+ p cmdproc.breakpoint_position('foo', true)
387
+ p cmdproc.breakpoint_position('@0', true)
388
+ p cmdproc.breakpoint_position("#{__LINE__}", true)
389
+ p cmdproc.breakpoint_position("#{__FILE__} @0", false)
390
+ p cmdproc.breakpoint_position("#{__FILE__}:#{__LINE__}", true)
391
+ p cmdproc.breakpoint_position("#{__FILE__} #{__LINE__} if 1 == a", true)
392
+ p cmdproc.breakpoint_position("cmdproc.errmsg", false)
393
+ p cmdproc.breakpoint_position("cmdproc.errmsg:@0", false)
394
+ ### p cmdproc.breakpoint_position(%w(2 if a > b))
395
+ p cmdproc.get_int_list(%w(1+0 3-1 3))
396
+ p cmdproc.get_int_list(%w(a 2 3))
409
397
  end
410
398
  end