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