2 // repl.cs: Support for using the compiler in interactive mode (read-eval-print loop)
5 // Miguel de Icaza (miguel@gnome.org)
7 // Dual licensed under the terms of the MIT X11 or GNU GPL
9 // Copyright 2001, 2002, 2003 Ximian, Inc (http://www.ximian.com)
10 // Copyright 2004, 2005, 2006, 2007, 2008 Novell, Inc
11 // Copyright 2011-2013 Xamarin Inc
15 // Do not print results in Evaluate, do that elsewhere in preparation for Eval refactoring.
16 // Driver.PartialReset should not reset the coretypes, nor the optional types, to avoid
17 // computing that on every call.
22 using System.Globalization;
23 using System.Collections;
24 using System.Reflection;
25 using System.Reflection.Emit;
26 using System.Threading;
28 using System.Net.Sockets;
29 using System.Collections.Generic;
36 public static string StartupEvalExpression;
38 static string target_host;
39 static int target_port;
42 static int Main (string [] args)
44 var cmd = new CommandLineParser (Console.Out);
45 cmd.UnknownOptionHandler += HandleExtraArguments;
47 // Enable unsafe code by default
48 var settings = new CompilerSettings () {
52 if (!cmd.ParseArguments (settings, args))
55 var startup_files = new string [settings.SourceFiles.Count];
57 foreach (var source in settings.SourceFiles)
58 startup_files [i++] = source.FullPathName;
59 settings.SourceFiles.Clear ();
61 TextWriter agent_stderr = null;
62 ReportPrinter printer;
64 agent_stderr = new StringWriter ();
65 printer = new StreamReportPrinter (agent_stderr);
67 printer = new ConsoleReportPrinter ();
70 var eval = new Evaluator (new CompilerContext (settings, printer));
72 eval.InteractiveBaseClass = typeof (InteractiveBaseShell);
73 eval.DescribeTypeExpressions = true;
74 eval.WaitOnTask = true;
78 if (attach.HasValue) {
79 shell = new ClientCSharpShell_v1 (eval, attach.Value);
80 } else if (agent != null) {
81 new CSharpAgent (eval, agent, agent_stderr).Run (startup_files);
85 if (target_host != null)
86 shell = new ClientCSharpShell (eval, target_host, target_port);
88 shell = new CSharpShell (eval);
90 return shell.Run (startup_files);
93 static int HandleExtraArguments (string [] args, int pos)
97 if (pos + 1 < args.Length) {
98 StartupEvalExpression = args[pos + 1];
103 if (pos + 1 < args.Length) {
104 attach = Int32.Parse (args[1]);
109 if (args [pos].StartsWith ("--server=")){
110 var hostport = args [pos].Substring (9);
111 int p = hostport.IndexOf (':');
113 target_host = hostport;
116 target_host = hostport.Substring (0,p);
117 if (!int.TryParse (hostport.Substring (p), out target_port)){
118 Console.Error.WriteLine ("Usage is: --server[=host[:port]");
119 Environment.Exit (1);
124 if (args [pos].StartsWith ("--client")){
125 target_host = "localhost";
129 if (args [pos].StartsWith ("--agent:")) {
141 public class InteractiveBaseShell : InteractiveBase {
142 static bool tab_at_start_completes;
144 static InteractiveBaseShell ()
146 tab_at_start_completes = false;
149 internal static Mono.Terminal.LineEditor Editor;
151 public static bool TabAtStartCompletes {
153 return tab_at_start_completes;
157 tab_at_start_completes = value;
159 Editor.TabAtStartCompletes = value;
163 public static new string help {
165 return InteractiveBase.help +
166 " TabAtStartCompletes - Whether tab will complete even on empty lines\n";
171 public class CSharpShell {
172 static bool isatty = true, is_unix = false;
173 protected string [] startup_files;
175 Mono.Terminal.LineEditor editor;
177 readonly Evaluator evaluator;
179 public CSharpShell (Evaluator evaluator)
181 this.evaluator = evaluator;
184 protected virtual void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
186 // Do not about our program
189 evaluator.Interrupt ();
195 string term = Environment.GetEnvironmentVariable ("TERM");
196 dumb = term == "dumb" || term == null || isatty == false;
200 editor = new Mono.Terminal.LineEditor ("csharp", 300) {
201 HeuristicsMode = "csharp"
203 InteractiveBaseShell.Editor = editor;
205 editor.AutoCompleteEvent += delegate (string s, int pos){
206 string prefix = null;
208 string complete = s.Substring (0, pos);
210 string [] completions = evaluator.GetCompletions (complete, out prefix);
212 return new Mono.Terminal.LineEditor.Completion (prefix, completions);
217 // This is a sample of how completions sould be implemented.
219 editor.AutoCompleteEvent += delegate (string s, int pos){
221 // Single match: "Substring": Sub-string
222 if (s.EndsWith ("Sub")){
223 return new string [] { "string" };
226 // Multiple matches: "ToString" and "ToLower"
227 if (s.EndsWith ("T")){
228 return new string [] { "ToString", "ToLower" };
234 Console.CancelKeyPress += ConsoleInterrupt;
237 string GetLine (bool primary)
239 string prompt = primary ? InteractiveBase.Prompt : InteractiveBase.ContinuationPrompt;
243 Console.Write (prompt);
245 return Console.ReadLine ();
247 return editor.Edit (prompt, "");
251 delegate string ReadLiner (bool primary);
253 void InitializeUsing ()
255 Evaluate ("using System; using System.Linq; using System.Collections.Generic; using System.Collections;");
258 void InitTerminal (bool show_banner)
260 int p = (int) Environment.OSVersion.Platform;
261 is_unix = (p == 4) || (p == 128);
263 isatty = !Console.IsInputRedirected && !Console.IsOutputRedirected;
265 // Work around, since Console is not accounting for
266 // cursor position when writing to Stderr. It also
267 // has the undesirable side effect of making
268 // errors plain, with no coloring.
269 // Report.Stderr = Console.Out;
272 if (isatty && show_banner)
273 Console.WriteLine ("Mono C# Shell, type \"help;\" for help\n\nEnter statements below.");
277 void ExecuteSources (IEnumerable<string> sources, bool ignore_errors)
279 foreach (string file in sources){
284 using (System.IO.StreamReader r = System.IO.File.OpenText (file)){
285 ReadEvalPrintLoopWith (p => {
286 var line = r.ReadLine ();
288 if (line.StartsWith ("#!"))
289 line = r.ReadLine ();
295 } catch (FileNotFoundException){
296 Console.Error.WriteLine ("cs2001: Source file `{0}' not found", file);
306 protected virtual void LoadStartupFiles ()
308 string dir = Path.Combine (
309 Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
311 if (!Directory.Exists (dir))
314 List<string> sources = new List<string> ();
315 List<string> libraries = new List<string> ();
317 foreach (string file in System.IO.Directory.GetFiles (dir)){
318 string l = file.ToLower ();
320 if (l.EndsWith (".cs"))
322 else if (l.EndsWith (".dll"))
323 libraries.Add (file);
326 foreach (string file in libraries)
327 evaluator.LoadAssembly (file);
329 ExecuteSources (sources, true);
332 void ReadEvalPrintLoopWith (ReadLiner readline)
335 while (!InteractiveBase.QuitRequested){
336 string input = readline (expr == null);
343 expr = expr == null ? input : expr + "\n" + input;
345 expr = Evaluate (expr);
349 public int ReadEvalPrintLoop ()
351 if (startup_files != null && startup_files.Length == 0)
352 InitTerminal (startup_files.Length == 0 && Driver.StartupEvalExpression == null);
358 if (startup_files != null && startup_files.Length != 0) {
359 ExecuteSources (startup_files, false);
361 if (Driver.StartupEvalExpression != null){
362 ReadEvalPrintLoopWith (p => {
363 var ret = Driver.StartupEvalExpression;
364 Driver.StartupEvalExpression = null;
368 ReadEvalPrintLoopWith (GetLine);
371 editor.SaveHistory ();
374 Console.CancelKeyPress -= ConsoleInterrupt;
379 protected virtual string Evaluate (string input)
385 input = evaluator.Evaluate (input, out result, out result_set);
388 PrettyPrint (Console.Out, result);
389 Console.WriteLine ();
391 } catch (Exception e){
392 Console.WriteLine (e);
399 static void p (TextWriter output, string s)
404 static void EscapeString (TextWriter output, string s)
406 foreach (var c in s){
413 output.Write ("\\\""); break;
415 output.Write ("\\a"); break;
417 output.Write ("\\b"); break;
419 output.Write ("\\n");
423 output.Write ("\\v");
427 output.Write ("\\r");
431 output.Write ("\\f");
435 output.Write ("\\t");
439 output.Write ("\\x{0:x}", (int) c);
445 static void EscapeChar (TextWriter output, char c)
448 output.Write ("'\\''");
452 output.Write ("'{0}'", c);
457 output.Write ("'\\a'");
461 output.Write ("'\\b'");
465 output.Write ("'\\n'");
469 output.Write ("'\\v'");
473 output.Write ("'\\r'");
477 output.Write ("'\\f'");
481 output.Write ("'\\t");
485 output.Write ("'\\x{0:x}", (int) c);
490 // Some types (System.Json.JsonPrimitive) implement
491 // IEnumerator and yet, throw an exception when we
492 // try to use them, helper function to check for that
494 static internal bool WorksAsEnumerable (object obj)
496 IEnumerable enumerable = obj as IEnumerable;
497 if (enumerable != null){
499 enumerable.GetEnumerator ();
502 // nothing, we return false below
508 internal static void PrettyPrint (TextWriter output, object result)
515 if (result is Array){
516 Array a = (Array) result;
519 int top = a.GetUpperBound (0);
520 for (int i = a.GetLowerBound (0); i <= top; i++){
521 PrettyPrint (output, a.GetValue (i));
526 } else if (result is bool){
531 } else if (result is string){
533 EscapeString (output, (string)result);
535 } else if (result is IDictionary){
536 IDictionary dict = (IDictionary) result;
537 int top = dict.Count, count = 0;
540 foreach (DictionaryEntry entry in dict){
543 PrettyPrint (output, entry.Key);
545 PrettyPrint (output, entry.Value);
552 } else if (WorksAsEnumerable (result)) {
555 foreach (object item in (IEnumerable) result) {
559 PrettyPrint (output, item);
562 } else if (result is char) {
563 EscapeChar (output, (char) result);
565 p (output, result.ToString ());
569 public virtual int Run (string [] startup_files)
571 this.startup_files = startup_files;
572 return ReadEvalPrintLoop ();
578 // Stream helper extension methods
580 public static class StreamHelper {
581 static DataConverter converter = DataConverter.LittleEndian;
583 static void GetBuffer (this Stream stream, byte [] b)
589 n = stream.Read (b, offset, len);
591 throw new IOException ("End reached");
598 public static int GetInt (this Stream stream)
600 byte [] b = new byte [4];
601 stream.GetBuffer (b);
602 return converter.GetInt32 (b, 0);
605 public static string GetString (this Stream stream)
607 int len = stream.GetInt ();
611 byte [] b = new byte [len];
612 stream.GetBuffer (b);
614 return Encoding.UTF8.GetString (b);
617 public static void WriteInt (this Stream stream, int n)
619 byte [] bytes = converter.GetBytes (n);
620 stream.Write (bytes, 0, bytes.Length);
623 public static void WriteString (this Stream stream, string s)
625 stream.WriteInt (s.Length);
626 byte [] bytes = Encoding.UTF8.GetBytes (s);
627 stream.Write (bytes, 0, bytes.Length);
631 public enum AgentStatus : byte {
632 // Received partial input, complete
635 // The result was set, expect the string with the result
638 // No result was set, complete
641 // Errors and warnings string follows
648 class ClientCSharpShell : CSharpShell {
652 public ClientCSharpShell (Evaluator evaluator, string target_host, int target_port) : base (evaluator)
654 this.target_port = target_port;
655 this.target_host = target_host;
658 T ConnectServer<T> (Func<NetworkStream,T> callback, Action<Exception> error)
661 var client = new TcpClient (target_host, target_port);
662 var ns = client.GetStream ();
663 T ret = callback (ns);
668 } catch (Exception e){
674 protected override string Evaluate (string input)
676 return ConnectServer<string> ((ns)=> {
678 ns.WriteString ("EVALTXT");
679 ns.WriteString (input);
682 AgentStatus s = (AgentStatus) ns.ReadByte ();
685 case AgentStatus.PARTIAL_INPUT:
688 case AgentStatus.ERROR:
689 string err = ns.GetString ();
690 Console.Error.WriteLine (err);
693 case AgentStatus.STDOUT:
694 string stdout = ns.GetString ();
695 Console.WriteLine (stdout);
698 case AgentStatus.RESULT_NOT_SET:
701 case AgentStatus.RESULT_SET:
702 string res = ns.GetString ();
703 Console.WriteLine (res);
707 } catch (Exception e){
708 Console.Error.WriteLine ("Error evaluating expression, exception: {0}", e);
712 Console.Error.WriteLine ("Error communicating with server {0}", e);
716 public override int Run (string [] startup_files)
718 // The difference is that we do not call Evaluator.Init, that is done on the target
719 this.startup_files = startup_files;
720 return ReadEvalPrintLoop ();
723 protected override void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
725 ConnectServer<int> ((ns)=> {
726 ns.WriteString ("INTERRUPT");
735 // A shell connected to a CSharpAgent running in a remote process.
736 // - maybe add 'class_name' and 'method_name' arguments to LoadAgent.
737 // - Support Gtk and Winforms main loops if detected, this should
738 // probably be done as a separate agent in a separate place.
740 class ClientCSharpShell_v1 : CSharpShell {
741 NetworkStream ns, interrupt_stream;
743 public ClientCSharpShell_v1 (Evaluator evaluator, int pid)
746 // Create a server socket we listen on whose address is passed to the agent
747 TcpListener listener = new TcpListener (new IPEndPoint (IPAddress.Loopback, 0));
749 TcpListener interrupt_listener = new TcpListener (new IPEndPoint (IPAddress.Loopback, 0));
750 interrupt_listener.Start ();
752 string agent_assembly = typeof (ClientCSharpShell).Assembly.Location;
753 string agent_arg = String.Format ("--agent:{0}:{1}" ,
754 ((IPEndPoint)listener.Server.LocalEndPoint).Port,
755 ((IPEndPoint)interrupt_listener.Server.LocalEndPoint).Port);
757 var vm = new Attach.VirtualMachine (pid);
758 vm.Attach (agent_assembly, agent_arg);
760 /* Wait for the client to connect */
761 TcpClient client = listener.AcceptTcpClient ();
762 ns = client.GetStream ();
763 TcpClient interrupt_client = interrupt_listener.AcceptTcpClient ();
764 interrupt_stream = interrupt_client.GetStream ();
766 Console.WriteLine ("Connected.");
770 // A remote version of Evaluate
772 protected override string Evaluate (string input)
774 ns.WriteString (input);
776 AgentStatus s = (AgentStatus) ns.ReadByte ();
779 case AgentStatus.PARTIAL_INPUT:
782 case AgentStatus.ERROR:
783 string err = ns.GetString ();
784 Console.Error.WriteLine (err);
787 case AgentStatus.RESULT_NOT_SET:
790 case AgentStatus.RESULT_SET:
791 string res = ns.GetString ();
792 Console.WriteLine (res);
798 public override int Run (string [] startup_files)
800 // The difference is that we do not call Evaluator.Init, that is done on the target
801 this.startup_files = startup_files;
802 return ReadEvalPrintLoop ();
805 protected override void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
807 // Do not about our program
810 interrupt_stream.WriteByte (0);
811 int c = interrupt_stream.ReadByte ();
813 Console.WriteLine ("Execution interrupted");
819 // This is the agent loaded into the target process when using --attach.
823 NetworkStream interrupt_stream;
824 readonly Evaluator evaluator;
827 public CSharpAgent (Evaluator evaluator, String arg, TextWriter stderr)
829 this.evaluator = evaluator;
830 this.stderr = stderr;
831 new Thread (new ParameterizedThreadStart (Run)).Start (arg);
834 public void InterruptListener ()
837 int b = interrupt_stream.ReadByte();
840 evaluator.Interrupt ();
841 interrupt_stream.WriteByte (0);
845 public void Run (object o)
847 string arg = (string)o;
848 string ports = arg.Substring (8);
849 int sp = ports.IndexOf (':');
850 int port = Int32.Parse (ports.Substring (0, sp));
851 int interrupt_port = Int32.Parse (ports.Substring (sp+1));
853 Console.WriteLine ("csharp-agent: started, connecting to localhost:" + port);
855 TcpClient client = new TcpClient ("127.0.0.1", port);
856 TcpClient interrupt_client = new TcpClient ("127.0.0.1", interrupt_port);
857 Console.WriteLine ("csharp-agent: connected.");
859 NetworkStream s = client.GetStream ();
860 interrupt_stream = interrupt_client.GetStream ();
861 new Thread (InterruptListener).Start ();
864 // Add all assemblies loaded later
865 AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded;
867 // Add all currently loaded assemblies
868 foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies ()) {
869 // Some assemblies seem to be already loaded, and loading them again causes 'defined multiple times' errors
870 if (a.GetName ().Name != "mscorlib" && a.GetName ().Name != "System.Core" && a.GetName ().Name != "System")
871 evaluator.ReferenceAssembly (a);
876 AppDomain.CurrentDomain.AssemblyLoad -= AssemblyLoaded;
878 interrupt_client.Close ();
879 Console.WriteLine ("csharp-agent: disconnected.");
883 void AssemblyLoaded (object sender, AssemblyLoadEventArgs e)
885 evaluator.ReferenceAssembly (e.LoadedAssembly);
888 public void RunRepl (NetworkStream s)
892 while (!InteractiveBase.QuitRequested) {
895 StringWriter error_output = (StringWriter)stderr;
897 string line = s.GetString ();
905 input = input + "\n" + line;
908 input = evaluator.Evaluate (input, out result, out result_set);
909 } catch (Exception e) {
910 s.WriteByte ((byte) AgentStatus.ERROR);
911 s.WriteString (e.ToString ());
912 s.WriteByte ((byte) AgentStatus.RESULT_NOT_SET);
917 s.WriteByte ((byte) AgentStatus.PARTIAL_INPUT);
921 // Send warnings and errors back
922 error_string = error_output.ToString ();
923 if (error_string.Length != 0){
924 s.WriteByte ((byte) AgentStatus.ERROR);
925 s.WriteString (error_output.ToString ());
926 error_output.GetStringBuilder ().Clear ();
930 s.WriteByte ((byte) AgentStatus.RESULT_SET);
931 StringWriter sr = new StringWriter ();
932 CSharpShell.PrettyPrint (sr, result);
933 s.WriteString (sr.ToString ());
935 s.WriteByte ((byte) AgentStatus.RESULT_NOT_SET);
937 } catch (IOException) {
939 } catch (Exception e){
940 Console.WriteLine (e);
946 public class UnixUtils {
947 [System.Runtime.InteropServices.DllImport ("libc", EntryPoint="isatty")]
948 extern static int _isatty (int fd);
950 public static bool isatty (int fd)
953 return _isatty (fd) == 1;
962 namespace Mono.Management
964 interface IVirtualMachine {
965 void LoadAgent (string filename, string args);