Rename REAMDE to README.md
[nit.git] / src / interpreter / debugger.nit
index 52444f7..1765d9e 100644 (file)
@@ -17,7 +17,6 @@
 # Debugging of a nit program using the NaiveInterpreter
 module debugger
 
-import breakpoint
 intrude import naive_interpreter
 import nitx
 intrude import semantize::local_var_init
@@ -66,7 +65,7 @@ end
 
 redef class ScopeVisitor
 
-       redef init(toolcontext)
+       redef init
        do
                super
                if toolcontext.dbg != null then
@@ -104,7 +103,7 @@ redef class ToolContext
        redef fun check_errors
        do
                if dbg == null then
-                       super
+                       return super
                else
                        if messages.length > 0 then
                                message_sorter.sort(messages)
@@ -117,12 +116,13 @@ redef class ToolContext
 
                        messages.clear
                end
+               return not had_error
        end
 
        # -d
-       var opt_debugger_mode: OptionBool = new OptionBool("Launches the target program with the debugger attached to it", "-d")
+       var opt_debugger_mode = new OptionBool("Launches the target program with the debugger attached to it", "-d")
        # -c
-       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")
+       var opt_debugger_autorun = new OptionBool("Launches the target program with the interpreter, such as when the program fails, the debugging prompt is summoned", "-c")
 
        redef init
        do
@@ -146,7 +146,7 @@ redef class ModelBuilder
 
                var interpreter = new Debugger(self, mainmodule, arguments)
 
-               init_naive_interpreter(interpreter, mainmodule)
+               interpreter.start(mainmodule)
 
                var time1 = get_time
                self.toolcontext.info("*** END INTERPRETING: {time1-time0} ***", 2)
@@ -160,13 +160,27 @@ redef class ModelBuilder
                var interpreter = new Debugger(self, mainmodule, arguments)
                interpreter.autocontinue = true
 
-               init_naive_interpreter(interpreter, mainmodule)
+               interpreter.start(mainmodule)
 
                var time1 = get_time
                self.toolcontext.info("*** END INTERPRETING: {time1-time0} ***", 2)
        end
 end
 
+# Contains all the informations of a Breakpoint for the Debugger
+class Breakpoint
+
+       # Line to break on
+       var line: Int
+
+       # File concerned by the breakpoint
+       var file: String
+
+       redef init do
+               if not file.has_suffix(".nit") then file += ".nit"
+       end
+end
+
 # The class extending `NaiveInterpreter` by adding debugging methods
 class Debugger
        super NaiveInterpreter
@@ -211,6 +225,8 @@ class Debugger
        # Auto continues the execution until the end or until an error is encountered
        var autocontinue = false
 
+       redef type FRAME: InterpreterFrame
+
        #######################################################################
        ##                  Execution of statement function                  ##
        #######################################################################
@@ -257,19 +273,8 @@ class Debugger
        end
 
        # Same as a regular call but for a runtime injected module
-       #
        fun rt_call(mpropdef: MMethodDef, args: Array[Instance]): nullable Instance
        do
-               args = call_commons(mpropdef, args)
-               return rt_call_without_varargs(mpropdef, args)
-       end
-
-       # Common code to call and this function
-       #
-       # Call only executes the variadic part, this avoids
-       # double encapsulation of variadic parameters into an Array
-       fun rt_call_without_varargs(mpropdef: MMethodDef, args: Array[Instance]): nullable Instance
-       do
                if self.modelbuilder.toolcontext.opt_discover_call_trace.value and not self.discover_call_trace.has(mpropdef) then
                        self.discover_call_trace.add mpropdef
                        self.debug("Discovered {mpropdef}")
@@ -277,20 +282,13 @@ class Debugger
                assert args.length == mpropdef.msignature.arity + 1 else debug("Invalid arity for {mpropdef}. {args.length} arguments given.")
 
                # Look for the AST node that implements the property
