nitdbg: Added the possibility to continue the execution without stopping on each...
[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 # Main loop, every call to a debug command is done here
66 redef fun stmt(n: nullable AExpr)
67 do
68 if n == null then return
69
70 var frame = self.frame
71 var old = frame.current_node
72 frame.current_node = n
73
74 steps_fun_call(n)
75
76 n.stmt(self)
77 frame.current_node = old
78 end
79
80 private fun steps_fun_call(n: AExpr)
81 do
82 if self.stop_after_step_over_trigger then
83 if self.frames.length <= self.step_stack_count then
84 n.debug("Execute stmt {n.to_s}")
85 while process_debug_command(gets) do end
86 end
87 end
88 end
89
90 #######################################################################
91 ## Processing commands functions ##
92 #######################################################################
93
94 # Takes a user command as a parameter
95 #
96 # Returns a boolean value, representing whether or not to
97 # continue reading commands from the console input
98 fun process_debug_command(command:String): Bool
99 do
100 # For lisibility
101 print "\n"
102
103 # Kills the current program
104 if command == "kill" then
105 abort
106 # Step-over command
107 else if command == "n" then
108 return step_over
109 # Continues execution until the end
110 else if command == "c" then
111 return continue_exec
112 else
113 var parts_of_command = command.split_with(' ')
114 # Shows the value of a variable in the current frame
115 if parts_of_command[0] == "p" or parts_of_command[0] == "print" then
116 print_command(parts_of_command)
117 end
118 end
119 return true
120 end
121
122 # Sets the flags to step-over an instruction in the current file
123 fun step_over: Bool
124 do
125 self.step_stack_count = frames.length
126 self.stop_after_step_over_trigger = true
127 return false
128 end
129
130 # Sets the flags to continue execution
131 fun continue_exec: Bool
132 do
133 self.stop_after_step_over_trigger = false
134 return false
135 end
136
137 # Prints the demanded variable in the command
138 #
139 # The name of the variable in in position 1 of the array 'parts_of_command'
140 fun print_command(parts_of_command: Array[String])
141 do
142 if parts_of_command[1] == "*" then
143 var map_of_instances = frame.map
144
145 var keys = map_of_instances.iterator
146
147 print "Variables collection : \n"
148
149 for instance in map_of_instances.keys do
150 print "Variable {instance.to_s}, Instance {map_of_instances[instance].to_s}"
151 end
152
153 print "\nEnd of current instruction \n"
154 else
155 var instance = seek_variable(parts_of_command[1], frame)
156
157 if instance != null
158 then
159 print_instance(instance)
160 end
161 end
162 end
163
164 #######################################################################
165 ## Print functions ##
166 #######################################################################
167
168 # Prints an object instance and its attributes if it has some
169 #
170 # If it is a primitive type, its value is directly printed
171 fun print_instance(instance: Instance)
172 do
173 if instance isa MutableInstance then
174 var attributes = instance.attributes
175 print "Object : {instance}"
176
177 for current_attribute in attributes.keys do
178 print "Attribute : {current_attribute.to_s} \nValeur : {attributes[current_attribute].to_s}"
179 end
180 else
181 print "Found variable {instance}"
182 end
183 end
184
185 #######################################################################
186 ## Variable Exploration functions ##
187 #######################################################################
188
189 # Seeks a variable from the current frame called 'variable_path', can introspect complex objects using function get_variable_in_mutable_instance
190 private fun seek_variable(variable_path: String, frame: Frame): nullable Instance
191 do
192 var full_variable = variable_path.split_with(".")
193
194 var full_variable_iterator = full_variable.iterator
195
196 var first_instance = get_variable_in_frame(full_variable_iterator.item, frame)
197
198 if first_instance == null then return null
199
200 if full_variable.length <= 1 then return first_instance
201
202 full_variable_iterator.next
203
204 if not (first_instance isa MutableInstance and full_variable_iterator.is_ok) then return null
205
206 return get_variable_in_mutable_instance(first_instance, full_variable_iterator)
207 end
208
209 # Gets a variable 'variable_name' contained in the frame 'frame'
210 private fun get_variable_in_frame(variable_name: String, frame: Frame): nullable Instance
211 do
212 if variable_name == "self" then
213 if frame.arguments.length >= 1 then return frame.arguments.first
214 end
215
216 var map_of_instances = frame.map
217
218 for key in map_of_instances.keys do
219 if key.to_s == variable_name then
220 return map_of_instances[key]
221 end
222 end
223
224 return null
225 end
226
227 # Gets an attribute 'attribute_name' contained in variable 'variable'
228 fun get_attribute_in_mutable_instance(variable: MutableInstance, attribute_name: String): nullable MAttribute
229 do
230 var map_of_attributes = variable.attributes
231
232 for key in map_of_attributes.keys do
233 if key.to_s.substring_from(1) == attribute_name then
234 return key
235 end
236 end
237
238 return null
239 end
240
241 # Recursive function, returns the variable described by 'total_chain'
242 fun get_variable_in_mutable_instance(variable: MutableInstance, iterator: Iterator[String]): nullable Instance
243 do
244 var attribute = get_attribute_in_mutable_instance(variable, iterator.item)
245
246 if attribute == null then return null
247
248 iterator.next
249
250 if iterator.is_ok then
251 var new_variable = variable.attributes[attribute]
252 if new_variable isa MutableInstance then
253 return get_variable_in_mutable_instance(new_variable, iterator)
254 else
255 return null
256 end
257 else
258 return variable.attributes[attribute]
259 end
260 end
261
262 end