Add support for stdout returns
[mono.git] / mcs / tools / csharp / repl.cs
index 1e14ce8534adb3239ab71c2df198bdfe528f8702..5083e6f28c594633232f14012fb1b9321c9e0c7e 100644 (file)
@@ -8,6 +8,7 @@
 //
 // Copyright 2001, 2002, 2003 Ximian, Inc (http://www.ximian.com)
 // Copyright 2004, 2005, 2006, 2007, 2008 Novell, Inc
+// Copyright 2011-2013 Xamarin Inc
 //
 //
 // TODO:
@@ -34,40 +35,58 @@ namespace Mono {
        public class Driver {
                public static string StartupEvalExpression;
                static int? attach;
+               static string target_host;
+               static int target_port;
                static string agent;
                
                static int Main (string [] args)
                {
-                       var r = new Report (new ConsoleReportPrinter ());
-                       var cmd = new CommandLineParser (r);
+                       var cmd = new CommandLineParser (Console.Out);
                        cmd.UnknownOptionHandler += HandleExtraArguments;
 
-                       var settings = cmd.ParseArguments (args);
-                       if (settings == null || r.Errors > 0)
+                       // Enable unsafe code by default
+                       var settings = new CompilerSettings () {
+                               Unsafe = true
+                       };
+
+                       if (!cmd.ParseArguments (settings, args))
                                return 1;
+
                        var startup_files = new string [settings.SourceFiles.Count];
                        int i = 0;
                        foreach (var source in settings.SourceFiles)
                                startup_files [i++] = source.FullPathName;
                        settings.SourceFiles.Clear ();
 
-                       var eval = new Evaluator (settings, r);
+                       TextWriter agent_stderr = null;
+                       ReportPrinter printer;
+                       if (agent != null) {
+                               agent_stderr = new StringWriter ();
+                               printer = new StreamReportPrinter (agent_stderr);
+                       } else {
+                               printer = new ConsoleReportPrinter ();
+                       }
+
+                       var eval = new Evaluator (new CompilerContext (settings, printer));
 
                        eval.InteractiveBaseClass = typeof (InteractiveBaseShell);
                        eval.DescribeTypeExpressions = true;
+                       eval.WaitOnTask = true;
 
                        CSharpShell shell;
 #if !ON_DOTNET
                        if (attach.HasValue) {
-                               shell = new ClientCSharpShell (eval, attach.Value);
+                               shell = new ClientCSharpShell_v1 (eval, attach.Value);
                        } else if (agent != null) {
-                               new CSharpAgent (eval, agent).Run (startup_files);
+                               new CSharpAgent (eval, agent, agent_stderr).Run (startup_files);
                                return 0;
                        } else
 #endif
-                       {
+                       if (target_host != null) 
+                               shell = new ClientCSharpShell  (eval, target_host, target_port);
+                       else 
                                shell = new CSharpShell (eval);
-                       }
+
                        return shell.Run (startup_files);
                }
 
@@ -86,9 +105,33 @@ namespace Mono {
                                        return pos + 1;
                                }
                                break;
-                       case "--agent:":
-                               agent = args[pos];
-                               return pos + 1;
+                       default:
+                               if (args [pos].StartsWith ("--server=")){
+                                       var hostport = args [pos].Substring (9);
+                                       int p = hostport.IndexOf (':');
+                                       if (p == -1){
+                                               target_host = hostport;
+                                               target_port = 10000;
+                                       } else {
+                                               target_host = hostport.Substring (0,p);
+                                               if (!int.TryParse (hostport.Substring (p), out target_port)){
+                                                       Console.Error.WriteLine ("Usage is: --server[=host[:port]");
+                                                       Environment.Exit (1);
+                                               }
+                                       }
+                                       return pos + 1;
+                               }
+                               if (args [pos].StartsWith ("--client")){
+                                       target_host = "localhost";
+                                       target_port = 10000;
+                                       return pos + 1;
+                               }
+                               if (args [pos].StartsWith ("--agent:")) {
+                                       agent = args[pos];
+                                       return pos + 1;
+                               } else {
+                                       return -1;
+                               }
                        }
                        return -1;
                }
