ni_nitdoc: added fast copy past utility to signatures.
[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 if instance isa MutableInstance then
717 var attributes = instance.attributes
718 print "Object : {instance}"
719
720 for current_attribute in attributes.keys do
721 print "Attribute : {current_attribute.to_s} \nValeur : {attributes[current_attribute].to_s}"
722 end
723 else
724 print "Found variable {instance}"
725 end
726 end
727
728 # Prints the attributes demanded in a SequenceRead
729 # Used recursively to print nested collections
730 fun print_nested_collection(instance: Instance, indexes: Array[Array[Int]], depth: Int, variable_name: String, depth_string: String)
731 do
732 var collection: nullable SequenceRead[Object] = null
733 var real_collection_length: nullable Int = null
734
735 if instance isa MutableInstance then
736 real_collection_length = get_collection_instance_real_length(instance)
737 collection = get_primary_collection(instance)
738 end
739
740 if collection != null and real_collection_length != null then
741 for i in indexes[depth] do
742 if i >= 0 and i < real_collection_length then
743 if depth == indexes.length-1 then
744 print "{variable_name}{depth_string}[{i}] = {collection[i]}"
745 else
746 var item_i = collection[i]
747
748 if item_i isa MutableInstance then
749 print_nested_collection(item_i, indexes, depth+1, variable_name, depth_string+"[{i}]")
750 else
751 print "The item at {variable_name}{depth_string}[{i}] is not a collection"
752 print item_i
753 end
754 end
755 else
756 print "Out of bounds exception : i = {i} and collection_length = {real_collection_length.to_s}"
757
758 if i < 0 then
759 continue
760 else if i >= real_collection_length then
761 break
762 end
763 end
764 end
765 else
766 if collection == null then
767 print "Cannot find {variable_name}{depth_string}"
768 else if real_collection_length != null then
769 print "Cannot find attribute length in {instance}"
770 else
771 print "Unknown error."
772 abort
773 end
774 end
775 end
776
777 #######################################################################
778 ## Variable Exploration functions ##
779 #######################################################################
780
781 # Seeks a variable from the current frame called 'variable_path', can introspect complex objects using function get_variable_in_mutable_instance
782 private fun seek_variable(variable_path: String, frame: Frame): nullable Instance
783 do
784 var full_variable = variable_path.split_with(".")
785
786 var full_variable_iterator = full_variable.iterator
787
788 var first_instance = get_variable_in_frame(full_variable_iterator.item, frame)
789
790 if first_instance == null then return null
791
792 if full_variable.length <= 1 then return first_instance
793
794 full_variable_iterator.next
795
796 if not (first_instance isa MutableInstance and full_variable_iterator.is_ok) then return null
797
798 return get_variable_in_mutable_instance(first_instance, full_variable_iterator)
799 end
800
801 # Gets a variable 'variable_name' contained in the frame 'frame'
802 private fun get_variable_in_frame(variable_name: String, frame: Frame): nullable Instance
803 do
804 if variable_name == "self" then
805 if frame.arguments.length >= 1 then return frame.arguments.first
806 end
807
808 var map_of_instances = frame.map
809
810 for key in map_of_instances.keys do
811 if key.to_s == variable_name then
812 return map_of_instances[key]
813 end
814 end
815
816 return null
817 end
818
819 # Gets an attribute 'attribute_name' contained in variable 'variable'
820 fun get_attribute_in_mutable_instance(variable: MutableInstance, attribute_name: String): nullable MAttribute
821 do
822 var map_of_attributes = variable.attributes
823
824 for key in map_of_attributes.keys do
825 if key.to_s.substring_from(1) == attribute_name then
826 return key
827 end
828 end
829
830 return null
831 end
832
833 # Recursive function, returns the variable described by 'total_chain'
834 fun get_variable_in_mutable_instance(variable: MutableInstance, iterator: Iterator[String]): nullable Instance
835 do
836 var attribute = get_attribute_in_mutable_instance(variable, iterator.item)
837
838 if attribute == null then return null
839
840 iterator.next
841
842 if iterator.is_ok then
843 var new_variable = variable.attributes[attribute]
844 if new_variable isa MutableInstance then
845 return get_variable_in_mutable_instance(new_variable, iterator)
846 else
847 return null
848 end
849 else
850 return variable.attributes[attribute]
851 end
852 end
853
854 #######################################################################
855 ## Array exploring functions ##
856 #######################################################################
857
858 # Gets the length of a collection
859 # Used by the debugger, else if we call Collection.length, it returns the capacity instead
860 fun get_collection_instance_real_length(collection: MutableInstance): nullable Int
861 do
862 var collection_length_attribute = get_attribute_in_mutable_instance(collection, "length")
863
864 var real_collection_length: nullable Int = null
865
866 if collection_length_attribute != null then
867 var primitive_length_instance = collection.attributes[collection_length_attribute]
868 if primitive_length_instance isa PrimitiveInstance[Int] then
869 return primitive_length_instance.val
870 end
871 end
872
873 return null
874 end
875
876 # Processes the indexes of a print array call
877 # Returns an array containing all the indexes demanded
878 fun process_index(index_string: String): nullable Array[Int]
879 do
880 var from_end_index = index_string.index_of('.')
881 var to_start_index = index_string.last_index_of('.')
882
883 if from_end_index != -1 and to_start_index != -1 then
884 var index_from_string = index_string.substring(0,from_end_index)
885 var index_to_string = index_string.substring_from(to_start_index+1)
886
887 if index_from_string.is_numeric and index_to_string.is_numeric then
888 var result_array = new Array[Int]
889
890 var index_from = index_from_string.to_i
891 var index_to = index_to_string.to_i
892
893 for i in [index_from..index_to] do
894 result_array.push(i)
895 end
896
897 return result_array
898 end
899 else
900 if index_string.is_numeric
901 then
902 var result_array = new Array[Int]
903
904 result_array.push(index_string.to_i)
905
906 return result_array
907 else
908 return null
909 end
910 end
911
912 return null
913 end
914
915 # Gets a collection in a MutableInstance
916 fun get_primary_collection(container: MutableInstance): nullable SequenceRead[Object]
917 do
918 var items_of_array = get_attribute_in_mutable_instance(container, "items")
919 if items_of_array != null then
920 var array = container.attributes[items_of_array]
921
922 if array isa PrimitiveInstance[Object] then
923 var sequenceRead_final = array.val
924 if sequenceRead_final isa SequenceRead[Object] then
925 return sequenceRead_final
926 end
927 end
928 end
929
930 return null
931 end
932
933 # Removes the braces '[' ']' in a print array command
934 # Returns an array containing their content
935 fun remove_braces(braces: String): nullable Array[String]
936 do
937 var buffer = new Buffer
938
939 var result_array = new Array[String]
940
941 var number_of_opening_brackets = 0
942 var number_of_closing_brackets = 0
943
944 var last_was_opening_bracket = false
945
946 for i in braces do
947 if i == '[' then
948 if last_was_opening_bracket then
949 return null
950 end
951
952 number_of_opening_brackets += 1
953 last_was_opening_bracket = true
954 else if i == ']' then
955 if not last_was_opening_bracket then
956 return null
957 end
958
959 result_array.push(buffer.to_s)
960 buffer.clear
961 number_of_closing_brackets += 1
962 last_was_opening_bracket = false
963 else if i.is_numeric or i == '.' then
964 buffer.append(i.to_s)
965 else if not i == ' ' then
966 return null
967 end
968 end
969
970 if number_of_opening_brackets != number_of_closing_brackets then
971 return null
972 end
973
974 return result_array
975 end
976
977 #######################################################################
978 ## Breakpoint placing functions ##
979 #######################################################################
980
981 # Places a breakpoint on line 'line_to_break' for file 'file_to_break'
982 fun place_breakpoint(breakpoint: Breakpoint)
983 do
984 if not self.breakpoints.keys.has(breakpoint.file) then
985 self.breakpoints[breakpoint.file] = new HashSet[Breakpoint]
986 end
987 if find_breakpoint(breakpoint.file, breakpoint.line) == null then
988 self.breakpoints[breakpoint.file].add(breakpoint)
989 print "Breakpoint added on line {breakpoint.line} for file {breakpoint.file}"
990 else
991 print "Breakpoint already present on line {breakpoint.line} for file {breakpoint.file}"
992 end
993 end
994
995 #Places a breakpoint that will trigger once and be destroyed afterwards
996 fun process_place_tbreak_fun(parts_of_command: Array[String])
997 do
998 var bp = get_breakpoint_from_command(parts_of_command)
999 if bp != null
1000 then
1001 bp.set_max_breaks(1)
1002 place_breakpoint(bp)
1003 else
1004 list_commands
1005 end
1006 end
1007
1008 #######################################################################
1009 ## Breakpoint removing functions ##
1010 #######################################################################
1011
1012 # Removes a breakpoint on line 'line_to_break' for file 'file_to_break'
1013 fun remove_breakpoint(file_to_break: String, line_to_break: Int)
1014 do
1015 if self.breakpoints.keys.has(file_to_break) then
1016 var bp = find_breakpoint(file_to_break, line_to_break)
1017
1018 if bp != null then
1019 self.breakpoints[file_to_break].remove(bp)
1020 print "Breakpoint removed on line {line_to_break} for file {file_to_break}"
1021 return
1022 end
1023 end
1024
1025 print "No breakpoint existing on line {line_to_break} for file {file_to_break}"
1026 end
1027
1028 #######################################################################
1029 ## Breakpoint searching functions ##
1030 #######################################################################
1031
1032 # Finds a breakpoint for 'file' and 'line' in the class HashMap
1033 fun find_breakpoint(file: String, line: Int): nullable Breakpoint
1034 do
1035 if self.breakpoints.keys.has(file)
1036 then
1037 for i in self.breakpoints[file]
1038 do
1039 if i.line == line
1040 then
1041 return i
1042 end
1043 end
1044 end
1045
1046 return null
1047 end
1048
1049 #######################################################################
1050 ## Runtime modification functions ##
1051 #######################################################################
1052
1053 # Modifies the value of a variable contained in a frame
1054 fun modify_in_frame(variable: Instance, value: String)
1055 do
1056 var new_variable = get_variable_of_type_with_value(variable.mtype.to_s, value)
1057 if new_variable != null
1058 then
1059 var keys = frame.map.keys
1060 for key in keys
1061 do
1062 if frame.map[key] == variable
1063 then
1064 frame.map[key] = new_variable
1065 end
1066 end
1067 end
1068 end
1069
1070 # Modifies the value of a variable contained in a MutableInstance
1071 fun modify_argument_of_complex_type(papa: MutableInstance, attribute: MAttribute, value: String)
1072 do
1073 var final_variable = papa.attributes[attribute]
1074 var type_of_variable = final_variable.mtype.to_s
1075 var new_variable = get_variable_of_type_with_value(type_of_variable, value)
1076 if new_variable != null
1077 then
1078 papa.attributes[attribute] = new_variable
1079 end
1080 end
1081
1082 #######################################################################
1083 ## Variable generator functions ##
1084 #######################################################################
1085
1086 # Returns a new variable of the type 'type_of_variable' with a value of 'value'
1087 fun get_variable_of_type_with_value(type_of_variable: String, value: String): nullable Instance
1088 do
1089 if type_of_variable == "Int" then
1090 return get_int(value)
1091 else if type_of_variable == "Float" then
1092 return get_float(value)
1093 else if type_of_variable == "Bool" then
1094 return get_bool(value)
1095 else if type_of_variable == "Char" then
1096 return get_char(value)
1097 end
1098
1099 return null
1100 end
1101
1102 # Returns a new int instance with value 'value'
1103 fun get_int(value: String): nullable Instance
1104 do
1105 if value.is_numeric then
1106 return int_instance(value.to_i)
1107 else
1108 return null
1109 end
1110 end
1111
1112 # Returns a new float instance with value 'value'
1113 fun get_float(value:String): nullable Instance
1114 do
1115 if value.is_numeric then
1116 return float_instance(value.to_f)
1117 else
1118 return null
1119 end
1120 end
1121
1122 # Returns a new char instance with value 'value'
1123 fun get_char(value: String): nullable Instance
1124 do
1125 if value.length >= 1 then
1126 return char_instance(value[0])
1127 else
1128 return null
1129 end
1130 end
1131
1132 # Returns a new bool instance with value 'value'
1133 fun get_bool(value: String): nullable Instance
1134 do
1135 if value.to_lower == "true" then
1136 return self.true_instance
1137 else if value.to_lower == "false" then
1138 return self.false_instance
1139 else
1140 print "Invalid value, a boolean must be set at \"true\" or \"false\""
1141 return null
1142 end
1143 end
1144
1145 #######################################################################
1146 ## Command listing function ##
1147 #######################################################################
1148
1149 # Lists the commands available when using the debugger
1150 fun list_commands
1151 do
1152 print "\nCommand not recognized\n"
1153 print "Commands accepted : \n"
1154 print "[break/b] line : Adds a breakpoint on line *line_nb* of the current file\n"
1155 print "[break/b] file_name line_nb : Adds a breakpoint on line *line_nb* of file *file_name* \n"
1156 print "[p/print] variable : [p/print] * shows the status of all the variables\n"
1157 print "[p/print] variable[i] : Prints the value of the variable contained at position *i* in SequenceRead collection *variable*\n"
1158 print "[p/print] variable[i..j]: Prints the value of all the variables contained between positions *i* and *j* in SequenceRead collection *variable*\n"
1159 print "[p/print] stack: Prints a stack trace at current instruction\n"
1160 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"
1161 print "s : steps in on the current function\n"
1162 print "n : steps-over the current instruction\n"
1163 print "finish : steps out of the current function\n"
1164 print "variable as alias : Adds an alias called *alias* for the variable *variable*"
1165 print "An alias can reference another alias\n"
1166 print "variable = value : Sets the value of *variable* to *value*\n"
1167 print "[d/delete] line_nb : Removes a breakpoint on line *line_nb* of the current file \n"
1168 print "[d/delete] file_name line_nb : Removes a breakpoint on line *line_nb* of file *file_name* \n"
1169 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."
1170 print "untrace variable_name : Removes the trace on the variable you chose to trace earlier in the program"
1171 print "kill : kills the current program (Exits with an error and stack trace)\n"
1172 end
1173
1174 end
1175
1176 # Traces the modifications of an object linked to a certain frame
1177 private class TraceObject
1178
1179 # Map of the local names bound to a frame
1180 var trace_map: HashMap[Frame, String]
1181 # Decides if breaking or printing statement when the variable is encountered
1182 var break_on_encounter: Bool
1183
1184 init(break_on_encounter: Bool)
1185 do
1186 trace_map = new HashMap[Frame, String]
1187 self.break_on_encounter = break_on_encounter
1188 end
1189
1190 # Adds the local alias for a variable and the frame bound to it
1191 fun add_frame_variable(frame: Frame, variable_name: String)
1192 do
1193 self.trace_map[frame] = variable_name
1194 end
1195
1196 # Checks if the prompted variable is traced in the specified frame
1197 fun is_variable_traced_in_frame(variable_name: String, frame: Frame): Bool
1198 do
1199 if self.trace_map.has_key(frame) then
1200 if self.trace_map[frame] == variable_name then
1201 return true
1202 end
1203 end
1204
1205 return false
1206 end
1207
1208 end
1209
1210 redef class ANode
1211
1212 # Breaks automatically when encountering an error
1213 # Permits the injunction of commands before crashing
1214 redef private fun fatal(v: NaiveInterpreter, message: String)
1215 do
1216 if v isa Debugger then
1217 print "An error was encountered, the program will stop now."
1218 self.debug(message)
1219 while v.process_debug_command(gets) do end
1220 end
1221
1222 super
1223 end
1224 end