-               var mproperty = mpropdef.mproperty
-               if self.modelbuilder.mpropdef2npropdef.has_key(mpropdef) then
-                       var npropdef = self.modelbuilder.mpropdef2npropdef[mpropdef]
-                       self.parameter_check(npropdef, mpropdef, args)
-                       if npropdef isa AMethPropdef then
-                               return npropdef.rt_call(self, mpropdef, args)
-                       else
-                               print "Error, invalid propdef to call at runtime !"
-                               return null
-                       end
-               else if mproperty.name == "init" then
-                       var nclassdef = self.modelbuilder.mclassdef2nclassdef[mpropdef.mclassdef]
-                       self.parameter_check(nclassdef, mpropdef, args)
-                       return nclassdef.call(self, mpropdef, args)
+               var node = modelbuilder.mpropdef2node(mpropdef)
+               if node isa AMethPropdef then
+                       self.parameter_check(node, mpropdef, args)
+                       return node.rt_call(self, mpropdef, args)
+               else if node isa AClassdef then
+                       self.parameter_check(node, mpropdef, args)
+                       return node.call(self, mpropdef, args)
                else
                        fatal("Fatal Error: method {mpropdef} not found in the AST")
                        abort
@@ -324,7 +322,6 @@ class Debugger
                        var mmod = e.mmodule
                        if mmod != null then
                                self.mainmodule = mmod
-                               var local_classdefs = mmod.mclassdefs
                                var sys_type = mmod.sys_type
                                if sys_type == null then
                                        print "Fatal error, cannot find Class Sys !\nAborting"
@@ -379,14 +376,6 @@ class Debugger
                var breakpoint = find_breakpoint(curr_file, n.location.line_start)
 
                if breakpoints.keys.has(curr_file) and breakpoint != null then
-
-                       breakpoint.check_in
-
-                       if not breakpoint.is_valid
-                       then
-                               remove_breakpoint(curr_file, n.location.line_start)
-                       end
-
                        n.debug("Execute stmt {n.to_s}")
                        while read_cmd do end
                end
@@ -399,7 +388,6 @@ class Debugger
                var identifiers_in_instruction = get_identifiers_in_current_instruction(n.location.text)
 
                for i in identifiers_in_instruction do
-                       var variable = seek_variable(i, frame)
                        for j in self.traces do
                                if j.is_variable_traced_in_frame(i, frame) then
                                        n.debug("Traced variable {i} used")
@@ -465,7 +453,7 @@ class Debugger
        #
        # Returns a boolean value, representing whether or not to
        # continue reading commands from the console input
-       fun process_debug_command(command:String): Bool
+       fun process_debug_command(command: String): Bool
        do
                # Step-out command
                if command == "finish"
@@ -478,10 +466,17 @@ class Debugger
                # Step-over command
                else if command == "n" then
                        return step_over
+               # Shows help
+               else if command == "help" then
+                       help
+                       return true
                # Opens a new NitIndex prompt on current model
                else if command == "nitx" then
                        new NitIndex.with_infos(modelbuilder, self.mainmodule).prompt
                        return true
+               else if command == "bt" or command == "backtrack" then
+                       print stack_trace
+                       return true
                # Continues execution until the end
                else if command == "c" then
                        return continue_exec
@@ -503,41 +498,72 @@ class Debugger
                        print stack_trace
                        exit(0)
                else
-                       var parts_of_command = command.split_with(' ')
+                       var parts = command.split_with(' ')
+                       var cname = parts.first
                        # Shows the value of a variable in the current frame
-                       if parts_of_command[0] == "p" or parts_of_command[0] == "print" then
-                               print_command(parts_of_command)
+                       if cname == "p" or cname == "print" then
+                               print_command(parts)
                        # Places a breakpoint on line x of file y
-                       else if parts_of_command[0] == "break" or parts_of_command[0] == "b"
-                       then
-                               process_place_break_fun(parts_of_command)
-                       # Places a temporary breakpoint on line x of file y
-                       else if parts_of_command[0] == "tbreak" and (parts_of_command.length == 2 or parts_of_command.length == 3)
-                       then
-                               process_place_tbreak_fun(parts_of_command)
+                       else if cname == "break" or cname == "b" then
+                               process_place_break_fun(parts)
                        # Removes a breakpoint on line x of file y
