rb8-trepanning 0.1.3

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 (274) hide show
  1. data/.gitignore +3 -0
  2. data/CHANGES +34 -0
  3. data/ChangeLog +875 -0
  4. data/README.textile +59 -0
  5. data/Rakefile +215 -0
  6. data/app/.gitignore +1 -0
  7. data/app/cmd_parse.kpeg +241 -0
  8. data/app/cmd_parse.rb +212 -0
  9. data/app/cmd_parser.rb +1948 -0
  10. data/app/complete.rb +79 -0
  11. data/app/default.rb +90 -0
  12. data/app/display.rb +148 -0
  13. data/app/eventbuffer.rb +147 -0
  14. data/app/frame.rb +166 -0
  15. data/app/irb.rb +114 -0
  16. data/app/options.rb +200 -0
  17. data/app/run.rb +74 -0
  18. data/app/util.rb +65 -0
  19. data/bin/.gitignore +1 -0
  20. data/bin/trepan8 +115 -0
  21. data/data/.gitignore +1 -0
  22. data/data/irbrc +41 -0
  23. data/interface/.gitignore +1 -0
  24. data/interface/base_intf.rb +109 -0
  25. data/interface/client.rb +82 -0
  26. data/interface/comcodes.rb +20 -0
  27. data/interface/script.rb +110 -0
  28. data/interface/server.rb +147 -0
  29. data/interface/user.rb +165 -0
  30. data/io/base_io.rb +148 -0
  31. data/io/input.rb +158 -0
  32. data/io/null_output.rb +46 -0
  33. data/io/string_array.rb +156 -0
  34. data/io/tcpclient.rb +129 -0
  35. data/io/tcpfns.rb +33 -0
  36. data/io/tcpserver.rb +141 -0
  37. data/lib/debugger.rb +8 -0
  38. data/lib/trepanning.rb +283 -0
  39. data/processor/.gitignore +1 -0
  40. data/processor/command-ruby-debug/breakpoints.rb +155 -0
  41. data/processor/command-ruby-debug/catchpoint.rb +55 -0
  42. data/processor/command-ruby-debug/condition.rb +49 -0
  43. data/processor/command-ruby-debug/control.rb +31 -0
  44. data/processor/command-ruby-debug/display.rb +120 -0
  45. data/processor/command-ruby-debug/enable.rb +202 -0
  46. data/processor/command-ruby-debug/frame.rb +199 -0
  47. data/processor/command-ruby-debug/help.rb +63 -0
  48. data/processor/command-ruby-debug/info.rb +359 -0
  49. data/processor/command-ruby-debug/method.rb +84 -0
  50. data/processor/command-ruby-debug/reload.rb +40 -0
  51. data/processor/command-ruby-debug/save.rb +90 -0
  52. data/processor/command-ruby-debug/set.rb +237 -0
  53. data/processor/command-ruby-debug/show.rb +251 -0
  54. data/processor/command-ruby-debug/source.rb +36 -0
  55. data/processor/command-ruby-debug/threads.rb +189 -0
  56. data/processor/command-ruby-debug/trace.rb +57 -0
  57. data/processor/command-ruby-debug/variables.rb +199 -0
  58. data/processor/command.rb +270 -0
  59. data/processor/command/.gitignore +1 -0
  60. data/processor/command/alias.rb +54 -0
  61. data/processor/command/backtrace.rb +123 -0
  62. data/processor/command/base/cmd.rb +177 -0
  63. data/processor/command/base/subcmd.rb +230 -0
  64. data/processor/command/base/submgr.rb +188 -0
  65. data/processor/command/base/subsubcmd.rb +128 -0
  66. data/processor/command/base/subsubmgr.rb +199 -0
  67. data/processor/command/break.rb +114 -0
  68. data/processor/command/catch.rb +71 -0
  69. data/processor/command/complete.rb +39 -0
  70. data/processor/command/continue.rb +57 -0
  71. data/processor/command/directory.rb +50 -0
  72. data/processor/command/disable.rb +85 -0
  73. data/processor/command/display.rb +78 -0
  74. data/processor/command/down.rb +54 -0
  75. data/processor/command/edit.rb +79 -0
  76. data/processor/command/enable.rb +48 -0
  77. data/processor/command/eval.rb +90 -0
  78. data/processor/command/exit.rb +66 -0
  79. data/processor/command/finish.rb +59 -0
  80. data/processor/command/frame.rb +97 -0
  81. data/processor/command/help.rb +230 -0
  82. data/processor/command/help/.gitignore +1 -0
  83. data/processor/command/help/README +10 -0
  84. data/processor/command/help/command.txt +58 -0
  85. data/processor/command/help/examples.txt +16 -0
  86. data/processor/command/help/filename.txt +40 -0
  87. data/processor/command/help/location.txt +37 -0
  88. data/processor/command/help/suffixes.txt +17 -0
  89. data/processor/command/info.rb +28 -0
  90. data/processor/command/info_subcmd/.gitignore +1 -0
  91. data/processor/command/info_subcmd/args.rb +39 -0
  92. data/processor/command/info_subcmd/breakpoints.rb +80 -0
  93. data/processor/command/info_subcmd/catch.rb +36 -0
  94. data/processor/command/info_subcmd/files.rb +39 -0
  95. data/processor/command/info_subcmd/globals.rb +64 -0
  96. data/processor/command/info_subcmd/line.rb +30 -0
  97. data/processor/command/info_subcmd/locals.rb +69 -0
  98. data/processor/command/info_subcmd/macro.rb +62 -0
  99. data/processor/command/info_subcmd/program.rb +51 -0
  100. data/processor/command/info_subcmd/ruby.rb +57 -0
  101. data/processor/command/info_subcmd/source.rb +74 -0
  102. data/processor/command/info_subcmd/stack.rb +25 -0
  103. data/processor/command/info_subcmd/threads.rb +75 -0
  104. data/processor/command/kill.rb +78 -0
  105. data/processor/command/list.rb +117 -0
  106. data/processor/command/macro.rb +68 -0
  107. data/processor/command/next.rb +79 -0
  108. data/processor/command/parsetree.rb +56 -0
  109. data/processor/command/pp.rb +40 -0
  110. data/processor/command/pr.rb +37 -0
  111. data/processor/command/ps.rb +40 -0
  112. data/processor/command/restart.rb +86 -0
  113. data/processor/command/save.rb +58 -0
  114. data/processor/command/set.rb +47 -0
  115. data/processor/command/set_subcmd/.gitignore +1 -0
  116. data/processor/command/set_subcmd/abbrev.rb +25 -0
  117. data/processor/command/set_subcmd/auto.rb +27 -0
  118. data/processor/command/set_subcmd/auto_subcmd/.gitignore +1 -0
  119. data/processor/command/set_subcmd/auto_subcmd/eval.rb +53 -0
  120. data/processor/command/set_subcmd/auto_subcmd/irb.rb +33 -0
  121. data/processor/command/set_subcmd/auto_subcmd/list.rb +33 -0
  122. data/processor/command/set_subcmd/basename.rb +25 -0
  123. data/processor/command/set_subcmd/callstyle.rb +46 -0
  124. data/processor/command/set_subcmd/confirm.rb +24 -0
  125. data/processor/command/set_subcmd/debug.rb +47 -0
  126. data/processor/command/set_subcmd/different.rb +61 -0
  127. data/processor/command/set_subcmd/highlight.rb +43 -0
  128. data/processor/command/set_subcmd/max.rb +26 -0
  129. data/processor/command/set_subcmd/max_subcmd/list.rb +49 -0
  130. data/processor/command/set_subcmd/max_subcmd/stack.rb +50 -0
  131. data/processor/command/set_subcmd/max_subcmd/string.rb +76 -0
  132. data/processor/command/set_subcmd/max_subcmd/width.rb +49 -0
  133. data/processor/command/set_subcmd/reload.rb +42 -0
  134. data/processor/command/set_subcmd/timer.rb +58 -0
  135. data/processor/command/set_subcmd/trace.rb +37 -0
  136. data/processor/command/set_subcmd/trace_subcmd/buffer.rb +42 -0
  137. data/processor/command/set_subcmd/trace_subcmd/print.rb +41 -0
  138. data/processor/command/shell.rb +139 -0
  139. data/processor/command/show.rb +39 -0
  140. data/processor/command/show_subcmd/.gitignore +1 -0
  141. data/processor/command/show_subcmd/abbrev.rb +20 -0
  142. data/processor/command/show_subcmd/alias.rb +46 -0
  143. data/processor/command/show_subcmd/args.rb +34 -0
  144. data/processor/command/show_subcmd/auto.rb +28 -0
  145. data/processor/command/show_subcmd/auto_subcmd/eval.rb +27 -0
  146. data/processor/command/show_subcmd/auto_subcmd/irb.rb +23 -0
  147. data/processor/command/show_subcmd/auto_subcmd/list.rb +22 -0
  148. data/processor/command/show_subcmd/basename.rb +20 -0
  149. data/processor/command/show_subcmd/callstyle.rb +22 -0
  150. data/processor/command/show_subcmd/confirm.rb +18 -0
  151. data/processor/command/show_subcmd/debug.rb +26 -0
  152. data/processor/command/show_subcmd/debug_subcmd/dbgr.rb +21 -0
  153. data/processor/command/show_subcmd/debug_subcmd/skip.rb +22 -0
  154. data/processor/command/show_subcmd/debug_subcmd/step.rb +22 -0
  155. data/processor/command/show_subcmd/different.rb +26 -0
  156. data/processor/command/show_subcmd/directories.rb +22 -0
  157. data/processor/command/show_subcmd/highlight.rb +24 -0
  158. data/processor/command/show_subcmd/max.rb +27 -0
  159. data/processor/command/show_subcmd/max_subcmd/list.rb +38 -0
  160. data/processor/command/show_subcmd/max_subcmd/stack.rb +36 -0
  161. data/processor/command/show_subcmd/max_subcmd/string.rb +42 -0
  162. data/processor/command/show_subcmd/max_subcmd/width.rb +37 -0
  163. data/processor/command/show_subcmd/reload.rb +18 -0
  164. data/processor/command/show_subcmd/timer.rb +18 -0
  165. data/processor/command/show_subcmd/trace.rb +29 -0
  166. data/processor/command/show_subcmd/trace_subcmd/buffer.rb +65 -0
  167. data/processor/command/show_subcmd/trace_subcmd/print.rb +23 -0
  168. data/processor/command/show_subcmd/version.rb +23 -0
  169. data/processor/command/source.rb +134 -0
  170. data/processor/command/step.rb +81 -0
  171. data/processor/command/tbreak.rb +19 -0
  172. data/processor/command/unalias.rb +44 -0
  173. data/processor/command/undisplay.rb +59 -0
  174. data/processor/command/up.rb +72 -0
  175. data/processor/default.rb +56 -0
  176. data/processor/display.rb +17 -0
  177. data/processor/eval.rb +113 -0
  178. data/processor/eventbuf.rb +105 -0
  179. data/processor/frame.rb +172 -0
  180. data/processor/help.rb +92 -0
  181. data/processor/helper.rb +76 -0
  182. data/processor/hook.rb +134 -0
  183. data/processor/load_cmds.rb +258 -0
  184. data/processor/location.rb +174 -0
  185. data/processor/main.rb +455 -0
  186. data/processor/mock.rb +136 -0
  187. data/processor/msg.rb +61 -0
  188. data/processor/processor.rb +674 -0
  189. data/processor/running.rb +168 -0
  190. data/processor/stepping.rb +18 -0
  191. data/processor/subcmd.rb +161 -0
  192. data/processor/validate.rb +355 -0
  193. data/processor/virtual.rb +34 -0
  194. data/test/data/.gitignore +1 -0
  195. data/test/data/break_bad.cmd +19 -0
  196. data/test/data/break_bad.right +29 -0
  197. data/test/data/break_loop_bug.cmd +5 -0
  198. data/test/data/break_loop_bug.right +15 -0
  199. data/test/data/dollar-0.right +2 -0
  200. data/test/data/dollar-0a.right +2 -0
  201. data/test/data/dollar-0b.right +2 -0
  202. data/test/data/edit.cmd +14 -0
  203. data/test/data/edit.right +24 -0
  204. data/test/data/file-with-space.cmd +6 -0
  205. data/test/data/file-with-space.right +4 -0
  206. data/test/data/printvar.cmd +17 -0
  207. data/test/data/printvar.right +31 -0
  208. data/test/data/raise.cmd +11 -0
  209. data/test/data/raise.right +19 -0
  210. data/test/data/source.cmd +5 -0
  211. data/test/data/source.right +18 -0
  212. data/test/data/stepping-1.9.right +50 -0
  213. data/test/data/stepping.cmd +21 -0
  214. data/test/data/stepping.right +48 -0
  215. data/test/data/trepan8-save.1 +6 -0
  216. data/test/example/bp_loop_issue.rb +3 -0
  217. data/test/example/break-bug.rb +7 -0
  218. data/test/example/brkpt-class-bug.rb +8 -0
  219. data/test/example/classes.rb +11 -0
  220. data/test/example/dollar-0.rb +5 -0
  221. data/test/example/except-bug1.rb +4 -0
  222. data/test/example/except-bug2.rb +7 -0
  223. data/test/example/file with space.rb +1 -0
  224. data/test/example/gcd.rb +18 -0
  225. data/test/example/info-var-bug.rb +47 -0
  226. data/test/example/info-var-bug2.rb +2 -0
  227. data/test/example/null.rb +1 -0
  228. data/test/example/pm-bug.rb +3 -0
  229. data/test/example/pm.rb +11 -0
  230. data/test/example/raise.rb +3 -0
  231. data/test/integration/.gitignore +4 -0
  232. data/test/integration/config.yaml +8 -0
  233. data/test/integration/helper.rb +154 -0
  234. data/test/integration/test-break_bad.rb +26 -0
  235. data/test/integration/test-dollar-0.rb +31 -0
  236. data/test/integration/test-edit.rb +17 -0
  237. data/test/integration/test-file-with-space.rb +26 -0
  238. data/test/integration/test-printvar.rb +17 -0
  239. data/test/integration/test-raise.rb +21 -0
  240. data/test/integration/test-source.rb +16 -0
  241. data/test/integration/test-stepping.rb +24 -0
  242. data/test/unit/.gitignore +1 -0
  243. data/test/unit/cmd-helper.rb +52 -0
  244. data/test/unit/mock-helper.rb +12 -0
  245. data/test/unit/test-app-cmd_parse.rb +97 -0
  246. data/test/unit/test-app-cmd_parser.rb +23 -0
  247. data/test/unit/test-app-complete.rb +39 -0
  248. data/test/unit/test-app-frame.rb +32 -0
  249. data/test/unit/test-app-options.rb +92 -0
  250. data/test/unit/test-app-run.rb +14 -0
  251. data/test/unit/test-app-util.rb +44 -0
  252. data/test/unit/test-base-cmd.rb +45 -0
  253. data/test/unit/test-base-subcmd.rb +57 -0
  254. data/test/unit/test-base-submgr.rb +23 -0
  255. data/test/unit/test-base-subsubcmd.rb +17 -0
  256. data/test/unit/test-cmd-alias.rb +48 -0
  257. data/test/unit/test-cmd-exit.rb +27 -0
  258. data/test/unit/test-cmd-help.rb +104 -0
  259. data/test/unit/test-cmd-kill.rb +46 -0
  260. data/test/unit/test-cmd-source.rb +34 -0
  261. data/test/unit/test-completion.rb +42 -0
  262. data/test/unit/test-intf-user.rb +46 -0
  263. data/test/unit/test-io-input.rb +27 -0
  264. data/test/unit/test-io-tcp.rb +33 -0
  265. data/test/unit/test-io-tcpclient.rb +54 -0
  266. data/test/unit/test-io-tcpfns.rb +17 -0
  267. data/test/unit/test-io-tcpserver.rb +50 -0
  268. data/test/unit/test-proc-eval.rb +36 -0
  269. data/test/unit/test-proc-hook.rb +30 -0
  270. data/test/unit/test-proc-load_cmds.rb +50 -0
  271. data/test/unit/test-proc-location.rb +79 -0
  272. data/test/unit/test-subcmd-help.rb +44 -0
  273. data/trepan8.gemspec +52 -0
  274. metadata +391 -0
