Merge: Infer more attribute types
[nit.git] / contrib / nitin / nitin.nit
index 0d4484d..6651636 100644 (file)
 # The Nit interactive interpreter
 module nitin
 
+import prompt
+
 import nitc::interpreter
 import nitc::frontend
 import nitc::parser_util
+intrude import nitc::scope
 
 redef class ToolContext
+
+       # --no-prompt
+       var opt_no_prompt = new OptionBool("Disable writing a prompt.", "--no-prompt")
+
+       # --source-name
+       var opt_source_name = new OptionString("Set a name for the input source.", "--source-name")
+
+       redef init do
+               super
+               option_context.add_option(opt_no_prompt, opt_source_name)
+       end
+
        # Parse a full module given as a string
        #
        # Return a AModule or a AError
        fun p_module(string: String): ANode
        do
-               var source = new SourceFile.from_string("", string)
+               var source_name = opt_source_name.value or else ""
+               string = "\n" * last_line + string
+               var source = new SourceFile.from_string(source_name, string)
                var lexer = new Lexer(source)
                var parser = new Parser(lexer)
                var tree = parser.parse
@@ -41,20 +58,8 @@ redef class ToolContext
                return tree.n_base.as(not null)
        end
 
-       # Read an user-line with a given `prompt`
-       #
-       # Return `null` if end of file
-       fun readline(prompt: String): nullable String do
-               printn prompt
-               var res = stdin.read_line
-               if res == "" and stdin.eof then return null
-               return res
-       end
-
-       # Add `text` in the history for `readline`.
-       #
-       # With the default implementation, the history is dropped
-       fun readline_add_history(text: String) do end
+       # The last line number read by `i_parse`
+       var last_line = 0
 
        # Parse the input of the user as a module
        fun i_parse(prompt: String): nullable ANode
@@ -62,9 +67,22 @@ redef class ToolContext
                var oldtext = ""
 
                loop
-                       var s = readline(prompt)
+                       var s
+                       if opt_no_prompt.value then
+                               s = stdin.read_line
+                               if s == "" and stdin.eof then s = null
+                       else
+                               s = sys.prompt(prompt)
+                       end
                        if s == null then return null
-                       if s == "" then continue
+                       if s == "" then
+                               if oldtext != "" then
+                                       oldtext += "\n"
+                               else
+                                       last_line += 1
+                               end
+                               continue
+                       end
 
                        if s.chars.first == ':' then
                                var res = new TString
@@ -76,19 +94,76 @@ redef class ToolContext
                        oldtext = ""
                        var n = p_module(text)
 
-                       if n isa AParserError and (n.token isa EOF) then
+                       if n isa AParserError and (n.token isa EOF or n.token isa TBadTString or n.token isa TBadExtern) then
                                # Unexpected end of file, thus continuing
                                if oldtext == "" then prompt = "." * prompt.length
                                oldtext = text
                                continue
                        end
 
-                       readline_add_history(text.chomp)
+                       last_line = n.location.file.line_starts.length - 1
+                       prompt_add_history(text.chomp)
                        return n
                end
        end
 end
 
+redef class AMethPropdef
+       var injected_variables: nullable Map[Variable, Instance] = null is writable
+       var new_variables: nullable Array[Variable] = null
+
+       redef fun accept_scope_visitor(v)
+       do
+               var injected_variables = self.injected_variables
+               if injected_variables == null then
+                       super
+                       return
+               end
+
+               # Inject main variables in the initial scope
+               var scope = v.scopes.first
+               for variable in injected_variables.keys do
+                       scope.variables[variable.name] = variable
+               end
+
+               super
+
+               # Gather new top-level variables as main variables
+               scope = v.scopes.first
+               var new_variables = new Array[Variable]
+               for variable in scope.variables.values do
+                       if not injected_variables.has_key(variable) then
+                               new_variables.add(variable)
+                       end
+               end
+               self.new_variables = new_variables
+       end
+
+       redef fun call_commons(v, m, a, f)
+       do
+               var injected_variables = self.injected_variables
+               if injected_variables == null then return super
+
+               # Inject main variables in the frame
+               assert f isa InterpreterFrame
+               for variable, i in injected_variables do
+                       f.map[variable] = i
+               end
+
+               var res = super
+
+               # Update the values of the variables
+               for variable in injected_variables.keys do
+                       injected_variables[variable] = f.map[variable]
+               end
+               # Retrieve the values of the new main variables
+               for variable in new_variables.as(not null) do
+                       injected_variables[variable] = f.map[variable]
+               end
+
+               return res
+       end
+end
 
 # Create a tool context to handle options and paths
 var toolcontext = new ToolContext
@@ -124,6 +199,8 @@ var sys_type = mainobj.mtype.as(MClassType)
 var mainprop = mainmodule.try_get_primitive_method("main", sys_type.mclass)
 assert mainprop != null
 
+var main_variables = new Map[Variable, Instance]
+
 var l = 0
 loop
        # Next piece of Nit code
@@ -145,7 +222,8 @@ loop
 
        # An error
        if n isa AError then
-               print "{n.location.colored_line("0;31")}: {n.message}"
+               modelbuilder.error(n, n.message)
+               toolcontext.check_errors
                continue
        end
 
@@ -158,6 +236,14 @@ loop
        l += 1
        var newmodule = modelbuilder.load_rt_module(mainmodule, amodule, "input-{l}")
        if newmodule == null then continue
+
+       var main_method = null
+       if amodule.n_classdefs.not_empty and amodule.n_classdefs.last isa AMainClassdef then
+               main_method = amodule.n_classdefs.last.n_propdefs.first
+               assert main_method isa AMethPropdef
+               main_method.injected_variables = main_variables
+       end
+
        modelbuilder.run_phases
        if not toolcontext.check_errors then
                toolcontext.error_count = 0
@@ -168,7 +254,22 @@ loop
        interpreter.mainmodule = mainmodule
 
        # Run the main if the AST contains a main
-       if amodule.n_classdefs.not_empty and amodule.n_classdefs.last isa AMainClassdef then
-               interpreter.send(mainprop, [mainobj])
+       if main_method != null then
+               do
+                       interpreter.catch_count += 1
+                       interpreter.send(mainprop, [mainobj])
+               catch
+                       var e = interpreter.last_error
+                       if e != null then
+                               var en = e.node
+                               if en != null then
+                                       print "{en.location}: Runtime error: {e.message}\n{en.location.colored_line("0;31")}"
+                               else
+                                       print "Runtime error: {e.message}"
+                               end
+                       end
+                       print interpreter.stack_trace
+                       interpreter.frames.clear
+               end
        end
 end