@@ -120,14 +163,14 @@ namespace Mono {
                public static new string help {
                        get {
                                return InteractiveBase.help +
-                                       "  TabAtStartCompletes - Whether tab will complete even on empty lines\n";
+                                       "  TabAtStartCompletes      - Whether tab will complete even on empty lines\n";
                        }
                }
        }
        
        public class CSharpShell {
                static bool isatty = true, is_unix = false;
-               string [] startup_files;
+               protected string [] startup_files;
                
                Mono.Terminal.LineEditor editor;
                bool dumb;
@@ -212,17 +255,13 @@ namespace Mono {
 
                void InitTerminal (bool show_banner)
                {
-#if ON_DOTNET
-                       is_unix = false;
-                       isatty = true;
-#else
                        int p = (int) Environment.OSVersion.Platform;
                        is_unix = (p == 4) || (p == 128);
 
-                       if (is_unix)
-                               isatty = UnixUtils.isatty (0) && UnixUtils.isatty (1);
-                       else
-                               isatty = true;
+#if NET_4_5
+                       isatty = !Console.IsInputRedirected && !Console.IsOutputRedirected;
+#else
+                       isatty = true;
 #endif
 
                        // Work around, since Console is not accounting for
@@ -306,7 +345,7 @@ namespace Mono {
                                expr = expr == null ? input : expr + "\n" + input;
                                
                                expr = Evaluate (expr);
-                       } 
+                       }
                }
 
                public int ReadEvalPrintLoop ()
@@ -318,17 +357,24 @@ namespace Mono {
 
                        LoadStartupFiles ();
 
-                       if (startup_files.Length != 0)
+                       if (startup_files != null && startup_files.Length != 0) {
                                ExecuteSources (startup_files, false);
-                       else if (Driver.StartupEvalExpression != null){
-                               ReadEvalPrintLoopWith (p => {
-                                       var ret = Driver.StartupEvalExpression;
-                                       Driver.StartupEvalExpression = null;
-                                       return ret;
-                                       });
-                       } else
-                               ReadEvalPrintLoopWith (GetLine);
+                       } else {
+                               if (Driver.StartupEvalExpression != null){
+                                       ReadEvalPrintLoopWith (p => {
+                                               var ret = Driver.StartupEvalExpression;
+                                               Driver.StartupEvalExpression = null;
+                                               return ret;
+                                               });
+                               } else {
+                                       ReadEvalPrintLoopWith (GetLine);
+                               }
+                               
+                               editor.SaveHistory ();
+                       }
 
+                       Console.CancelKeyPress -= ConsoleInterrupt;
+                       
                        return 0;
                }
 
@@ -406,6 +452,24 @@ namespace Mono {
                                break;
                        }
                }