data/processor/mock.rb ADDED
@@ -0,0 +1,136 @@
1
+ # Copyright (C) 2010, 2011 Rocky Bernstein <rockyb@rubyforge.net>
2
+ # Mock setup for commands.
3
+ require 'rubygems'; require 'require_relative'
4
+ require_relative 'virtual'
5
+
6
+ # require_relative '../app/core'
7
+ require_relative '../app/default'
8
+ require_relative '../interface/user' # user interface (includes I/O)
9
+
10
+ require 'ruby-debug-base'; Debugger.start(:init => true)
11
+ require_relative 'processor'
12
+
13
+ module MockDebugger
14
+ class MockDebugger
15
+ attr_accessor :trace_filter # Procs/Methods we ignore.
16
+
17
+ attr_accessor :frame # Actually a "Rubinius::Location object
18
+ attr_accessor :core # access to Debugger::Core instance
19
+ attr_accessor :intf # The way the outside world interfaces with us.
20
+ attr_reader :initial_dir # String. Current directory when program
21
+ # started. Used in restart program.
22
+ attr_accessor :restart_argv # How to restart us, empty or nil.
23
+ # Note restart[0] is typically $0.
24
+ attr_reader :settings # Hash[:symbol] of things you can configure
25
+ attr_accessor :processor
26
+
27
+ # FIXME: move more stuff of here and into Trepan::CmdProcessor
28
+ # These below should go into Trepan::CmdProcessor.
29
+ attr_reader :cmd_argstr, :cmd_name, :current_frame, :completion_proc
30
+
31
+ def initialize(settings={:start_frame=>1})
32
+ @before_cmdloop_hooks = []
33
+ @settings = Trepan::DEFAULT_SETTINGS.merge(settings)
34
+ @intf = [Trepan::UserInterface.new(nil, nil,
35
+ :history_save=>false)]
36
+ # @current_frame = Trepan::Frame.new(context)
37
+ @frames = []
38
+
39
+ @trace_filter = []
40
+
41
+ @completion_proc = Proc.new{|str| str}
42
+
43
+ # Don't allow user commands in mocks.
44
+ ## @core.processor.settings[:user_cmd_dir] = nil
45
+
46
+ end
47
+
48
+ def frame(num)
49
+ @frames[num] ||= caller
50
+ end
51
+ end
52
+
53
+ # Common Mock debugger setup
54
+ def setup(name=nil, show_constants=true)
55
+ unless name
56
+ file = caller.first.split(/:\d/,2).first
57
+ name = File.basename(File.basename(file), '.rb')
58
+ end
59
+
60
+ if ARGV.size > 0 && ARGV[0] == 'debug'
61
+ require_relative '../lib/trepanning'
62
+ dbgr = Trepan.new
63
+ dbgr.debugger
64
+ else
65
+ dbgr = MockDebugger.new(:start_frame=>2)
66
+ end
67
+
68
+ cmdproc = Trepan::CmdProcessor.new(dbgr.intf)
69
+ state = Trepan::CommandProcessor::State.new(self) do |s|
70
+ s.context = Debugger.current_context
71
+ s.binding = binding
72
+ end
73
+ cmdproc.frame_setup(state.context, state)
74
+ dbgr.processor = cmdproc
75
+
76
+ cmdproc.interfaces = dbgr.intf
77
+ cmdproc.load_cmds_initialize
78
+ cmds = cmdproc.commands
79
+ cmd = cmds[name]
80
+ ## cmd.proc.frame_setup
81
+ ## cmd.proc.event = 'debugger-call'
82
+ show_special_class_constants(cmd) if show_constants
83
+
84
+ def cmd.confirm(prompt, default)
85
+ true
86
+ end
87
+ def cmd.msg_nocr(message, opts={})
88
+ print message
89
+ end
90
+
91
+ return dbgr, cmd
92
+ end
93
+ module_function :setup
94
+
95
+ def sub_setup(sub_class, run=true)
96
+ sub_name = sub_class.const_get('PREFIX')
97
+ dbgr, cmd = setup(sub_name[0], false)
98
+ sub_cmd = sub_class.new(cmd)
99
+ sub_cmd.summary_help(sub_cmd.name)
100
+ puts
101
+ sub_cmd.run([cmd.name]) if run
102
+ return sub_cmd
103
+ end
104
+ module_function :sub_setup
105
+
106
+ def subsub_setup(sub_class, subsub_class, run=true)
107
+ subsub_name = subsub_class.const_get('PREFIX')
108
+ dbgr, cmd = setup(subsub_name[0], false)
109
+ sub_cmd = sub_class.new(dbgr.processor, cmd)
110
+ subsub_cmd = subsub_class.new(cmd.proc, sub_cmd, subsub_name.join(''))
111
+ subsub_cmd.run([subsub_cmd.name]) if run
112
+ return subsub_cmd
113
+ end
114
+ module_function :subsub_setup
115
+
116
+ def show_special_class_constants(cmd)
117
+ puts 'ALIASES: %s' % [cmd.class.const_get('ALIASES').inspect] if
118
+ cmd.class.constants.member?(:ALIASES)
119
+ %w(CATEGORY MIN_ARGS MAX_ARGS
120
+ NAME NEED_STACK SHORT_HELP).each do |name|
121
+ puts '%s: %s' % [name, cmd.class.const_get(name).inspect]
122
+ end
123
+ puts '-' * 30
124
+ puts cmd.class.const_get('HELP')
125
+ puts '=' * 30
126
+ end
127
+ module_function :show_special_class_constants
128
+
129
+ end
130
+
131
+ if __FILE__ == $0
132
+ dbgr = MockDebugger::MockDebugger.new
133
+ p dbgr.settings
134
+ puts '=' * 10
135
+ # p dbgr.core.processor.settings
136
+ end
data/processor/msg.rb ADDED
@@ -0,0 +1,61 @@
1
+ # Copyright (C) 2010, 2011 Rocky Bernstein <rockyb@rubyforge.net>
2
+ # I/O related command processor methods
3
+ require 'rubygems'; require 'require_relative'
4
+ require_relative '../app/util'
5
+ require_relative 'virtual'
6
+ module Trepan
7
+ class CmdProcessor < VirtualCmdProcessor
8
+ attr_accessor :ruby_highlighter
9
+
10
+ def errmsg(message, opts={})
11
+ message = safe_rep(message) unless opts[:unlimited]
12
+ if @settings[:highlight] && defined?(Term::ANSIColor)
13
+ message =
14
+ Term::ANSIColor.italic + message + Term::ANSIColor.reset
15
+ end
16
+ @intf.errmsg(message)
17
+ end
18
+
19
+ def msg(message, opts={})
20
+ message = safe_rep(message) unless opts[:unlimited]
21
+ @intf.msg(message)
22
+ end
23
+
24
+ def msg_nocr(message, opts={})
25
+ message = safe_rep(message) unless opts[:unlimited]
26
+ @intf.msg_nocr(message)
27
+ end
28
+
29
+ def read_command()
30
+ @intf.read_command(@prompt)
31
+ end
32
+
33
+ def ruby_format(text)
34
+ return text unless settings[:highlight]
35
+ unless @ruby_highlighter
36
+ begin
37
+ require 'coderay'
38
+ require 'term/ansicolor'
39
+ @ruby_highlighter = CodeRay::Duo[:ruby, :term]
40
+ rescue LoadError
41
+ return text
42
+ end
43
+ end
44
+ return @ruby_highlighter.encode(text)
45
+ end
46
+
47
+ def safe_rep(str)
48
+ Util::safe_repr(str, @settings[:maxstring])
49
+ end
50
+
51
+ def section(message, opts={})
52
+ message = safe_rep(message) unless opts[:unlimited]
53
+ if @settings[:highlight] && defined?(Term::ANSIColor)
54
+ message =
55
+ Term::ANSIColor.bold + message + Term::ANSIColor.reset
56
+ end
57
+ @intf.msg(message)
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,674 @@
1
+ require 'rubygems'
2
+ require 'ruby-debug-base'
3
+ require 'require_relative'
4
+ require_relative '../interface/user'
5
+ require_relative './command'
6
+ require_relative './default'
7
+ require_relative './main'
8
+ require_relative '../app/frame'
9
+
10
+ # _Trepan_ is the module name space for this debugger.
11
+ module Trepan
12
+
13
+ # A processor handles the kind of front-end to program interaction.
14
+ # Debugger::Processor is the the base class with subclasses
15
+ # Debugger::CommandProcessor and Debugger::ControlCommandProcessor.
16
+ class Processor
17
+ attr_accessor :interface
18
+ attr_reader :processor
19
+ attr_reader :commands
20
+
21
+ attr_accessor :frame # FIXME: move to frame.rb
22
+
23
+ # optional argument passed passed in callback. For catchpoints it is
24
+ # the catch object. For breakpoints it is the breakpoint object.
25
+ attr_reader :event_arg
26
+
27
+ # Format _msg_ with gdb-style annotation header.
28
+ def afmt(msg, newline="\n")
29
+ "\032\032#{msg}#{newline}"
30
+ end
31
+
32
+ # Print "annotation" message _msg_. Annotation messages are used
33
+ # by the GNU-Emacs front-end to get status about stacks and
34
+ # the state of the debugger without having to poll it for information
35
+ def aprint(msg)
36
+ print afmt(msg) if Trepan.annotate.to_i > 2
37
+ end
38
+
39
+ # Print a debugger error message; _args_ should be compatible
40
+ # with something you would pass to Kernel::print.
41
+ def errmsg(*args)
42
+ @interface.errmsg(*args)
43
+ @interface.print("\n")
44
+ end
45
+
46
+ # Print a normal debugger message; _args_ should be compatible
47
+ # with something you would pass to Kernel::print.
48
+ #
49
+ # Callers of this routine should make sure to use comma to
50
+ # separate format argments rather than %. Otherwise it seems that
51
+ # if the string you want to print has format specifier, which
52
+ # could happen if you are trying to show say a source-code line
53
+ # with "puts" or "print" in it, this print routine will give an
54
+ # error saying it is looking for more arguments.
55
+ def print(*args)
56
+ @interface.msg(*args)
57
+ end
58
+
59
+ end
60
+
61
+ # A Debugger::CommandProcessor is the kind of Debugger::Processor
62
+ # used when you are running inside the same process as the debugged
63
+ # program.
64
+ class CommandProcessor < Processor
65
+ attr_reader :display
66
+ attr_accessor :frame
67
+ attr_reader :cmdproc
68
+
69
+ # FIXME: get from Command regexp method.
70
+ @@Show_breakpoints_postcmd = [
71
+ /^\s*b(?:reak)?/,
72
+ /^\s* cond(?:ition)? (?:\s+(\d+)\s*(.*))?$/ix,
73
+ /^\s*del(?:ete)?(?:\s+(.*))?$/ix,
74
+ /^\s* dis(?:able)? (?:\s+(.*))?$/ix,
75
+ /^\s* en(?:able)? (?:\s+(.*))?$/ix,
76
+ # "tbreak", "clear",
77
+ ]
78
+ @@Show_annotations_run = [
79
+ /^\s*c(?:ont(?:inue)?)?(?:\s+(.*))?$/,
80
+ /^\s*fin(?:ish)?$/,
81
+ /^\s*n(?:ext)?([+-])?(?:\s+(.*))?$/,
82
+ /^\s*s(?:tep)?([+-])?(?:\s+(.*))?$/
83
+ ]
84
+
85
+ @@Show_annotations_postcmd = [
86
+ /^\s* down (?:\s+(.*))? .*$/x,
87
+ /^\s* f(?:rame)? (?:\s+ (.*))? \s*$/x,
88
+ /^\s* u(?:p)? (?:\s+(.*))?$/x
89
+ ]
90
+
91
+ def initialize(interfaces = [Trepan::UserInterface.new], settings={})
92
+ @interface = interfaces.first
93
+ @commands = []
94
+ @display = []
95
+
96
+ @mutex = Mutex.new
97
+ @last_cmd = nil
98
+ @last_file = nil # Filename the last time we stopped
99
+ @last_line = nil # line number the last time we stopped
100
+ @debugger_breakpoints_were_empty = false # Show breakpoints 1st time
101
+ @debugger_displays_were_empty = true # No display 1st time
102
+ @debugger_context_was_dead = true # Assume we haven't started.
103
+ @cmdproc = CmdProcessor.new(interfaces, settings)
104
+ end
105
+
106
+ def interface=(interface)
107
+ @mutex.synchronize do
108
+ @interface.close if @interface
109
+ @interface = interface
110
+ end
111
+ end
112
+
113
+ require 'pathname' # For cleanpath
114
+
115
+ # Regularize or "canonicalize" file name _filename_.
116
+ # This is also used as a common funnel place if basename is
117
+ # desired or if we are working remotely and want to change the
118
+ # basename. Or we are eliding filenames.
119
+ def self.canonic_file(filename)
120
+ # For now we want resolved filenames
121
+ if OldCommand.settings[:basename]
122
+ File.basename(filename)
123
+ else
124
+ # Cache this?
125
+ Pathname.new(filename).cleanpath.to_s
126
+ end
127
+ end
128
+
129
+ def self.print_location_and_text(file, line)
130
+ file_line = "%s:%s\n%s" % [canonic_file(file), line,
131
+ Debugger.line_at(file, line)]
132
+ # FIXME: use annotations routines
133
+ if Trepan.annotate.to_i > 2
134
+ file_line = "\032\032source #{file_line}"
135
+ elsif Trepan.inside_emacs?
136
+ file_line = "\032\032#{file_line}"
137
+ end
138
+ print file_line
139
+ end
140
+
141
+ # Create a "protected" version of method _mname_. A protected
142
+ # method handles all unhandled exceptions that would cause the
143
+ # program to terminate.
144
+ def self.protect(mname)
145
+ alias_method "__#{mname}", mname
146
+ module_eval %{
147
+ def #{mname}(*args)
148
+ @mutex.synchronize do
149
+ return unless @interface
150
+ __#{mname}(*args)
151
+ end
152
+ rescue IOError, Errno::EPIPE
153
+ self.interface = nil
154
+ rescue SignalException
155
+ raise
156
+ rescue Exception
157
+ print "INTERNAL ERROR!!! #\{$!\}\n" rescue nil
158
+ print $!.backtrace.map{|l| "\t#\{l\}"}.join("\n") rescue nil
159
+ end
160
+ }
161
+ end
162
+
163
+ # This is a callback routine when the debugged program hits a
164
+ # breakpoint event. For example ruby-debug-base calls this method.
165
+ def at_breakpoint(context, breakpoint)
166
+ @event_arg = breakpoint
167
+ @cmdproc.event = 'brkpt'
168
+ aprint 'stopped' if Trepan.annotate.to_i > 2
169
+ n = Debugger.breakpoints.index(breakpoint) + 1
170
+ file = @cmdproc.canonic_file(breakpoint.source)
171
+ line = breakpoint.pos
172
+ if Trepan.annotate.to_i > 2
173
+ print afmt("source #{file}:#{line}")
174
+ end
175
+ @cmdproc.msg 'Breakpoint %d at %s:%s' % [n, file, line]
176
+ end
177
+ protect :at_breakpoint
178
+
179
+ # This is a callback routine when the debugged program raises an
180
+ # unhandled exception that we have arranged to catch.
181
+ # In particular, ruby-debug-base calls this method
182
+ def at_catchpoint(context, excpt)
183
+ @event_arg = excpt
184
+ @cmdproc.event = 'raise'
185
+ file = @cmdproc.canonic_file(context.frame_file(0))
186
+ line = context.frame_line(0)
187
+ @cmdproc.msg("Catchpoint at %s:%d: `%s' (%s)" %
188
+ [file, line, excpt, excpt.class])
189
+ process_commands(context, file, line)
190
+ end
191
+ protect :at_catchpoint
192
+
193
+ def at_tracing(context, file, line)
194
+ @cmdproc.event = 'tracing'
195
+ return if defined?(Debugger::RDEBUG_FILE) &&
196
+ Debugger::RDEBUG_FILE == file # Don't trace ourself
197
+ @last_file = CommandProcessor.canonic_file(file)
198
+ canonic_file = CommandProcessor.canonic_file(file)
199
+ unless canonic_file == @last_file and @last_line == line and
200
+ Command.settings[:tracing_plus]
201
+ print "Tracing(%d):%s:%s %s",
202
+ context.thnum, canonic_file, line, Debugger.line_at(file, line)
203
+ @last_file = canonic_file
204
+ @last_line = line
205
+ end
206
+ always_run(context, file, line, 2)
207
+ end
208
+ protect :at_tracing
209
+
210
+ # This is a callback routine when the debugged program hits a
211
+ # "line" (or statement boundary) event. For example
212
+ # ruby-debug-base calls this.
213
+ def at_line(context, file, line)
214
+ @cmdproc.event = 'line'
215
+ process_commands(context, file, line)
216
+ end
217
+ protect :at_line
218
+
219
+ # This is a callback routine when the debugged program hits a
220
+ # "return" event. For example ruby-debug-base calls this.
221
+ # Note: right now ruby-debug-base does not call this. Perhaps
222
+ # other bases routines such as the one in JRuby do.
223
+ def at_return(context, file, line)
224
+ @cmdproc.event = 'return'
225
+ context.stop_frame = -1
226
+ process_commands(context, file, line)
227
+ end
228
+
229
+ # Return the command object to run given input string _input_.
230
+ def lookup(input)
231
+ ###########################################
232
+ ## Test the waters with new-style commands
233
+ args = input.split
234
+ cmd_name = args[0]
235
+ run_cmd_name =
236
+ if @cmdproc.aliases.member?(cmd_name)
237
+ @cmdproc.aliases[cmd_name]
238
+ else
239
+ cmd_name
240
+ end
241
+
242
+ if @cmdproc.commands.member?(run_cmd_name)
243
+ cmd = @cmdproc.commands[run_cmd_name]
244
+ if @cmdproc.ok_for_running(cmd, run_cmd_name, args.size-1)
245
+ return cmd
246
+ end
247
+ end
248
+ ###########################################
249
+
250
+ @commands.find{ |c| c.match(input) }
251
+ end
252
+
253
+ # Run a single command specified by string _input_; _commands_ is and
254
+ # Array of possible debugger command objects and _context_ is
255
+ # a Debugger::Context object.
256
+ def one_cmd(commands, context, input)
257
+ if cmd = lookup(input)
258
+ if cmd.kind_of?(Command)
259
+ if context.dead? && cmd.class.need_context
260
+ p cmd
261
+ print "Command is unavailable\n"
262
+ else
263
+ args = input.split
264
+ cmd_name = args[0]
265
+ @cmdproc.instance_variable_set('@cmd_argstr', input[cmd_name.size..-1].lstrip)
266
+ @cmdproc.instance_variable_set('@cmd_name', cmd_name)
267
+ begin
268
+ cmd.run(args)
269
+ rescue Exception
270
+ print "INTERNAL ERROR running command: #{cmd_name}\n"
271
+ print "#{$!}\n"
272
+ print $!.backtrace.map{|l| "\t#{l}"}.join("\n"), "\n" rescue nil
273
+ end
274
+ end
275
+ elsif context.dead? && cmd.class.need_context
276
+ p cmd
277
+ msg "Command is unavailable"
278
+ else
279
+ puts "Using old-style command" unless
280
+ @cmdproc.settings[:debuggertesting]
281
+ cmd.execute
282
+ end
283
+ else
284
+ unknown_cmd = commands.find{|cmd| cmd.class.unknown }
285
+ if unknown_cmd
286
+ unknown_cmd.execute
287
+ else
288
+ if @cmdproc.settings[:autoeval]
289
+ begin
290
+ @cmdproc.eval_code(input, @cmdproc.settings[:maxstring])
291
+ return
292
+ rescue NameError
293
+ end
294
+ end
295
+ errmsg "Unknown command: \"#{input.chomp}\". Try \"help\"."
296
+ end
297
+ end
298
+ end
299
+
300
+ private
301
+
302
+ # Return a prompt string to show before reading a command.
303
+ def prompt(context)
304
+ p = '(rdb:%s) ' % (context.dead? ? 'post-mortem' : context.thnum)
305
+ p = afmt("pre-prompt")+p+"\n"+afmt("prompt") if
306
+ Trepan.annotate.to_i > 2
307
+ return p
308
+ end
309
+
310
+ # Run commands that we always have to run before a entering a
311
+ # command loop. For example commands registered via debugger "set
312
+ # display", "set autolist", or set "autoirb". We return a list of
313
+ # commands that are acceptable to run bound to the current state.
314
+ def always_run(context, file, line, run_level)
315
+ event_cmds = OldCommand.commands.select{|cmd| cmd.event }
316
+
317
+ # Remove some commands if we are post mortem.
318
+ event_cmds = event_cmds.find_all do |cmd|
319
+ cmd.allow_in_post_mortem
320
+ end if !context || context.dead?
321
+
322
+ state = State.new(self) do |s|
323
+ s.context = context
324
+ s.file = file
325
+ s.line = line
326
+ s.display = display
327
+ s.commands = event_cmds
328
+ end
329
+ @interface.state = state if @interface.respond_to?('state=')
330
+
331
+ # Bind commands to the current state.
332
+ commands = event_cmds.map{|cmd| cmd.new(state)}
333
+
334
+ commands.select do |cmd|
335
+ cmd.class.always_run >= run_level
336
+ end.each {|cmd| cmd.execute}
337
+ return state, commands
338
+ end
339
+
340
+ # This the main debugger command-line loop. Here we read a
341
+ # debugger command, perform it, and ask for another one unless we
342
+ # are told to continue execution or terminate.
343
+ def process_commands(context, file, line)
344
+ @state, @commands = always_run(context, file, line, 1)
345
+ $rdebug_state = @state if @cmdproc.settings[:debuggertesting]
346
+ @cmdproc.process_commands(context, @state)
347
+
348
+ # @cmdproc.frame_setup(context, @state)
349
+ # splitter = lambda do |str|
350
+ # str.split(/;/).inject([]) do |m, v|
351
+ # if m.empty?
352
+ # m << v
353
+ # else
354
+ # if m.last[-1] == ?\\
355
+ # m.last[-1,1] = ''
356
+ # m.last << ';' << v
357
+ # else
358
+ # m << v
359
+ # end
360
+ # end
361
+ # m
362
+ # end
363
+ # end
364
+
365
+ # preloop(@commands, context)
366
+ # CommandProcessor.print_location_and_text(file, line)
367
+ # while !@state.proceed?
368
+ # input = if @interface.command_queue.empty?
369
+ # @interface.read_command(prompt(context))
370
+ # else
371
+ # @interface.command_queue.shift
372
+ # end
373
+ # break unless input
374
+ # next if input =~ /^\s*#/
375
+ # catch(:debug_error) do
376
+ # if input == ""
377
+ # next unless @last_cmd
378
+ # input = @last_cmd
379
+ # else
380
+ # @last_cmd = input
381
+ # end
382
+ # splitter[input].each do |cmd|
383
+ # one_cmd(@commands, context, cmd)
384
+ # postcmd(@commands, context, cmd)
385
+ # end
386
+ # end
387
+ # end
388
+ # postloop(@commands, context)
389
+ end # process_commands
390
+
391
+ # Things we do before entering the debugger command loop.
392
+ # Note: in the trepanning debuggers this and always_run have been
393
+ # merged. To do this and get the order right we add a priority level
394
+ # for each hook.
395
+ def preloop(commands, context)
396
+ aprint('stopped') if Trepan.annotate.to_i > 2
397
+ if context.dead?
398
+ unless @debugger_context_was_dead
399
+ if Trepan.annotate.to_i > 2
400
+ aprint('exited')
401
+ print "The program finished.\n"
402
+ end
403
+ @debugger_context_was_dead = true
404
+ end
405
+ end
406
+
407
+ if Trepan.annotate.to_i > 2
408
+ # if we are here, the stack frames have changed outside the
409
+ # command loop (e.g. after a "continue" command), so we show
410
+ # the annotations again
411
+ breakpoint_annotations(commands, context)
412
+ display_annotations(commands, context)
413
+ annotation('stack', commands, context, "where")
414
+ annotation('variables', commands, context, "info variables") unless
415
+ context.dead?
416
+ end
417
+ end
418
+
419
+ # Things we do after leaving the debugger command loop.
420
+ def postcmd(commands, context, cmd)
421
+ if Trepan.annotate.to_i > 0
422
+ cmd = @last_cmd unless cmd
423
+ breakpoint_annotations(commands, context) if
424
+ @@Show_breakpoints_postcmd.find{|pat| cmd =~ pat}
425
+ display_annotations(commands, context)
426
+ if @@Show_annotations_postcmd.find{|pat| cmd =~ pat}
427
+ annotation('stack', commands, context, "where") if
428
+ context.stack_size > 0
429
+ annotation('variables', commands, context, "info variables") unless
430
+ context.dead?
431
+ end
432
+ if not context.dead? and @@Show_annotations_run.find{|pat| cmd =~ pat}
433
+ aprint 'starting' if Trepan.annotate.to_i > 2
434
+
435
+ @debugger_context_was_dead = false
436
+ end
437
+ end
438
+ end
439
+
440
+ # Things we do after leaving the debugger command loop.
441
+ def postloop(commands, context)
442
+ end
443
+
444
+ # Run a command in String _cmd_, but tag output with annotation
445
+ # specified in String _label+. +commands_ is an Array of all
446
+ # possible debugger command objects, and _context_ is a
447
+ # Debugger::Context object.
448
+ def annotation(label, commands, context, cmd)
449
+ print afmt(label)
450
+ one_cmd(commands, context, cmd)
451
+ ### FIXME ANNOTATE: the following line should be deleted
452
+ print "\032\032\n"
453
+ end
454
+
455
+ def breakpoint_annotations(commands, context)
456
+ unless Debugger.breakpoints.empty? and @debugger_breakpoints_were_empty
457
+ annotation('breakpoints', commands, context, "info breakpoints")
458
+ @debugger_breakpoints_were_empty = Debugger.breakpoints.empty?
459
+ end
460
+ end
461
+
462
+ def display_annotations(commands, context)
463
+ return if display.empty?
464
+ # have_display = display.find{|d| d[0]}
465
+ # return unless have_display and @debugger_displays_were_empty
466
+ # @debugger_displays_were_empty = have_display
467
+ annotation('display', commands, context, "display")
468
+ end
469
+
470
+ class State # :nodoc:
471
+ attr_accessor :context, :file, :line, :binding
472
+ attr_accessor :frame_pos, :previous_line, :display
473
+ attr_accessor :interface, :commands, :processor
474
+
475
+ def initialize(processor=nil)
476
+ super()
477
+ @frame_pos = 0
478
+ @previous_line = nil
479
+ @proceed = false
480
+ @processor = processor
481
+ yield self
482
+ end
483
+
484
+ # Print a debugger error message; _args_ should be compatible
485
+ # with something you would pass to Kernel::print.
486
+ def errmsg(*args)
487
+ @interface.errmsg(*args)
488
+ @interface.msg("\n")
489
+ end
490
+
491
+ # Print a normal debugger message; _args_ should be compatible
492
+ # with something you would pass to Kernel::print.
493
+ #
494
+ # Callers of this routine should make sure to use comma to
495
+ # separate format argments rather than %. Otherwise it seems that
496
+ # if the string you want to print has format specifier, which
497
+ # could happen if you are trying to show say a source-code line
498
+ # with "puts" or "print" in it, this print routine will give an
499
+ # error saying it is looking for more arguments.
500
+ def print(*args)
501
+ @interface.print(*args)
502
+ end
503
+
504
+ # confirm is called before performing a dangerous action.
505
+ def confirm(*args)
506
+ @interface.confirm(*args)
507
+ end
508
+
509
+ def proceed?
510
+ @proceed
511
+ end
512
+
513
+ def proceed
514
+ @proceed = true
515
+ end
516
+ end
517
+ end
518
+
519
+ # A Debugger::ControlCommandProcessor is the kind of Debugger::Processor
520
+ # used the debugged program is running remotely. It is also entered
521
+ # after the debugged program has terminated.
522
+ class ControlCommandProcessor < Processor
523
+ def initialize(interface)
524
+ super()
525
+ @interface = interface
526
+ @cmdproc = CmdProcessor.new([interface])
527
+ @debugger_context_was_dead = true # Assume we haven't started.
528
+ end
529
+
530
+ # Return the command object to run given input string _input_.
531
+ def lookup(input)
532
+ ###########################################
533
+ ## Test the waters with new-style commands
534
+ args = input.split
535
+ cmd_name = args[0]
536
+ run_cmd_name =
537
+ if @cmdproc.aliases.member?(cmd_name)
538
+ @cmdproc.aliases[cmd_name]
539
+ else
540
+ cmd_name
541
+ end
542
+
543
+ if @cmdproc.commands.member?(run_cmd_name)
544
+ cmd = @cmdproc.commands[run_cmd_name]
545
+ if @cmdproc.ok_for_running(cmd, run_cmd_name, args.size-1)
546
+ return cmd
547
+ end
548
+ end
549
+ ###########################################
550
+
551
+ @commands.find{ |c| c.match(input) }
552
+ end
553
+
554
+ # This the main debugger command-line loop. Here we read a
555
+ # debugger command, perform it, and ask for another one unless we
556
+ # are told to continue execution or terminate.
557
+ def process_commands(verbose=false)
558
+ control_cmds = OldCommand.commands.select do |cmd|
559
+ cmd.allow_in_control
560
+ end
561
+ context = Debugger.current_context
562
+ @state = State.new(@interface, control_cmds)
563
+ @cmdproc.frame_setup(context, @state)
564
+ @commands = control_cmds.map{|cmd| cmd.new(@state) }
565
+
566
+ unless @debugger_context_was_dead
567
+ if Trepan.annotate.to_i > 2
568
+ aprint 'exited'
569
+ print "The program finished.\n"
570
+ end
571
+ @debugger_context_was_dead = true
572
+ end
573
+
574
+ while input = @interface.read_command(prompt(nil))
575
+ print "+#{input}\n" if true # verbose
576
+ catch(:debug_error) do
577
+ if cmd = lookup(input)
578
+ if cmd.kind_of?(Command)
579
+ args = input.split
580
+ cmd_name = args[0]
581
+ @cmdproc.instance_variable_set('@cmd_argstr', input[cmd_name.size..-1].lstrip)
582
+ @cmdproc.instance_variable_set('@cmd_name', cmd_name)
583
+ begin
584
+ cmd.run(args)
585
+ rescue Exception
586
+ print "INTERNAL ERROR running command: #{cmd_name}\n"
587
+ print "#{$!}\n"
588
+ print $!.backtrace.map{|l| "\t#{l}"}.join("\n"), "\n" rescue nil
589
+ end
590
+ else
591
+ puts "Using old-style command" unless
592
+ @cmdproc.settings[:debuggertesting]
593
+ cmd.execute
594
+ end
595
+ else
596
+ if @cmdproc.settings[:autoeval]
597
+ begin
598
+ @cmdproc.eval_code(input, @cmdproc.settings[:maxstring])
599
+ return
600
+ rescue NameError
601
+ end
602
+ end
603
+ errmsg "Unknown command: \"#{input.chomp}\". Try \"help\"."
604
+ end
605
+ end
606
+ end
607
+ rescue IOError, Errno::EPIPE
608
+ # rescue Exception
609
+ # print "INTERNAL ERROR!!! #{$!}\n" rescue nil
610
+ # print $!.backtrace.map{|l| "\t#{l}"}.join("\n") rescue nil
611
+ ensure
612
+ @interface.close
613
+ end
614
+
615
+ # Return a prompt string to show before reading a command. Note: The
616
+ # _context_ parameter is not used. It must be provided so that the
617
+ # interface matches Debugger::CommandProcessor#prompt.
618
+ def prompt(context)
619
+ p = '(rdb:ctrl) '
620
+ p = afmt("pre-prompt")+p+"\n"+afmt("prompt") if
621
+ Trepan.annotate.to_i > 2
622
+ return p
623
+ end
624
+
625
+ class State # :nodoc:
626
+ attr_reader :commands, :interface, :frame_pos
627
+
628
+ def initialize(interface, commands)
629
+ @interface = interface
630
+ @commands = commands
631
+ @frame_pos = 0
632
+ end
633
+
634
+ def proceed
635
+ end
636
+
637
+ # Print a debugger error message; _args_ should be compatible
638
+ # with something you would pass to Kernel::print.
639
+ def errmsg(*args)
640
+ @interface.print(*args)
641
+ @interface.print "\n"
642
+ end
643
+
644
+ # Print a normal debugger message; _args_ should be compatible
645
+ # with something you would pass to Kernel::print.
646
+ #
647
+ # Callers of this routine should make sure to use comma to
648
+ # separate format argments rather than %. Otherwise it seems that
649
+ # if the string you want to print has format specifier, which
650
+ # could happen if you are trying to show say a source-code line
651
+ # with "puts" or "print" in it, this print routine will give an
652
+ # error saying it is looking for more arguments.
653
+ def print(*args)
654
+ @interface.print(*args)
655
+ @interface.print "\n"
656
+ end
657
+
658
+ # confirm is called before performing a dangerous action. In
659
+ # control processor we always return "yes" or "y".
660
+ def confirm(*args)
661
+ 'y'
662
+ end
663
+
664
+ def context
665
+ nil
666
+ end
667
+
668
+ def file
669
+ errmsg "No filename given."
670
+ throw :debug_error
671
+ end
672
+ end # State
673
+ end
674
+ end