b16a797708c94cfe5babfdd5d5a1bf20ec1eb18f
[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 import breakpoint
21 intrude import naive_interpreter
22 import nitx
23 intrude import toolcontext
24
25 redef class ToolContext
26 private var dbg: nullable Debugger = null
27
28 private var had_error: Bool = false
29
30 redef fun check_errors
31 do
32 if dbg == null then
33 super
34 else
35 if messages.length > 0 then
36 message_sorter.sort(messages)
37
38 for m in messages do
39 if "Warning".search_in(m.text, 0) == null then had_error = true
40 stderr.write("{m.to_color_string}\n")
41 end
42 end
43
44 messages.clear
45 end
46 end
47
48 # -d
49 var opt_debugger_mode: OptionBool = new OptionBool("Launches the target program with the debugger attached to it", "-d")
50 # -c
51 var opt_debugger_autorun: OptionBool = new OptionBool("Launches the target program with the interpreter, such as when the program fails, the debugging prompt is summoned", "-c")
52
53 redef init
54 do
55 super
56 self.option_context.add_option(self.opt_debugger_mode)
57 self.option_context.add_option(self.opt_debugger_autorun)
58 end
59 end
60
61 redef class ModelBuilder
62 # Execute the program from the entry point (Sys::main) of the `mainmodule`
63 # `arguments` are the command-line arguments in order
64 # REQUIRE that:
65 # 1. the AST is fully loaded.
66 # 2. the model is fully built.
67 # 3. the instructions are fully analysed.
68 fun run_debugger(mainmodule: MModule, arguments: Array[String])
69 do
70 var time0 = get_time
71 self.toolcontext.info("*** START INTERPRETING ***", 1)
72
73 var interpreter = new Debugger(self, mainmodule, arguments)
74
75 init_naive_interpreter(interpreter, mainmodule)
76
77 var time1 = get_time
78 self.toolcontext.info("*** END INTERPRETING: {time1-time0} ***", 2)
79 end
80
81 fun run_debugger_autorun(mainmodule: MModule, arguments: Array[String])
82 do
83 var time0 = get_time
84 self.toolcontext.info("*** START INTERPRETING ***", 1)
85
86 var interpreter = new Debugger(self, mainmodule, arguments)
87 interpreter.autocontinue = true
88
89 init_naive_interpreter(interpreter, mainmodule)
90
91 var time1 = get_time
92 self.toolcontext.info("*** END INTERPRETING: {time1-time0} ***", 2)
93 end
94 end
95
96 # The class extending `NaiveInterpreter` by adding debugging methods
97 class Debugger
98 super NaiveInterpreter
99
100 # Keeps the frame count in memory to find when to stop
101 # and launch the command prompt after a step out call
102 var step_stack_count = 1
103
104 # Triggers a step over an instruction in a nit program
105 var stop_after_step_over_trigger = true
106
107 # Triggers a step out of an instruction
108 var stop_after_step_out_trigger= false
109
110 # Triggers a step in a instruction (enters a function
111 # if the instruction is a function call)
112 var step_in_trigger = false
113
114 # HashMap containing the breakpoints bound to a file
115 var breakpoints = new HashMap[String, HashSet[Breakpoint]]
116
117 # Contains the current file
118 var curr_file = ""
119
120 # Aliases hashmap (maps an alias to a variable name)
121 var aliases = new HashMap[String, String]
122
123 # Set containing all the traced variables and their related frame
124 private var traces = new HashSet[TraceObject]
125
126 # Map containing all the positions for the positions of the arguments traced
127 # In a function call
128 private var fun_call_arguments_positions = new HashMap[Int, TraceObject]
129
130 # Triggers the remapping of a trace object in the local context after a function call
131 var aftermath = false
132
133 # Used to prevent the case when the body of the function called is empty
134 # If it is not, then, the remapping won't be happening
135 var frame_count_aftermath = 1
136
137 # Auto continues the execution until the end or until an error is encountered
138 var autocontinue = false
139
140 #######################################################################
141 ## Execution of statement function ##
142 #######################################################################
143
144 # Main loop, every call to a debug command is done here
145 redef fun stmt(n: nullable AExpr)
146 do
147 if n == null then return
148
149 var frame = self.frame
150 var old = frame.current_node
151 frame.current_node = n
152
153 if not self.autocontinue then
154 if not n isa ABlockExpr then
155 steps_fun_call(n)
156
157 breakpoint_check(n)
158
159 check_funcall_and_traced_args(n)
160
161 remap(n)
162
163 check_if_vars_are_traced(n)
164 end
165 end
166
167 n.stmt(self)
168 frame.current_node = old
169 end
170
171 # Encpasulates the behaviour for step over/out
172 private fun steps_fun_call(n: AExpr)
173 do
174 if self.stop_after_step_over_trigger then
175 if self.frames.length <= self.step_stack_count then
176 n.debug("Execute stmt {n.to_s}")
177 while read_cmd do end
178 end
179 else if self.stop_after_step_out_trigger then
180 if frames.length < self.step_stack_count then
181 n.debug("Execute stmt {n.to_s}")
182 while read_cmd do end
183 end
184 else if step_in_trigger then
185 n.debug("Execute stmt {n.to_s}")
186 while read_cmd do end
187 end
188 end
189
190 # Checks if a breakpoint is encountered, and launches the debugging prompt if true
191 private fun breakpoint_check(n: AExpr)
192 do
193 var currFileNameSplit = self.frame.current_node.location.file.filename.to_s.split_with("/")
194
195 self.curr_file = currFileNameSplit[currFileNameSplit.length-1]
196
197 var breakpoint = find_breakpoint(curr_file, n.location.line_start)
198
199 if breakpoints.keys.has(curr_file) and breakpoint != null then
200
201 breakpoint.check_in
202
203 if not breakpoint.is_valid
204 then
205 remove_breakpoint(curr_file, n.location.line_start)
206 end
207
208 n.debug("Execute stmt {n.to_s}")
209 while read_cmd do end
210 end
211 end
212
213 # Check if a variable of current expression is traced
214 # Then prints and/or breaks for command prompt
215 private fun check_if_vars_are_traced(n: AExpr)
216 do
217 var identifiers_in_instruction = get_identifiers_in_current_instruction(n.location.text)
218
219 for i in identifiers_in_instruction do
220 var variable = seek_variable(i, frame)
221 for j in self.traces do
222 if j.is_variable_traced_in_frame(i, frame) then
223 n.debug("Traced variable {i} used")
224 if j.break_on_encounter then while read_cmd do end
225 break
226 end
227 end
228 end
229 end
230
231 # Function remapping all the traced objects to match their name in the local context
232 private fun remap(n: AExpr)
233 do
234 if self.aftermath then
235
236 # Trace every argument variable pre-specified
237 if self.frame_count_aftermath < frames.length and fun_call_arguments_positions.length > 0 then
238
239 var ids_in_fun_def = get_identifiers_in_current_instruction(get_function_arguments(frame.mpropdef.location.text))
240
241 for i in fun_call_arguments_positions.keys do
242 self.fun_call_arguments_positions[i].add_frame_variable(frame, ids_in_fun_def[i])
243 end
244 end
245
246 self.aftermath = false
247 end
248 end
249
250 # If the current instruction is a function call
251 # We analyse its signature and the position of traced arguments if the call
252 # For future remapping when inside the function
253 private fun check_funcall_and_traced_args(n: AExpr) do
254 # If we have a function call, we need to see if any of the arguments is traced (including the caller)
255 # if it is, next time we face an instruction, we'll trace the local version on the traced variable in the next frame
256 if n isa ACallExpr then
257 self.aftermath = true
258 self.frame_count_aftermath = frames.length
259 fun_call_arguments_positions.clear
260 var fun_arguments = get_identifiers_in_current_instruction(get_function_arguments(n.location.text))
261
262 for i in self.traces do
263 for j in [0 .. fun_arguments.length - 1] do
264 if i.is_variable_traced_in_frame(fun_arguments[j],frame) then
265 fun_call_arguments_positions[j] = i
266 end
267 end
268 end
269 end
270 end
271
272 #######################################################################
273 ## Processing commands functions ##
274 #######################################################################
275
276 fun read_cmd: Bool
277 do
278 printn "> "
279 return process_debug_command(gets)
280 end
281
282 # Takes a user command as a parameter
283 #
284 # Returns a boolean value, representing whether or not to
285 # continue reading commands from the console input
286 fun process_debug_command(command:String): Bool
287 do
288 # For lisibility
289 print "\n"
290
291 # Kills the current program
292 if command == "kill" then
293 abort
294 # Step-out command
295 else if command == "finish"
296 then
297 return step_out
298 # Step-in command
299 else if command == "s"
300 then
301 return step_in
302 # Step-over command
303 else if command == "n" then
304 return step_over
305 # Opens a new NitIndex prompt on current model
306 else if command == "nitx" then
307 new NitIndex.with_infos(modelbuilder, self.mainmodule).prompt
308 return true
309 # Continues execution until the end
310 else if command == "c" then
311 return continue_exec
312 else
313 var parts_of_command = command.split_with(' ')
314 # Shows the value of a variable in the current frame
315 if parts_of_command[0] == "p" or parts_of_command[0] == "print" then
316 print_command(parts_of_command)
317 # Places a breakpoint on line x of file y
318 else if parts_of_command[0] == "break" or parts_of_command[0] == "b"
319 then
320 process_place_break_fun(parts_of_command)
321 # Places a temporary breakpoint on line x of file y
322 else if parts_of_command[0] == "tbreak" and (parts_of_command.length == 2 or parts_of_command.length == 3)
323 then
324 process_place_tbreak_fun(parts_of_command)
325 # Removes a breakpoint on line x of file y
326 else if parts_of_command[0] == "d" or parts_of_command[0] == "delete" then
327 process_remove_break_fun(parts_of_command)
328 # Sets an alias for a variable
329 else if parts_of_command.length == 3 and parts_of_command[1] == "as"
330 then
331 add_alias(parts_of_command[0], parts_of_command[2])
332 # Modifies the value of a variable in the current frame
333 else if parts_of_command.length >= 3 and parts_of_command[1] == "=" then
334 process_mod_function(parts_of_command)
335 # Traces the modifications on a variable
336 else if parts_of_command.length >= 2 and parts_of_command[0] == "trace" then
337 process_trace_command(parts_of_command)
338 # Untraces the modifications on a variable
339 else if parts_of_command.length == 2 and parts_of_command[0] == "untrace" then
340 process_untrace_command(parts_of_command)
341 # Lists all the commands available
342 else
343 list_commands
344 end
345 end
346 return true
347 end
348
349 #######################################################################
350 ## Processing specific command functions ##
351 #######################################################################
352
353 # Sets the flags to step-over an instruction in the current file
354 fun step_over: Bool
355 do
356 self.step_stack_count = frames.length
357 self.stop_after_step_over_trigger = true
358 self.stop_after_step_out_trigger = false
359 self.step_in_trigger = false
360 return false
361 end
362
363 #Sets the flags to step-out of a function
364 fun step_out: Bool
365 do
366 self.stop_after_step_over_trigger = false
367 self.stop_after_step_out_trigger = true
368 self.step_in_trigger = false
369 self.step_stack_count = frames.length
370 return false
371 end
372
373 # Sets the flags to step-in an instruction
374 fun step_in: Bool
375 do
376 self.step_in_trigger = true
377 self.stop_after_step_over_trigger = false
378 self.stop_after_step_out_trigger = false
379 return false
380 end
381
382 # Sets the flags to continue execution
383 fun continue_exec: Bool
384 do
385 self.stop_after_step_over_trigger = false
386 self.stop_after_step_out_trigger = false
387 self.step_in_trigger = false
388 return false
389 end
390
391 # Prints the demanded variable in the command
392 #
393 # The name of the variable in in position 1 of the array 'parts_of_command'
394 fun print_command(parts_of_command: Array[String])
395 do
396 if parts_of_command[1] == "*" then
397 var map_of_instances = frame.map
398
399 var keys = map_of_instances.iterator
400
401 print "Variables collection : \n"
402
403 for instance in map_of_instances.keys do
404 print "Variable {instance.to_s}, Instance {map_of_instances[instance].to_s}"
405 end
406
407 print "\nEnd of current instruction \n"
408 else if parts_of_command[1] == "stack" then
409 print self.stack_trace
410 else if parts_of_command[1].has('[') and parts_of_command[1].has(']') then
411 process_array_command(parts_of_command)
412 else
413 var instance = seek_variable(get_real_variable_name(parts_of_command[1]), frame)
414
415 if instance != null
416 then
417 print_instance(instance)
418 else
419 print "Cannot find variable {parts_of_command[1]}"
420 end
421 end
422 end
423
424 # Processes the input string to know where to put a breakpoint
425 fun process_place_break_fun(parts_of_command: Array[String])
426 do
427 var bp = get_breakpoint_from_command(parts_of_command)
428 if bp != null then
429 place_breakpoint(bp)
430 else
431 list_commands
432 end
433 end
434
435 # Returns a breakpoint containing the informations stored in the command
436 fun get_breakpoint_from_command(parts_of_command: Array[String]): nullable Breakpoint
437 do
438 if parts_of_command[1].is_numeric then
439 return new Breakpoint(parts_of_command[1].to_i, curr_file)
440 else if parts_of_command.length >= 3 and parts_of_command[2].is_numeric then
441 return new Breakpoint(parts_of_command[2].to_i, parts_of_command[1])
442 else
443 return null
444 end
445 end
446
447 # Processes the command of removing a breakpoint on specified line and file
448 fun process_remove_break_fun(parts_of_command: Array[String])
449 do
450 if parts_of_command[1].is_numeric then
451 remove_breakpoint(self.curr_file, parts_of_command[1].to_i)
452 else if parts_of_command.length >= 3 and parts_of_command[2].is_numeric then
453 remove_breakpoint(parts_of_command[1], parts_of_command[2].to_i)
454 else
455 list_commands
456 end
457 end
458
459 # Processes an array print command
460 fun process_array_command(parts_of_command: Array[String])
461 do
462 var index_of_first_brace = parts_of_command[1].index_of('[')
463 var variable_name = get_real_variable_name(parts_of_command[1].substring(0,index_of_first_brace))
464 var braces = parts_of_command[1].substring_from(index_of_first_brace)
465
466 var indexes = remove_braces(braces)
467
468 var index_array = new Array[Array[Int]]
469
470 if indexes != null then
471 for index in indexes do
472 var temp_indexes_array = process_index(index)
473 if temp_indexes_array != null then
474 index_array.push(temp_indexes_array)
475 #print index_array.last
476 end
477 end
478 end
479
480 var instance = seek_variable(variable_name, frame)
481
482 if instance != null then
483 print_nested_collection(instance, index_array, 0, variable_name, "")
484 else
485 print "Cannot find variable {variable_name}"
486 end
487 end
488
489 # Processes the modification function to modify a variable dynamically
490 #
491 # Command of type variable = value
492 fun process_mod_function(parts_of_command: Array[String])
493 do
494 parts_of_command[0] = get_real_variable_name(parts_of_command[0])
495 var parts_of_variable = parts_of_command[0].split_with(".")
496
497 if parts_of_variable.length > 1 then
498 var last_part = parts_of_variable.pop
499 var first_part = parts_of_command[0].substring(0,parts_of_command[0].length - last_part.length - 1)
500 var papa = seek_variable(first_part, frame)
501
502 if papa != null and papa isa MutableInstance then
503 var attribute = get_attribute_in_mutable_instance(papa, last_part)
504
505 if attribute != null then
506 modify_argument_of_complex_type(papa, attribute, parts_of_command[2])
507 end
508 end
509 else
510 var target = seek_variable(parts_of_variable[0], frame)
511 if target != null then
512 modify_in_frame(target, parts_of_command[2])
513 end
514 end
515 end
516
517 # Processes the untrace variable command
518 #
519 # Command pattern : "untrace variable"
520 fun process_untrace_command(parts_of_command: Array[String])
521 do
522 var variable_name = get_real_variable_name(parts_of_command[1])
523 if untrace_variable(variable_name) then
524 print "Untraced variable {parts_of_command[1]}"
525 else
526 print "{parts_of_command[1]} is not traced"
527 end
528 end
529
530 # Processes the trace variable command
531 #
532 # Command pattern : "trace variable [break/print]"
533 fun process_trace_command(parts_of_command: Array[String])
534 do
535 var variable_name = get_real_variable_name(parts_of_command[1])
536 var breaker:Bool
537
538 if seek_variable(variable_name, frame) == null then
539 print "Cannot find a variable called {parts_of_command[1]}"
540 return
541 end
542
543 if parts_of_command.length == 3 then
544 if parts_of_command[2] == "break" then
545 breaker = true
546 else
547 breaker = false
548 end
549 else
550 breaker = false
551 end
552
553 trace_variable(variable_name, breaker)
554
555 print "Successfully tracing {parts_of_command[1]}"
556 end
557
558 #######################################################################
559 ## Trace Management functions ##
560 #######################################################################
561
562 # Effectively untraces the variable called *variable_name*
563 #
564 # Returns true if the variable exists, false otherwise
565 private fun untrace_variable(variable_name: String): Bool
566 do
567 var to_remove: nullable TraceObject = null
568 for i in self.traces do
569 if i.is_variable_traced_in_frame(variable_name, frame) then
570 to_remove = i
571 end
572 end
573
574 if to_remove != null then
575 self.traces.remove(to_remove)
576 return true
577 else
578 return false
579 end
580 end
581
582 # Effectively traces the variable *variable_name* either in print or break mode depending on the value of breaker (break if true, print if false)
583 #
584 private fun trace_variable(variable_name: String, breaker: Bool)
585 do
586 for i in self.traces do
587 if i.is_variable_traced_in_frame(variable_name, frame) then
588 print "This variable is already traced"
589 return
590 end
591 end
592
593 var trace_object: TraceObject
594
595 if breaker then
596 trace_object = new TraceObject(true)
597 else
598 trace_object = new TraceObject(false)
599 end
600
601 # We trace the current variable found for the current frame
602 trace_object.add_frame_variable(self.frame, variable_name)
603
604 var position_of_variable_in_arguments = get_position_of_variable_in_arguments(frame, variable_name)
605
606 # Start parsing the frames starting with the parent of the current one, until the highest
607 # When the variable traced is declared locally, the loop stops
608 for i in [1 .. frames.length-1] do
609
610 # If the variable was reported to be an argument of the previous frame
611 if position_of_variable_in_arguments != -1 then
612
613 var local_name = get_identifiers_in_current_instruction(get_function_arguments(frames[i].current_node.location.text))[position_of_variable_in_arguments]
614
615 position_of_variable_in_arguments = get_position_of_variable_in_arguments(frames[i], local_name)
616
617 trace_object.add_frame_variable(frames[i], local_name)
618 else
619 break
620 end
621 end
622
623 self.traces.add(trace_object)
624 end
625
626 # If the variable *variable_name* is an argument of the function being executed in the frame *frame*
627 # The function returns its position in the arguments
628 # Else, it returns -1
629 private fun get_position_of_variable_in_arguments(frame: Frame, variable_name: String): Int
630 do
631 var identifiers = get_identifiers_in_current_instruction(get_function_arguments(frame.mpropdef.location.text))
632 for i in [0 .. identifiers.length-1] do
633 # If the current traced variable is an argument of the current function, we trace its parent (at least)
634 if identifiers[i] == variable_name then return i
635 end
636 return -1
637 end
638
639 # Gets all the identifiers of an instruction (uses the rules of Nit as of Mar 05 2013)
640 #
641 fun get_identifiers_in_current_instruction(instruction: AbstractString): Array[String]
642 do
643 var result_array = new Array[String]
644 var instruction_buffer = new Buffer
645
646 var trigger_char_escape = false
647 var trigger_string_escape = false
648 var trigger_concat_in_string = false
649
650 for i in instruction do
651 if trigger_char_escape then
652 if i == '\'' then trigger_char_escape = false
653 else if trigger_string_escape then
654 if i == '{' then
655 trigger_concat_in_string = true
656 trigger_string_escape = false
657 else if i == '\"' then trigger_string_escape = false
658 else
659 if i.is_alphanumeric or i == '_' then
660 instruction_buffer.add(i)
661 else if i == '.' then
662 if instruction_buffer.is_numeric or (instruction_buffer[0] >= 'A' and instruction_buffer[0] <= 'Z') then
663 instruction_buffer.clear
664 else
665 result_array.push(instruction_buffer.to_s)
666 instruction_buffer.add(i)
667 end
668 else if i == '\'' then
669 trigger_char_escape = true
670 else if i == '\"' then
671 trigger_string_escape = true
672 else if i == '}' then
673 trigger_concat_in_string = false
674 trigger_string_escape = true
675 else
676 if instruction_buffer.length > 0 and not instruction_buffer.is_numeric and not (instruction_buffer[0] >= 'A' and instruction_buffer[0] <= 'Z') then result_array.push(instruction_buffer.to_s)
677 instruction_buffer.clear
678 end
679 end
680 end
681
682 if instruction_buffer.length > 0 and not instruction_buffer.is_numeric and not (instruction_buffer[0] >= 'A' and instruction_buffer[0] <= 'Z') then result_array.push(instruction_buffer.to_s)
683
684 return result_array
685 end
686
687 # Takes a function call or declaration and strips all but the arguments
688 #
689 fun get_function_arguments(function: AbstractString): String
690 do
691 var buf = new Buffer
692 var trigger_copy = false
693
694 for i in function do
695 if i == ')' then break
696 if trigger_copy then buf.add(i)
697 if i == '(' then trigger_copy = true
698 end
699
700 return buf.to_s
701 end
702
703 #######################################################################
704 ## Alias management functions ##
705 #######################################################################
706
707 # Adds a new alias to the tables
708 fun add_alias(var_represented: String, alias: String)
709 do
710 self.aliases[alias] = var_represented
711 end
712
713 # Gets the real name of a variable hidden by an alias
714 fun get_variable_name_by_alias(alias: String): nullable String
715 do
716 if self.aliases.keys.has(alias) then
717 return self.aliases[alias]
718 end
719
720 return null
721 end
722
723 # Gets the variable named by name, whether it is an alias or not
724 fun get_real_variable_name(name: String): String
725 do
726 var explode_string = name.split_with(".")
727 var final_string = new Buffer
728 for i in explode_string do
729 var alias_resolved = get_variable_name_by_alias(i)
730 if alias_resolved != null then
731 final_string.append(get_real_variable_name(alias_resolved))
732 else
733 final_string.append(i)
734 end
735 final_string.append(".")
736 end
737
738 return final_string.substring(0,final_string.length-1).to_s
739 end
740
741 #######################################################################
742 ## Print functions ##
743 #######################################################################
744
745 # Prints an object instance and its attributes if it has some
746 #
747 # If it is a primitive type, its value is directly printed
748 fun print_instance(instance: Instance)
749 do
750 print "Printing innards of a variable"
751
752 if instance isa MutableInstance then
753 var attributes = instance.attributes
754 print "Object : {instance}"
755
756 for current_attribute in attributes.keys do
757 print "Attribute : {current_attribute.to_s} \nValeur : {attributes[current_attribute].to_s}"
758 end
759 else
760 print "Found variable {instance}"
761 end
762
763 print "Stopping printing innards of a variable"
764 end
765
766 # Prints the attributes demanded in a SequenceRead
767 # Used recursively to print nested collections
768 fun print_nested_collection(instance: Instance, indexes: Array[Array[Int]], depth: Int, variable_name: String, depth_string: String)
769 do
770 var collection: nullable SequenceRead[Object] = null
771 var real_collection_length: nullable Int = null
772
773 if instance isa MutableInstance then
774 real_collection_length = get_collection_instance_real_length(instance)
775 collection = get_primary_collection(instance)
776 end
777
778 if collection != null and real_collection_length != null then
779 for i in indexes[depth] do
780 if i >= 0 and i < real_collection_length then
781 if depth == indexes.length-1 then
782 print "{variable_name}{depth_string}[{i}] = {collection[i]}"
783 else
784 var item_i = collection[i]
785
786 if item_i isa MutableInstance then
787 print_nested_collection(item_i, indexes, depth+1, variable_name, depth_string+"[{i}]")
788 else
789 print "The item at {variable_name}{depth_string}[{i}] is not a collection"
790 print item_i
791 end
792 end
793 else
794 print "Out of bounds exception : i = {i} and collection_length = {real_collection_length.to_s}"
795
796 if i < 0 then
797 continue
798 else if i >= real_collection_length then
799 break
800 end
801 end
802 end
803 else
804 if collection == null then
805 print "Cannot find {variable_name}{depth_string}"
806 else if real_collection_length != null then
807 print "Cannot find attribute length in {instance}"
808 else
809 print "Unknown error."
810 abort
811 end
812 end
813 end
814
815 #######################################################################
816 ## Variable Exploration functions ##
817 #######################################################################
818
819 # Seeks a variable from the current frame called 'variable_path', can introspect complex objects using function get_variable_in_mutable_instance
820 private fun seek_variable(variable_path: String, frame: Frame): nullable Instance
821 do
822 var full_variable = variable_path.split_with(".")
823
824 var full_variable_iterator = full_variable.iterator
825
826 var first_instance = get_variable_in_frame(full_variable_iterator.item, frame)
827
828 if first_instance == null then return null
829
830 if full_variable.length <= 1 then return first_instance
831
832 full_variable_iterator.next
833
834 if not (first_instance isa MutableInstance and full_variable_iterator.is_ok) then return null
835
836 return get_variable_in_mutable_instance(first_instance, full_variable_iterator)
837 end
838
839 # Gets a variable 'variable_name' contained in the frame 'frame'
840 private fun get_variable_in_frame(variable_name: String, frame: Frame): nullable Instance
841 do
842 if variable_name == "self" then
843 if frame.arguments.length >= 1 then return frame.arguments.first
844 end
845
846 var map_of_instances = frame.map
847
848 for key in map_of_instances.keys do
849 if key.to_s == variable_name then
850 return map_of_instances[key]
851 end
852 end
853
854 return null
855 end
856
857 # Gets an attribute 'attribute_name' contained in variable 'variable'
858 fun get_attribute_in_mutable_instance(variable: MutableInstance, attribute_name: String): nullable MAttribute
859 do
860 var map_of_attributes = variable.attributes
861
862 for key in map_of_attributes.keys do
863 if key.to_s.substring_from(1) == attribute_name then
864 return key
865 end
866 end
867
868 return null
869 end
870
871 # Recursive function, returns the variable described by 'total_chain'
872 fun get_variable_in_mutable_instance(variable: MutableInstance, iterator: Iterator[String]): nullable Instance
873 do
874 var attribute = get_attribute_in_mutable_instance(variable, iterator.item)
875
876 if attribute == null then return null
877
878 iterator.next
879
880 if iterator.is_ok then
881 var new_variable = variable.attributes[attribute]
882 if new_variable isa MutableInstance then
883 return get_variable_in_mutable_instance(new_variable, iterator)
884 else
885 return null
886 end
887 else
888 return variable.attributes[attribute]
889 end
890 end
891
892 #######################################################################
893 ## Array exploring functions ##
894 #######################################################################
895
896 # Gets the length of a collection
897 # Used by the debugger, else if we call Collection.length, it returns the capacity instead
898 fun get_collection_instance_real_length(collection: MutableInstance): nullable Int
899 do
900 var collection_length_attribute = get_attribute_in_mutable_instance(collection, "length")
901
902 var real_collection_length: nullable Int = null
903
904 if collection_length_attribute != null then
905 var primitive_length_instance = collection.attributes[collection_length_attribute]
906 if primitive_length_instance isa PrimitiveInstance[Int] then
907 return primitive_length_instance.val
908 end
909 end
910
911 return null
912 end
913
914 # Processes the indexes of a print array call
915 # Returns an array containing all the indexes demanded
916 fun process_index(index_string: String): nullable Array[Int]
917 do
918 var from_end_index = index_string.index_of('.')
919 var to_start_index = index_string.last_index_of('.')
920
921 if from_end_index != -1 and to_start_index != -1 then
922 var index_from_string = index_string.substring(0,from_end_index)
923 var index_to_string = index_string.substring_from(to_start_index+1)
924
925 if index_from_string.is_numeric and index_to_string.is_numeric then
926 var result_array = new Array[Int]
927
928 var index_from = index_from_string.to_i
929 var index_to = index_to_string.to_i
930
931 for i in [index_from..index_to] do
932 result_array.push(i)
933 end
934
935 return result_array
936 end
937 else
938 if index_string.is_numeric
939 then
940 var result_array = new Array[Int]
941
942 result_array.push(index_string.to_i)
943
944 return result_array
945 else
946 return null
947 end
948 end
949
950 return null
951 end
952
953 # Gets a collection in a MutableInstance
954 fun get_primary_collection(container: MutableInstance): nullable SequenceRead[Object]
955 do
956 var items_of_array = get_attribute_in_mutable_instance(container, "items")
957 if items_of_array != null then
958 var array = container.attributes[items_of_array]
959
960 if array isa PrimitiveInstance[Object] then
961 var sequenceRead_final = array.val
962 if sequenceRead_final isa SequenceRead[Object] then
963 return sequenceRead_final
964 end
965 end
966 end
967
968 return null
969 end
970
971 # Removes the braces '[' ']' in a print array command
972 # Returns an array containing their content
973 fun remove_braces(braces: String): nullable Array[String]
974 do
975 var buffer = new Buffer
976
977 var result_array = new Array[String]
978
979 var number_of_opening_brackets = 0
980 var number_of_closing_brackets = 0
981
982 var last_was_opening_bracket = false
983
984 for i in braces do
985 if i == '[' then
986 if last_was_opening_bracket then
987 return null
988 end
989
990 number_of_opening_brackets += 1
991 last_was_opening_bracket = true
992 else if i == ']' then
993 if not last_was_opening_bracket then
994 return null
995 end
996
997 result_array.push(buffer.to_s)
998 buffer.clear
999 number_of_closing_brackets += 1
1000 last_was_opening_bracket = false
1001 else if i.is_numeric or i == '.' then
1002 buffer.append(i.to_s)
1003 else if not i == ' ' then
1004 return null
1005 end
1006 end
1007
1008 if number_of_opening_brackets != number_of_closing_brackets then
1009 return null
1010 end
1011
1012 return result_array
1013 end
1014
1015 #######################################################################
1016 ## Breakpoint placing functions ##
1017 #######################################################################
1018
1019 # Places a breakpoint on line 'line_to_break' for file 'file_to_break'
1020 fun place_breakpoint(breakpoint: Breakpoint)
1021 do
1022 if not self.breakpoints.keys.has(breakpoint.file) then
1023 self.breakpoints[breakpoint.file] = new HashSet[Breakpoint]
1024 end
1025 if find_breakpoint(breakpoint.file, breakpoint.line) == null then
1026 self.breakpoints[breakpoint.file].add(breakpoint)
1027 print "Breakpoint added on line {breakpoint.line} for file {breakpoint.file}"
1028 else
1029 print "Breakpoint already present on line {breakpoint.line} for file {breakpoint.file}"
1030 end
1031 end
1032
1033 #Places a breakpoint that will trigger once and be destroyed afterwards
1034 fun process_place_tbreak_fun(parts_of_command: Array[String])
1035 do
1036 var bp = get_breakpoint_from_command(parts_of_command)
1037 if bp != null
1038 then
1039 bp.set_max_breaks(1)
1040 place_breakpoint(bp)
1041 else
1042 list_commands
1043 end
1044 end
1045
1046 #######################################################################
1047 ## Breakpoint removing functions ##
1048 #######################################################################
1049
1050 # Removes a breakpoint on line 'line_to_break' for file 'file_to_break'
1051 fun remove_breakpoint(file_to_break: String, line_to_break: Int)
1052 do
1053 if self.breakpoints.keys.has(file_to_break) then
1054 var bp = find_breakpoint(file_to_break, line_to_break)
1055
1056 if bp != null then
1057 self.breakpoints[file_to_break].remove(bp)
1058 print "Breakpoint removed on line {line_to_break} for file {file_to_break}"
1059 return
1060 end
1061 end
1062
1063 print "No breakpoint existing on line {line_to_break} for file {file_to_break}"
1064 end
1065
1066 #######################################################################
1067 ## Breakpoint searching functions ##
1068 #######################################################################
1069
1070 # Finds a breakpoint for 'file' and 'line' in the class HashMap
1071 fun find_breakpoint(file: String, line: Int): nullable Breakpoint
1072 do
1073 if self.breakpoints.keys.has(file)
1074 then
1075 for i in self.breakpoints[file]
1076 do
1077 if i.line == line
1078 then
1079 return i
1080 end
1081 end
1082 end
1083
1084 return null
1085 end
1086
1087 #######################################################################
1088 ## Runtime modification functions ##
1089 #######################################################################
1090
1091 # Modifies the value of a variable contained in a frame
1092 fun modify_in_frame(variable: Instance, value: String)
1093 do
1094 var new_variable = get_variable_of_type_with_value(variable.mtype.to_s, value)
1095 if new_variable != null
1096 then
1097 var keys = frame.map.keys
1098 for key in keys
1099 do
1100 if frame.map[key] == variable
1101 then
1102 frame.map[key] = new_variable
1103 end
1104 end
1105 end
1106 end
1107
1108 # Modifies the value of a variable contained in a MutableInstance
1109 fun modify_argument_of_complex_type(papa: MutableInstance, attribute: MAttribute, value: String)
1110 do
1111 var final_variable = papa.attributes[attribute]
1112 var type_of_variable = final_variable.mtype.to_s
1113 var new_variable = get_variable_of_type_with_value(type_of_variable, value)
1114 if new_variable != null
1115 then
1116 papa.attributes[attribute] = new_variable
1117 end
1118 end
1119
1120 #######################################################################
1121 ## Variable generator functions ##
1122 #######################################################################
1123
1124 # Returns a new variable of the type 'type_of_variable' with a value of 'value'
1125 fun get_variable_of_type_with_value(type_of_variable: String, value: String): nullable Instance
1126 do
1127 if type_of_variable == "Int" then
1128 return get_int(value)
1129 else if type_of_variable == "Float" then
1130 return get_float(value)
1131 else if type_of_variable == "Bool" then
1132 return get_bool(value)
1133 else if type_of_variable == "Char" then
1134 return get_char(value)
1135 end
1136
1137 return null
1138 end
1139
1140 # Returns a new int instance with value 'value'
1141 fun get_int(value: String): nullable Instance
1142 do
1143 if value.is_numeric then
1144 return int_instance(value.to_i)
1145 else
1146 return null
1147 end
1148 end
1149
1150 # Returns a new float instance with value 'value'
1151 fun get_float(value:String): nullable Instance
1152 do
1153 if value.is_numeric then
1154 return float_instance(value.to_f)
1155 else
1156 return null
1157 end
1158 end
1159
1160 # Returns a new char instance with value 'value'
1161 fun get_char(value: String): nullable Instance
1162 do
1163 if value.length >= 1 then
1164 return char_instance(value[0])
1165 else
1166 return null
1167 end
1168 end
1169
1170 # Returns a new bool instance with value 'value'
1171 fun get_bool(value: String): nullable Instance
1172 do
1173 if value.to_lower == "true" then
1174 return self.true_instance
1175 else if value.to_lower == "false" then
1176 return self.false_instance
1177 else
1178 print "Invalid value, a boolean must be set at \"true\" or \"false\""
1179 return null
1180 end
1181 end
1182
1183 #######################################################################
1184 ## Command listing function ##
1185 #######################################################################
1186
1187 # Lists the commands available when using the debugger
1188 fun list_commands
1189 do
1190 print "\nCommand not recognized\n"
1191 print "Commands accepted : \n"
1192 print "[break/b] line : Adds a breakpoint on line *line_nb* of the current file\n"
1193 print "[break/b] file_name line_nb : Adds a breakpoint on line *line_nb* of file *file_name* \n"
1194 print "[p/print] variable : [p/print] * shows the status of all the variables\n"
1195 print "[p/print] variable[i] : Prints the value of the variable contained at position *i* in SequenceRead collection *variable*\n"
1196 print "[p/print] variable[i..j]: Prints the value of all the variables contained between positions *i* and *j* in SequenceRead collection *variable*\n"
1197 print "[p/print] stack: Prints a stack trace at current instruction\n"
1198 print "Note : The arrays can be multi-dimensional (Ex : variable[i..j][k] will print all the values at position *k* of all the SequenceRead collections contained between positions *i* and *j* in SequenceRead collection *variable*)\n"
1199 print "s : steps in on the current function\n"
1200 print "n : steps-over the current instruction\n"
1201 print "finish : steps out of the current function\n"
1202 print "variable as alias : Adds an alias called *alias* for the variable *variable*"
1203 print "An alias can reference another alias\n"
1204 print "variable = value : Sets the value of *variable* to *value*\n"
1205 print "[d/delete] line_nb : Removes a breakpoint on line *line_nb* of the current file \n"
1206 print "[d/delete] file_name line_nb : Removes a breakpoint on line *line_nb* of file *file_name* \n"
1207 print "trace variable_name [break/print] : Traces the uses of the variable you chose to trace by printing the statement it appears in or by breaking on each use."
1208 print "untrace variable_name : Removes the trace on the variable you chose to trace earlier in the program"
1209 print "kill : kills the current program (Exits with an error and stack trace)\n"
1210 end
1211
1212 end
1213
1214 # Traces the modifications of an object linked to a certain frame
1215 private class TraceObject
1216
1217 # Map of the local names bound to a frame
1218 var trace_map: HashMap[Frame, String]
1219 # Decides if breaking or printing statement when the variable is encountered
1220 var break_on_encounter: Bool
1221
1222 init(break_on_encounter: Bool)
1223 do
1224 trace_map = new HashMap[Frame, String]
1225 self.break_on_encounter = break_on_encounter
1226 end
1227
1228 # Adds the local alias for a variable and the frame bound to it
1229 fun add_frame_variable(frame: Frame, variable_name: String)
1230 do
1231 self.trace_map[frame] = variable_name
1232 end
1233
1234 # Checks if the prompted variable is traced in the specified frame
1235 fun is_variable_traced_in_frame(variable_name: String, frame: Frame): Bool
1236 do
1237 if self.trace_map.has_key(frame) then
1238 if self.trace_map[frame] == variable_name then
1239 return true
1240 end
1241 end
1242
1243 return false
1244 end
1245
1246 end
1247
1248 redef class ANode
1249
1250 # Breaks automatically when encountering an error
1251 # Permits the injunction of commands before crashing
1252 redef private fun fatal(v: NaiveInterpreter, message: String)
1253 do
1254 if v isa Debugger then
1255 print "An error was encountered, the program will stop now."
1256 self.debug(message)
1257 while v.process_debug_command(gets) do end
1258 end
1259
1260 super
1261 end
1262 end