-                       else if parts_of_command[0] == "d" or parts_of_command[0] == "delete" then
-                               process_remove_break_fun(parts_of_command)
+                       else if cname == "d" or cname == "delete" then
+                               process_remove_break_fun(parts)
                        # Sets an alias for a variable
-                       else if parts_of_command.length == 3 and parts_of_command[1] == "as"
-                       then
-                               add_alias(parts_of_command[0], parts_of_command[2])
+                       else if parts.length == 2 and parts[1] == "as" then
+                               process_alias(parts)
                        # Modifies the value of a variable in the current frame
-                       else if parts_of_command.length >= 3 and parts_of_command[1] == "=" then
-                               process_mod_function(parts_of_command)
+                       else if parts.length == 3 and parts[1] == "=" then
+                               process_mod_function(parts)
                        # Traces the modifications on a variable
-                       else if parts_of_command.length >= 2 and parts_of_command[0] == "trace" then
-                               process_trace_command(parts_of_command)
+                       else if cname == "trace" then
+                               process_trace_command(parts)
                        # Untraces the modifications on a variable
-                       else if parts_of_command.length == 2 and parts_of_command[0] == "untrace" then
-                               process_untrace_command(parts_of_command)
+                       else if cname == "untrace" then
+                               process_untrace_command(parts)
                        else
-                               print "Unknown command \"{command}\""
+                               bad_command(command)
                        end
                end
                return true
        end
 
+       # Produces help for the commands of the debugger
+       fun help do
+               print ""
+               print "Help :"
+               print "-----------------------------------"
+               print ""
+               print "Variables"
+               print " * Modification: var_name = value (Warning: var_name must be primitive)"
+               print " * Alias: var_name as alias"
+               print ""
+               print "Printing"
+               print " * Variables: p(rint) var_name (Use * to print all local variables)"
+               print " * Collections: p(rint) var_name '[' start_index (.. end_index) ']'"
+               print ""
+               print "Breakpoints"
+               print " * File/line: b(reak) file_name line_number"
+               print " * Remove: d(elete) id"
+               print ""
+               print "Tracepoints"
+               print " * Variable: trace var_name break/print"
+               print " * Untrace variable: untrace var_name"
+               print ""
+               print "Flow control"
+               print " * Next instruction (same-level): n"
+               print " * Next instruction: s"
+               print " * Finish current method: finish"
+               print " * Continue until next breakpoint or end: c"
+               print ""
+               print "General commands"
+               print " * quit: Quits the debugger"
+               print " * abort: Aborts the interpretation, prints the stack trace before leaving"
+               print " * nitx: Ask questions to the model about its entities (classes, methods, etc.)"
+               print " * nit: Inject dynamic code for interpretation"
+               print ""
+       end
+
        #######################################################################
        ##               Processing specific command functions               ##
        #######################################################################
@@ -580,75 +606,96 @@ class Debugger
                return false
        end
 
+       fun bad_command(cmd: String) do
+               print "Unrecognized command {cmd}. Use 'help' to show help."
+       end
+
        # Prints the demanded variable in the command
        #
        # The name of the variable in in position 1 of the array 'parts_of_command'
-       fun print_command(parts_of_command: Array[String])
+       fun print_command(parts: Array[String])
        do
-               if parts_of_command[1] == "*" then
+               if parts.length != 2 then
+                       bad_command(parts.join(" "))
+                       return
+               end
+               if parts[1] == "*" then
                        var map_of_instances = frame.map
 
-                       var keys = map_of_instances.iterator
-
                        var self_var = seek_variable("self", frame)
                        print "self: {self_var.to_s}"
 
                        for instance in map_of_instances.keys do
                                print "{instance.to_s}: {map_of_instances[instance].to_s}"
                        end
