Add support for stdout returns
[mono.git] / mcs / tools / csharp / repl.cs
index 93f484733adc2674f198f1e29cfa9620bdcbadb6..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:
@@ -25,54 +26,190 @@ using System.Reflection.Emit;
 using System.Threading;
 using System.Net;
 using System.Net.Sockets;
+using System.Collections.Generic;
 
 using Mono.CSharp;
-using Mono.Attach;
 
 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)
                {
-                       if (args.Length > 0 && args [0] == "--attach") {
-                               new ClientCSharpShell (Int32.Parse (args [1])).Run ();
-                               return 0;
-                       } else if (args.Length > 0 && args [0].StartsWith ("--agent:")) {
-                               new CSharpAgent (args [0]);
-                               return 0;
+                       var cmd = new CommandLineParser (Console.Out);
+                       cmd.UnknownOptionHandler += HandleExtraArguments;
+
+                       // 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 ();
+
+                       TextWriter agent_stderr = null;
+                       ReportPrinter printer;
+                       if (agent != null) {
+                               agent_stderr = new StringWriter ();
+                               printer = new StreamReportPrinter (agent_stderr);
                        } else {
-                               try {
-                                       Evaluator.Init (args);
-                               } catch {
-                                       return 1;
+                               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_v1 (eval, attach.Value);
+                       } else if (agent != null) {
+                               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);
+               }
+
+               static int HandleExtraArguments (string [] args, int pos)
+               {
+                       switch (args [pos]) {
+                       case "-e":
+                               if (pos + 1 < args.Length) {
+                                       StartupEvalExpression = args[pos + 1];
+                                       return pos + 1;
                                }
-                       
-                               return new CSharpShell ().Run ();
+                               break;
+                       case "--attach":
+                               if (pos + 1 < args.Length) {
+                                       attach = Int32.Parse (args[1]);
+                                       return pos + 1;
+                               }
+                               break;
+                       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;
+               }
+               
+       }
+
+       public class InteractiveBaseShell : InteractiveBase {
+               static bool tab_at_start_completes;
+               
+               static InteractiveBaseShell ()
+               {
+                       tab_at_start_completes = false;
+               }
+
+               internal static Mono.Terminal.LineEditor Editor;
+               
+               public static bool TabAtStartCompletes {
+                       get {
+                               return tab_at_start_completes;
+                       }
+
+                       set {
+                               tab_at_start_completes = value;
+                               if (Editor != null)
+                                       Editor.TabAtStartCompletes = value;
+                       }
+               }
+
+               public static new string help {
+                       get {
+                               return InteractiveBase.help +
+                                       "  TabAtStartCompletes      - Whether tab will complete even on empty lines\n";
                        }
                }
        }
        
        public class CSharpShell {
-               static bool isatty = true;
+               static bool isatty = true, is_unix = false;
+               protected string [] startup_files;
                
                Mono.Terminal.LineEditor editor;
                bool dumb;
+               readonly Evaluator evaluator;
+
+               public CSharpShell (Evaluator evaluator)
+               {
+                       this.evaluator = evaluator;
+               }
 
                protected virtual void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
                {
                        // Do not about our program
                        a.Cancel = true;
 
-                       Mono.CSharp.Evaluator.Interrupt ();
+                       evaluator.Interrupt ();
                }
                
                void SetupConsole ()
                {
-                       string term = Environment.GetEnvironmentVariable ("TERM");
-                       dumb = term == "dumb" || term == null || isatty == false;
+                       if (is_unix){
+                               string term = Environment.GetEnvironmentVariable ("TERM");
+                               dumb = term == "dumb" || term == null || isatty == false;
+                       } else
+                               dumb = false;
                        
                        editor = new Mono.Terminal.LineEditor ("csharp", 300);
+                       InteractiveBaseShell.Editor = editor;
+
+                       editor.AutoCompleteEvent += delegate (string s, int pos){
+                               string prefix = null;
+
+                               string complete = s.Substring (0, pos);
+                               
+                               string [] completions = evaluator.GetCompletions (complete, out prefix);
+                               
+                               return new Mono.Terminal.LineEditor.Completion (prefix, completions);
+                       };
+                       
 #if false
                        //
                        // This is a sample of how completions sould be implemented.
@@ -116,22 +253,58 @@ namespace Mono {
                        Evaluate ("using System; using System.Linq; using System.Collections.Generic; using System.Collections;");
                }
 
-               void InitTerminal ()
+               void InitTerminal (bool show_banner)
                {
-                       isatty = UnixUtils.isatty (0) && UnixUtils.isatty (1);
+                       int p = (int) Environment.OSVersion.Platform;
+                       is_unix = (p == 4) || (p == 128);
+
+#if NET_4_5
+                       isatty = !Console.IsInputRedirected && !Console.IsOutputRedirected;
+#else
+                       isatty = true;
+#endif
 
                        // Work around, since Console is not accounting for
                        // cursor position when writing to Stderr.  It also
                        // has the undesirable side effect of making
                        // errors plain, with no coloring.
-                       Report.Stderr = Console.Out;
+//                     Report.Stderr = Console.Out;
                        SetupConsole ();
 
-                       if (isatty)
+                       if (isatty && show_banner)
                                Console.WriteLine ("Mono C# Shell, type \"help;\" for help\n\nEnter statements below.");
 
                }
 
+               void ExecuteSources (IEnumerable<string> sources, bool ignore_errors)
+               {
+                       foreach (string file in sources){
+                               try {
+                                       try {
+                                               bool first = true;
+                       
+                                               using (System.IO.StreamReader r = System.IO.File.OpenText (file)){
+                                                       ReadEvalPrintLoopWith (p => {
+                                                               var line = r.ReadLine ();
+                                                               if (first){
+                                                                       if (line.StartsWith ("#!"))
+                                                                               line = r.ReadLine ();
+                                                                       first = false;
+                                                               }
+                                                               return line;
+                                                       });
+                                               }
+                                       } catch (FileNotFoundException){
+                                               Console.Error.WriteLine ("cs2001: Source file `{0}' not found", file);
+                                               return;
+                                       }
+                               } catch {
+                                       if (!ignore_errors)
+                                               throw;
+                               }
+                       }
+               }
+               
                protected virtual void LoadStartupFiles ()
                {
                        string dir = Path.Combine (
@@ -140,20 +313,22 @@ namespace Mono {
                        if (!Directory.Exists (dir))
                                return;
 
-                       foreach (string file in Directory.GetFiles (dir)){
+                       List<string> sources = new List<string> ();
+                       List<string> libraries = new List<string> ();
+                       
+                       foreach (string file in System.IO.Directory.GetFiles (dir)){
                                string l = file.ToLower ();
                                
-                               if (l.EndsWith (".cs")){
-                                       try {
-                                               using (StreamReader r = File.OpenText (file)){
-                                                       ReadEvalPrintLoopWith (p => r.ReadLine ());
-                                               }
-                                       } catch {
-                                       }
-                               } else if (l.EndsWith (".dll")){
-                                       Evaluator.LoadAssembly (file);
-                               }
+                               if (l.EndsWith (".cs"))
+                                       sources.Add (file);
+                               else if (l.EndsWith (".dll"))
+                                       libraries.Add (file);
                        }
+
+                       foreach (string file in libraries)
+                               evaluator.LoadAssembly (file);
+
+                       ExecuteSources (sources, true);
                }
 
                void ReadEvalPrintLoopWith (ReadLiner readline)
@@ -170,18 +345,36 @@ namespace Mono {
                                expr = expr == null ? input : expr + "\n" + input;
                                
                                expr = Evaluate (expr);
-                       } 
+                       }
                }
 
                public int ReadEvalPrintLoop ()
                {
-                       InitTerminal ();
+                       if (startup_files != null && startup_files.Length == 0)
+                               InitTerminal (startup_files.Length == 0 && Driver.StartupEvalExpression == null);
 
                        InitializeUsing ();
 
                        LoadStartupFiles ();
-                       ReadEvalPrintLoopWith (GetLine);
 
+                       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);
+                               }
+                               
+                               editor.SaveHistory ();
+                       }
+
+                       Console.CancelKeyPress -= ConsoleInterrupt;
+                       
                        return 0;
                }
 
@@ -191,7 +384,7 @@ namespace Mono {
                        object result;
 
                        try {
-                               input = Evaluator.Evaluate (input, out result, out result_set);
+                               input = evaluator.Evaluate (input, out result, out result_set);
 
                                if (result_set){
                                        PrettyPrint (Console.Out, result);
@@ -259,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)
                {
@@ -302,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) {
@@ -319,27 +530,182 @@ namespace Mono {
                        }
                }
 
-               public CSharpShell ()
+               public virtual int Run (string [] startup_files)
+               {
+                       this.startup_files = startup_files;
+                       return ReadEvalPrintLoop ();
+               }
+               
+       }
+
+       //
+       // 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 virtual int Run ()
+               public static int GetInt (this Stream stream)
                {
-                       return ReadEvalPrintLoop ();
+                       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.
        //  - maybe add 'class_name' and 'method_name' arguments to LoadAgent.
        //  - 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 (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
                        TcpListener listener = new TcpListener (new IPEndPoint (IPAddress.Loopback, 0));
@@ -352,7 +718,7 @@ namespace Mono {
                                                          ((IPEndPoint)listener.Server.LocalEndPoint).Port,
                                                          ((IPEndPoint)interrupt_listener.Server.LocalEndPoint).Port);
        
-                       VirtualMachine vm = new VirtualMachine (pid);
+                       var vm = new Attach.VirtualMachine (pid);
                        vm.Attach (agent_assembly, agent_arg);
        
                        /* Wait for the client to connect */
@@ -363,7 +729,7 @@ namespace Mono {
        
                        Console.WriteLine ("Connected.");
                }
-       
+
                //
                // A remote version of Evaluate
                //
@@ -393,9 +759,10 @@ namespace Mono {
                        }
                }
                
-               public override int Run ()
+               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 ();
                }
        
@@ -412,66 +779,19 @@ 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.
        //
        class CSharpAgent
        {
                NetworkStream interrupt_stream;
+               readonly Evaluator evaluator;
+               TextWriter stderr;
                
-               public CSharpAgent (String arg)
+               public CSharpAgent (Evaluator evaluator, String arg, TextWriter stderr)
                {
+                       this.evaluator = evaluator;
+                       this.stderr = stderr;
                        new Thread (new ParameterizedThreadStart (Run)).Start (arg);
                }
 
@@ -481,7 +801,7 @@ namespace Mono {
                                int b = interrupt_stream.ReadByte();
                                if (b == -1)
                                        return;
-                               Evaluator.Interrupt ();
+                               evaluator.Interrupt ();
                                interrupt_stream.WriteByte (0);
                        }
                }
@@ -503,22 +823,17 @@ namespace Mono {
                        NetworkStream s = client.GetStream ();
                        interrupt_stream = interrupt_client.GetStream ();
                        new Thread (InterruptListener).Start ();
-                       
-                       try {
-                               Evaluator.Init (new string [0]);
-                       } catch {
-                               // TODO: send a result back.
-                               Console.WriteLine ("csharp-agent: initialization failed");
-                               return;
-                       }
-       
+
                        try {
                                // Add all assemblies loaded later
                                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 {
@@ -529,9 +844,9 @@ namespace Mono {
                        }
                }
        
-               static void AssemblyLoaded (object sender, AssemblyLoadEventArgs e)
+               void AssemblyLoaded (object sender, AssemblyLoadEventArgs e)
                {
-                       Evaluator.ReferenceAssembly (e.LoadedAssembly);
+                       evaluator.ReferenceAssembly (e.LoadedAssembly);
                }
        
                public void RunRepl (NetworkStream s)
@@ -541,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;
@@ -555,7 +869,7 @@ namespace Mono {
                                                input = input + "\n" + line;
        
                                        try {
-                                               input = Evaluator.Evaluate (input, out result, out result_set);
+                                               input = evaluator.Evaluate (input, out result, out result_set);
                                        } catch (Exception e) {
                                                s.WriteByte ((byte) AgentStatus.ERROR);
                                                s.WriteString (e.ToString ());
@@ -573,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){
@@ -591,6 +906,21 @@ namespace Mono {
                        }
                }
        }
+
+       public class UnixUtils {
+               [System.Runtime.InteropServices.DllImport ("libc", EntryPoint="isatty")]
+               extern static int _isatty (int fd);
+                       
+               public static bool isatty (int fd)
+               {
+                       try {
+                               return _isatty (fd) == 1;
+                       } catch {
+                               return false;
+                       }
+               }
+       }
+#endif
 }
        
 namespace Mono.Management
@@ -600,4 +930,3 @@ namespace Mono.Management
        }
 }
 
-