nitdbg: Added possibility to step-out of a function
[nit.git] / src / debugger.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2013 Lucas Bajolet <lucas.bajolet@gmail.com>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Debugging of a nit program using the NaiveInterpreter
18 module debugger
19
20 intrude import naive_interpreter
21
22 redef class ToolContext
23 # -d
24 var opt_debugger_mode: OptionBool = new OptionBool("Launches the target program with the debugger attached to it", "-d")
25
26 redef init
27 do
28 super
29 self.option_context.add_option(self.opt_debugger_mode)
30 end
31 end
32
33 redef class ModelBuilder
34 # Execute the program from the entry point (Sys::main) of the `mainmodule'
35 # `arguments' are the command-line arguments in order
36 # REQUIRE that:
37 # 1. the AST is fully loaded.
38 # 2. the model is fully built.
39 # 3. the instructions are fully analysed.
40 fun run_debugger(mainmodule: MModule, arguments: Array[String])
41 do
42 var time0 = get_time
43 self.toolcontext.info("*** START INTERPRETING ***", 1)
44
45 var interpreter = new Debugger(self, mainmodule, arguments)
46
47 init_naive_interpreter(interpreter, mainmodule)
48
49 var time1 = get_time
50 self.toolcontext.info("*** END INTERPRETING: {time1-time0} ***", 2)
51 end
52 end
53
54 # The class extending NaiveInterpreter by adding debugging methods
55 class Debugger
56 super NaiveInterpreter
57
58 # Keeps the frame count in memory to find when to stop
59 # and launch the command prompt after a step out call
60 var step_stack_count = 1
61
62 # Triggers a step over an instruction in a nit program
63 var stop_after_step_over_trigger = true
64
65 # Triggers a step out of an instruction
66 var stop_after_step_out_trigger= false
67
68 #######################################################################
69 ## Execution of statement function ##
70 #######################################################################
71
72 # Main loop, every call to a debug command is done here
73 redef fun stmt(n: nullable AExpr)
74 do
75 if n == null then return
76
77 var frame = self.frame
78 var old = frame.current_node
79 frame.current_node = n
80
81 steps_fun_call(n)
82
83 n.stmt(self)
84 frame.current_node = old
85 end
86
87 # Encpasulates the behaviour for step over/out
88 private fun steps_fun_call(n: AExpr)
89 do
90 if self.stop_after_step_over_trigger then
91 if self.frames.length <= self.step_stack_count then
92 n.debug("Execute stmt {n.to_s}")
93 while process_debug_command(gets) do end
94 end
95 else if self.stop_after_step_out_trigger then
96 if frames.length < self.step_stack_count then
97 n.debug("Execute stmt {n.to_s}")
98 while process_debug_command(gets) do end
99 end
100 end
101 end
102
103 #######################################################################
104 ## Processing commands functions ##
105 #######################################################################
106
107 # Takes a user command as a parameter
108 #
109 # Returns a boolean value, representing whether or not to
110 # continue reading commands from the console input
111 fun process_debug_command(command:String): Bool
112 do
113 # For lisibility
114 print "\n"
115
116 # Kills the current program
117 if command == "kill" then
118 abort
119 # Step-out command
120 else if command == "finish"
121 then
122 return step_out
123 # Step-over command
124 else if command == "n" then
125 return step_over
126 # Continues execution until the end
127 else if command == "c" then
128 return continue_exec
129 else
130 var parts_of_command = command.split_with(' ')
131 # Shows the value of a variable in the current frame
132 if parts_of_command[0] == "p" or parts_of_command[0] == "print" then
133 print_command(parts_of_command)
134 end
135 end
136 return true
137 end
138
139 #######################################################################
140 ## Processing specific command functions ##
141 #######################################################################
142
143 # Sets the flags to step-over an instruction in the current file
144 fun step_over: Bool
145 do
146 self.step_stack_count = frames.length
147 self.stop_after_step_over_trigger = true
148 self.stop_after_step_out_trigger = false
149 return false
150 end
151
152 #Sets the flags to step-out of a function
153 fun step_out: Bool
154 do
155 self.stop_after_step_over_trigger = false
156 self.stop_after_step_out_trigger = true
157 self.step_stack_count = frames.length
158 return false
159 end
160
161 # Sets the flags to continue execution
162 fun continue_exec: Bool
163 do
164 self.stop_after_step_over_trigger = false
165 self.stop_after_step_out_trigger = false
166 return false
167 end
168
169 # Prints the demanded variable in the command
170 #
171 # The name of the variable in in position 1 of the array 'parts_of_command'
172 fun print_command(parts_of_command: Array[String])
173 do
174 if parts_of_command[1] == "*" then
175 var map_of_instances = frame.map
176
177 var keys = map_of_instances.iterator
178
179 print "Variables collection : \n"
180
181 for instance in map_of_instances.keys do
182 print "Variable {instance.to_s}, Instance {map_of_instances[instance].to_s}"
183 end
184
185 print "\nEnd of current instruction \n"
186 else
187 var instance = seek_variable(parts_of_command[1], frame)
188
189 if instance != null
190 then
191 print_instance(instance)
192 end
193 end
194 end
195
196 #######################################################################
197 ## Print functions ##
198 #######################################################################
199
200 # Prints an object instance and its attributes if it has some
201 #
202 # If it is a primitive type, its value is directly printed
203 fun print_instance(instance: Instance)
204 do
205 if instance isa MutableInstance then
206 var attributes = instance.attributes
207 print "Object : {instance}"
208
209 for current_attribute in attributes.keys do
210 print "Attribute : {current_attribute.to_s} \nValeur : {attributes[current_attribute].to_s}"
211 end
212 else
213 print "Found variable {instance}"
214 end
215 end
216
217 #######################################################################
218 ## Variable Exploration functions ##
219 #######################################################################
220
221 # Seeks a variable from the current frame called 'variable_path', can introspect complex objects using function get_variable_in_mutable_instance
222 private fun seek_variable(variable_path: String, frame: Frame): nullable Instance
223 do
224 var full_variable = variable_path.split_with(".")
225
226 var full_variable_iterator = full_variable.iterator
227
228 var first_instance = get_variable_in_frame(full_variable_iterator.item, frame)
229
230 if first_instance == null then return null
231
232 if full_variable.length <= 1 then return first_instance
233
234 full_variable_iterator.next
235
236 if not (first_instance isa MutableInstance and full_variable_iterator.is_ok) then return null
237
238 return get_variable_in_mutable_instance(first_instance, full_variable_iterator)
239 end
240
241 # Gets a variable 'variable_name' contained in the frame 'frame'
242 private fun get_variable_in_frame(variable_name: String, frame: Frame): nullable Instance
243 do
244 if variable_name == "self" then
245 if frame.arguments.length >= 1 then return frame.arguments.first
246 end
247
248 var map_of_instances = frame.map
249
250 for key in map_of_instances.keys do
251 if key.to_s == variable_name then
252 return map_of_instances[key]
253 end
254 end
255
256 return null
257 end
258
259 # Gets an attribute 'attribute_name' contained in variable 'variable'
260 fun get_attribute_in_mutable_instance(variable: MutableInstance, attribute_name: String): nullable MAttribute
261 do
262 var map_of_attributes = variable.attributes
263
264 for key in map_of_attributes.keys do
265 if key.to_s.substring_from(1) == attribute_name then
266 return key
267 end
268 end
269
270 return null
271 end
272
273 # Recursive function, returns the variable described by 'total_chain'
274 fun get_variable_in_mutable_instance(variable: MutableInstance, iterator: Iterator[String]): nullable Instance
275 do
276 var attribute = get_attribute_in_mutable_instance(variable, iterator.item)
277
278 if attribute == null then return null
279
280 iterator.next
281
282 if iterator.is_ok then
283 var new_variable = variable.attributes[attribute]
284 if new_variable isa MutableInstance then
285 return get_variable_in_mutable_instance(new_variable, iterator)
286 else
287 return null
288 end
289 else
290 return variable.attributes[attribute]
291 end
292 end
293
294 end