-               else if parts_of_command[1] == "stack" then
-                       print self.stack_trace
-               else if parts_of_command[1].chars.has('[') and parts_of_command[1].chars.has(']') then
-                       process_array_command(parts_of_command)
+               else if parts[1].chars.has('[') and parts[1].chars.has(']') then
+                       process_array_command(parts)
                else
-                       var instance = seek_variable(get_real_variable_name(parts_of_command[1]), frame)
+                       var instance = seek_variable(get_real_variable_name(parts[1]), frame)
 
                        if instance != null
                        then
                                print_instance(instance)
                        else
-                               print "Cannot find variable {parts_of_command[1]}"
+                               print "Cannot find variable {parts[1]}"
                        end
                end
        end
 
+       # Process the input command to set an alias for a variable
+       fun process_alias(parts: Array[String]) do
+               if parts.length != 3 then
+                       bad_command(parts.join(" "))
+                       return
+               end
+               add_alias(parts.first, parts.last)
+       end
+
        # Processes the input string to know where to put a breakpoint
-       fun process_place_break_fun(parts_of_command: Array[String])
+       fun process_place_break_fun(parts: Array[String])
        do
-               var bp = get_breakpoint_from_command(parts_of_command)
+               if parts.length != 3 then
+                       bad_command(parts.join(" "))
+                       return
+               end
+               var bp = get_breakpoint_from_command(parts)
                if bp != null then
                        place_breakpoint(bp)
                end
        end
 
        # Returns a breakpoint containing the informations stored in the command
-       fun get_breakpoint_from_command(parts_of_command: Array[String]): nullable Breakpoint
+       fun get_breakpoint_from_command(parts: Array[String]): nullable Breakpoint
        do
-               if parts_of_command[1].is_numeric then
-                       return new Breakpoint(parts_of_command[1].to_i, curr_file)
-               else if parts_of_command.length >= 3 and parts_of_command[2].is_numeric then
-                       return new Breakpoint(parts_of_command[2].to_i, parts_of_command[1])
+               if parts[1].is_numeric then
+                       return new Breakpoint(parts[1].to_i, curr_file)
+               else if parts.length >= 3 and parts[2].is_numeric then
+                       return new Breakpoint(parts[2].to_i, parts[1])
                else
                        return null
                end
        end
 
        # Processes the command of removing a breakpoint on specified line and file
-       fun process_remove_break_fun(parts_of_command: Array[String])
+       fun process_remove_break_fun(parts: Array[String])
        do
-               if parts_of_command[1].is_numeric then
-                       remove_breakpoint(self.curr_file, parts_of_command[1].to_i)
-               else if parts_of_command.length >= 3 and parts_of_command[2].is_numeric then
-                       remove_breakpoint(parts_of_command[1], parts_of_command[2].to_i)
+               if parts.length != 2 then
+                       bad_command(parts.join(" "))
+                       return
+               end
+               if parts[1].is_numeric then
+                       remove_breakpoint(self.curr_file, parts[1].to_i)
+               else if parts.length >= 3 and parts[2].is_numeric then
+                       remove_breakpoint(parts[1], parts[2].to_i)
                end
        end
 
        # Processes an array print command
-       fun process_array_command(parts_of_command: Array[String])
+       fun process_array_command(parts: Array[String])
        do
-               var index_of_first_brace = parts_of_command[1].chars.index_of('[')
-               var variable_name = get_real_variable_name(parts_of_command[1].substring(0,index_of_first_brace))
-               var braces = parts_of_command[1].substring_from(index_of_first_brace)
+               var index_of_first_brace = parts[1].chars.index_of('[')
+               var variable_name = get_real_variable_name(parts[1].substring(0,index_of_first_brace))
+               var braces = parts[1].substring_from(index_of_first_brace)
 
                var indexes = remove_braces(braces)
 
@@ -676,27 +723,32 @@ class Debugger
        # Processes the modification function to modify a variable dynamically
        #
        # Command of type variable = value
-       fun process_mod_function(parts_of_command: Array[String])
+       fun process_mod_function(parts: Array[String])
        do
