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