+
+               // Some types (System.Json.JsonPrimitive) implement
+               // IEnumerator and yet, throw an exception when we
+               // try to use them, helper function to check for that
+               // condition
+               static internal bool WorksAsEnumerable (object obj)
+               {
+                       IEnumerable enumerable = obj as IEnumerable;
+                       if (enumerable != null){
+                               try {
+                                       enumerable.GetEnumerator ();
+                                       return true;
+                               } catch {
+                                       // nothing, we return false below
+                               }
+                       }
+                       return false;
+               }
                
                internal static void PrettyPrint (TextWriter output, object result)
                {
@@ -449,7 +513,7 @@ namespace Mono {
                                                p (output, " }");
                                }
                                p (output, "}");
-                       } else if (result is IEnumerable) {
+                       } else if (WorksAsEnumerable (result)) {
                                int i = 0;
                                p (output, "{ ");
                                foreach (object item in (IEnumerable) result) {
@@ -474,6 +538,162 @@ namespace Mono {
                
        }
 
+       //
+       // Stream helper extension methods
+       //
+       public static class StreamHelper {
+               static DataConverter converter = DataConverter.LittleEndian;
+               
+               static void GetBuffer (this Stream stream, byte [] b)
+               {
+                       int n, offset = 0;
+                       int len = b.Length;
+
+                       do {
+                               n = stream.Read (b, offset, len);
+                               if (n == 0)
+                                       throw new IOException ("End reached");
+
+                               offset += n;
+                               len -= n;
+                       } while (len > 0);
+               }
+
+               public static int GetInt (this Stream stream)
+               {
+                       byte [] b = new byte [4];
+                       stream.GetBuffer (b);
+                       return converter.GetInt32 (b, 0);
+               }
+
+               public static string GetString (this Stream stream)
+               {
+                       int len = stream.GetInt ();
+                       if (len == 0)
+                               return "";
+
+                       byte [] b = new byte [len];
+                       stream.GetBuffer (b);
+
+                       return Encoding.UTF8.GetString (b);
+               }
+
+               public static void WriteInt (this Stream stream, int n)
+               {
+                       byte [] bytes = converter.GetBytes (n);
+                       stream.Write (bytes, 0, bytes.Length);
+               }
+       
+               public static void WriteString (this Stream stream, string s)
+               {
+                       stream.WriteInt (s.Length);
+                       byte [] bytes = Encoding.UTF8.GetBytes (s);
+                       stream.Write (bytes, 0, bytes.Length);
+               }
+       }
+       
+       public enum AgentStatus : byte {
+               // Received partial input, complete
+               PARTIAL_INPUT  = 1,
+       
+               // The result was set, expect the string with the result
+               RESULT_SET     = 2,
+       
+               // No result was set, complete
+               RESULT_NOT_SET = 3,
+       
+               // Errors and warnings string follows
+               ERROR          = 4,
+
+               // Stdout
+               STDOUT         = 5,
+       }
+
+       class ClientCSharpShell : CSharpShell {
+               string target_host;
+               int target_port;
+               
+               public ClientCSharpShell (Evaluator evaluator, string target_host, int target_port) : base (evaluator)
+               {
+                       this.target_port = target_port;
+                       this.target_host = target_host;
+               }
+
+               T ConnectServer<T> (Func<NetworkStream,T> callback, Action<Exception> error)
+               {
+                       try {
+                               var client = new TcpClient (target_host, target_port);
+                               var ns = client.GetStream ();
+                               T ret = callback (ns);
+                               ns.Flush ();
+                               ns.Close ();
+                               client.Close ();
+                               return ret;
+                       } catch (Exception e){
+                               error (e);
+                               return default(T);
+                       }
+               }
+               
+               protected override string Evaluate (string input)
+               {
+                       return ConnectServer<string> ((ns)=> {
+                               try {
+                                       ns.WriteString ("EVALTXT");
+                                       ns.WriteString (input);
+
+                                       while (true) {
+                                               AgentStatus s = (AgentStatus) ns.ReadByte ();
+                                       
+                                               switch (s){
+                                               case AgentStatus.PARTIAL_INPUT:
+                                                       return input;
+                                               
+                                               case AgentStatus.ERROR:
+                                                       string err = ns.GetString ();
+                                                       Console.Error.WriteLine (err);
+                                                       break;
+
+                                               case AgentStatus.STDOUT:
+                                                       string stdout = ns.GetString ();
+                                                       Console.WriteLine (stdout);
+                                                       break;
+                                               
+                                               case AgentStatus.RESULT_NOT_SET:
+                                                       return null;
+                                               
+                                               case AgentStatus.RESULT_SET:
+                                                       string res = ns.GetString ();
+                                                       Console.WriteLine (res);
+                                                       return null;
+                                               }
+                                       }
+                               } catch (Exception e){
+                                       Console.Error.WriteLine ("Error evaluating expression, exception: {0}", e);
+                               }
+                               return null;
+                       }, (e) => {
+                               Console.Error.WriteLine ("Error communicating with server {0}", e);
+                       });
+               }
+               
+               public override int Run (string [] startup_files)
+               {
+                       // The difference is that we do not call Evaluator.Init, that is done on the target
+                       this.startup_files = startup_files;
+                       return ReadEvalPrintLoop ();
+               }
+       
+               protected override void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
+               {
+                       ConnectServer<int> ((ns)=> {
+                               ns.WriteString ("INTERRUPT");
+                               return 0;
+                       }, (e) => { });
+               }
+                       
+       }
+
 #if !ON_DOTNET
        //
        // A shell connected to a CSharpAgent running in a remote process.
@@ -481,10 +701,10 @@ namespace Mono {
        //  - Support Gtk and Winforms main loops if detected, this should
        //    probably be done as a separate agent in a separate place.
        //
-       class ClientCSharpShell : CSharpShell {
+       class ClientCSharpShell_v1 : CSharpShell {
                NetworkStream ns, interrupt_stream;
                
-               public ClientCSharpShell (Evaluator evaluator, int pid)
+               public ClientCSharpShell_v1 (Evaluator evaluator, int pid)
                        : base (evaluator)
                {
                        // Create a server socket we listen on whose address is passed to the agent
@@ -509,7 +729,7 @@ namespace Mono {
        
                        Console.WriteLine ("Connected.");
                }
-       
+
                //
                // A remote version of Evaluate
                //
@@ -542,6 +762,7 @@ namespace Mono {
                public override int Run (string [] startup_files)
                {
                        // The difference is that we do not call Evaluator.Init, that is done on the target
+                       this.startup_files = startup_files;
                        return ReadEvalPrintLoop ();
                }
        
@@ -558,57 +779,6 @@ namespace Mono {
                        
        }
 
-       //
-       // Stream helper extension methods
-       //
-       public static class StreamHelper {
-               static DataConverter converter = DataConverter.LittleEndian;
-               
-               public static int GetInt (this Stream stream)
-               {
-                       byte [] b = new byte [4];
-                       if (stream.Read (b, 0, 4) != 4)
-                               throw new IOException ("End reached");
-                       return converter.GetInt32 (b, 0);
-               }
-               
-               public static string GetString (this Stream stream)
-               {
-                       int len = stream.GetInt ();
-                       byte [] b = new byte [len];
-                       if (stream.Read (b, 0, len) != len)
-                               throw new IOException ("End reached");
-                       return Encoding.UTF8.GetString (b);
-               }
-       
-               public static void WriteInt (this Stream stream, int n)
-               {
-                       byte [] bytes = converter.GetBytes (n);
-                       stream.Write (bytes, 0, bytes.Length);
-               }
-       
-               public static void WriteString (this Stream stream, string s)
-               {
-                       stream.WriteInt (s.Length);
-                       byte [] bytes = Encoding.UTF8.GetBytes (s);
-                       stream.Write (bytes, 0, bytes.Length);
-               }
-       }
-       
-       public enum AgentStatus : byte {
-               // Received partial input, complete
-               PARTIAL_INPUT  = 1,
-       
-               // The result was set, expect the string with the result
-               RESULT_SET     = 2,
-       
-               // No result was set, complete
-               RESULT_NOT_SET = 3,
-       
-               // Errors and warnings string follows
-               ERROR          = 4, 
-       }
-       
        //
        // This is the agent loaded into the target process when using --attach.
        //
@@ -616,10 +786,12 @@ namespace Mono {
        {
                NetworkStream interrupt_stream;
                readonly Evaluator evaluator;
+               TextWriter stderr;
                
-               public CSharpAgent (Evaluator evaluator, String arg)
+               public CSharpAgent (Evaluator evaluator, String arg, TextWriter stderr)
                {
                        this.evaluator = evaluator;
+                       this.stderr = stderr;
                        new Thread (new ParameterizedThreadStart (Run)).Start (arg);
                }
 
@@ -657,8 +829,11 @@ namespace Mono {
                                AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded;
        
                                // Add all currently loaded assemblies
-                               foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies ())
-                                       evaluator.ReferenceAssembly (a);
+                               foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies ()) {
+                                       // Some assemblies seem to be already loaded, and loading them again causes 'defined multiple times' errors
+                                       if (a.GetName ().Name != "mscorlib" && a.GetName ().Name != "System.Core" && a.GetName ().Name != "System")
+                                               evaluator.ReferenceAssembly (a);
+                               }
        
                                RunRepl (s);
                        } finally {
@@ -681,9 +856,8 @@ namespace Mono {
                        while (!InteractiveBase.QuitRequested) {
                                try {
                                        string error_string;
-                                       StringWriter error_output = new StringWriter ();
-//                                     Report.Stderr = error_output;
-                                       
+                                       StringWriter error_output = (StringWriter)stderr;
+
                                        string line = s.GetString ();
        
                                        bool result_set;
@@ -713,6 +887,7 @@ namespace Mono {
                                        if (error_string.Length != 0){
                                                s.WriteByte ((byte) AgentStatus.ERROR);
                                                s.WriteString (error_output.ToString ());
+                                               error_output.GetStringBuilder ().Clear ();
                                        }
        
                                        if (result_set){