-               parts_of_command[0] = get_real_variable_name(parts_of_command[0])
-               var parts_of_variable = parts_of_command[0].split_with(".")
+               if parts.length != 3 then
+                       bad_command(parts.join(" "))
+                       return
+               end
+               var p0 = parts[0]
+               p0 = get_real_variable_name(p0)
+               var parts_of_variable = p0.split_with(".")
 
                if parts_of_variable.length > 1 then
                        var last_part = parts_of_variable.pop
-                       var first_part = parts_of_command[0].substring(0,parts_of_command[0].length - last_part.length - 1)
+                       var first_part = p0.substring(0,p0.length - last_part.length - 1)
                        var papa = seek_variable(first_part, frame)
 
                        if papa != null and papa isa MutableInstance then
                                var attribute = get_attribute_in_mutable_instance(papa, last_part)
 
                                if attribute != null then
-                                       modify_argument_of_complex_type(papa, attribute, parts_of_command[2])
+                                       modify_argument_of_complex_type(papa, attribute, parts[2])
                                end
                        end
                else
                        var target = seek_variable(parts_of_variable[0], frame)
                        if target != null then
-                               modify_in_frame(target, parts_of_command[2])
+                               modify_in_frame(target, parts[2])
                        end
                end
        end
@@ -704,42 +756,46 @@ class Debugger
        # Processes the untrace variable command
        #
        # Command pattern : "untrace variable"
-       fun process_untrace_command(parts_of_command: Array[String])
+       fun process_untrace_command(parts: Array[String])
        do
-               var variable_name = get_real_variable_name(parts_of_command[1])
+               if parts.length != 2 then
+                       bad_command(parts.join(" "))
+                       return
+               end
+               var variable_name = get_real_variable_name(parts[1])
                if untrace_variable(variable_name) then
-                       print "Untraced variable {parts_of_command[1]}"
+                       print "Untraced variable {parts[1]}"
                else
-                       print "{parts_of_command[1]} is not traced"
+                       print "{parts[1]} is not traced"
                end
        end
 
        # Processes the trace variable command
        #
        # Command pattern : "trace variable [break/print]"
-       fun process_trace_command(parts_of_command: Array[String])
+       fun process_trace_command(parts: Array[String])
        do
-               var variable_name = get_real_variable_name(parts_of_command[1])
+               if parts.length != 3 then
+                       bad_command(parts.join(" "))
+                       return
+               end
+               var variable_name = get_real_variable_name(parts[1])
                var breaker:Bool
 
                if seek_variable(variable_name, frame) == null then
-                       print "Cannot find a variable called {parts_of_command[1]}"
+                       print "Cannot find a variable called {parts[1]}"
                        return
                end
 
-               if parts_of_command.length == 3 then
-                       if parts_of_command[2] == "break" then
-                               breaker = true
-                       else
-                               breaker = false
-                       end
+               if parts[2] == "break" then
+                       breaker = true
                else
                        breaker = false
                end
 
                trace_variable(variable_name, breaker)
 
-               print "Successfully tracing {parts_of_command[1]}"
+               print "Successfully tracing {parts[1]}"
        end
 
        #######################################################################
@@ -813,7 +869,7 @@ class Debugger
        # If the variable *variable_name* is an argument of the function being executed in the frame *frame*
        # The function returns its position in the arguments
        # Else, it returns -1
-       private fun get_position_of_variable_in_arguments(frame: Frame, variable_name: String): Int
+       private fun get_position_of_variable_in_arguments(frame: FRAME, variable_name: String): Int
        do
                var identifiers = get_identifiers_in_current_instruction(get_function_arguments(frame.mpropdef.location.text))
                for i in [0 .. identifiers.length-1] do
@@ -832,14 +888,12 @@ class Debugger
 
                var trigger_char_escape = false
                var trigger_string_escape = false
-               var trigger_concat_in_string = false
 
                for i in instruction.chars do
                        if trigger_char_escape then
                                if i == '\'' then trigger_char_escape = false
                        else if trigger_string_escape then
                                if i == '{' then
-                                       trigger_concat_in_string = true
                                        trigger_string_escape = false
                                else if i == '\"' then trigger_string_escape = false
                        else
@@ -857,7 +911,6 @@ class Debugger
                                else if i == '\"' then
                                        trigger_string_escape = true
                                else if i == '}' then
-                                       trigger_concat_in_string = false
                                        trigger_string_escape = true
                                else
                                        if instruction_buffer.length > 0 and not instruction_buffer.is_numeric and not (instruction_buffer.chars[0] >= 'A' and instruction_buffer.chars[0] <= 'Z') then result_array.push(instruction_buffer.to_s)
@@ -1002,7 +1055,7 @@ class Debugger
        #######################################################################
 
        # Seeks a variable from the current frame called 'variable_path', can introspect complex objects using function get_variable_in_mutable_instance
-       private fun seek_variable(variable_path: String, frame: Frame): nullable Instance
+       private fun seek_variable(variable_path: String, frame: FRAME): nullable Instance
        do
                var full_variable = variable_path.split_with(".")
 
@@ -1022,7 +1075,7 @@ class Debugger
        end
 
        # Gets a variable 'variable_name' contained in the frame 'frame'
-       private fun get_variable_in_frame(variable_name: String, frame: Frame): nullable Instance
+       private fun get_variable_in_frame(variable_name: String, frame: FRAME): nullable Instance
        do
                if variable_name == "self" then
                        if frame.arguments.length >= 1 then return frame.arguments.first
@@ -1084,8 +1137,6 @@ class Debugger
        do
                var collection_length_attribute = get_attribute_in_mutable_instance(collection, "length")
 
-               var real_collection_length: nullable Int = null
-
                if collection_length_attribute != null then
                        var primitive_length_instance = collection.attributes[collection_length_attribute]
                        if primitive_length_instance isa PrimitiveInstance[Int] then
@@ -1215,17 +1266,6 @@ class Debugger
                end
        end
 
-       #Places a breakpoint that will trigger once and be destroyed afterwards
-       fun process_place_tbreak_fun(parts_of_command: Array[String])
-       do
-               var bp = get_breakpoint_from_command(parts_of_command)
-               if bp != null
-               then
-                       bp.set_max_breaks(1)
-                       place_breakpoint(bp)
-               end
-       end
-
        #######################################################################
        ##                  Breakpoint removing functions                    ##
        #######################################################################
@@ -1371,7 +1411,7 @@ redef class AMethPropdef
        # Not supposed to be used by anyone else than the Debugger.
        private fun rt_call(v: Debugger, mpropdef: MMethodDef, args: Array[Instance]): nullable Instance
        do
-               var f = new Frame(self, self.mpropdef.as(not null), args)
+               var f = new InterpreterFrame(self, self.mpropdef.as(not null), args)
                var curr_instances = v.frame.map
                for i in curr_instances.keys do
                        f.map[i] = curr_instances[i]
@@ -1398,16 +1438,11 @@ end
 private class TraceObject
 
        # Map of the local names bound to a frame
-       var trace_map: HashMap[Frame, String]
+       var trace_map = new HashMap[Frame, String]
+
        # Decides if breaking or printing statement when the variable is encountered
        var break_on_encounter: Bool
 
-       init(break_on_encounter: Bool)
-       do
-               trace_map = new HashMap[Frame, String]
-               self.break_on_encounter = break_on_encounter
-       end
-
        # Adds the local alias for a variable and the frame bound to it
        fun add_frame_variable(frame: Frame, variable_name: String)
        do
@@ -1432,7 +1467,7 @@ redef class ANode
 
        # Breaks automatically when encountering an error
        # Permits the injunction of commands before crashing
-       redef private fun fatal(v: NaiveInterpreter, message: String)
+       redef fun fatal(v: NaiveInterpreter, message: String)
        do
                if v isa Debugger then
                        print "An error was encountered, the program will stop now."