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;
41 static string [] script_args;
43 public static string [] ScriptArgs => script_args;
45 static int Main (string [] args)
47 if (!SplitDriverAndScriptArguments (ref args, out script_args))
50 var cmd = new CommandLineParser (Console.Out);
51 cmd.UnknownOptionHandler += HandleExtraArguments;
53 // Enable unsafe code by default
54 var settings = new CompilerSettings () {
58 if (!cmd.ParseArguments (settings, args))
61 var startup_files = new string [settings.SourceFiles.Count];
63 foreach (var source in settings.SourceFiles)
64 startup_files [i++] = source.OriginalFullPathName;
65 settings.SourceFiles.Clear ();
67 TextWriter agent_stderr = null;
68 ReportPrinter printer;
70 agent_stderr = new StringWriter ();
71 printer = new StreamReportPrinter (agent_stderr);
73 printer = new ConsoleReportPrinter ();
76 var eval = new Evaluator (new CompilerContext (settings, printer));
78 eval.InteractiveBaseClass = typeof (InteractiveBaseShell);
79 eval.DescribeTypeExpressions = true;
80 eval.WaitOnTask = true;
84 if (attach.HasValue) {
85 shell = new ClientCSharpShell_v1 (eval, attach.Value);
86 } else if (agent != null) {
87 new CSharpAgent (eval, agent, agent_stderr).Run (startup_files);
91 if (target_host != null)
92 shell = new ClientCSharpShell (eval, target_host, target_port);
94 shell = new CSharpShell (eval);
96 return shell.Run (startup_files);
99 static bool SplitDriverAndScriptArguments (ref string [] driver_args, out string [] script_args)
101 // split command line arguments into two groups:
102 // - anything before '--' or '-s' goes to the mcs driver, which may
103 // call back into the csharp driver for further processing
104 // - anything after '--' or '-s' is made available to the REPL/script
105 // via the 'Args' global, similar to csi.
106 // - if '-s' is used, the argument immediately following it will
107 // also be processed by the mcs driver (e.g. a source file)
109 int driver_args_count = 0;
110 int script_args_offset = 0;
111 string script_file = null;
113 while (driver_args_count < driver_args.Length && script_args_offset == 0) {
114 switch (driver_args [driver_args_count]) {
116 script_args_offset = driver_args_count + 1;
119 if (driver_args_count + 1 >= driver_args.Length) {
121 Console.Error.WriteLine ("usage is: -s SCRIPT_FILE");
125 script_file = driver_args [driver_args_count];
126 script_args_offset = driver_args_count + 1;
134 if (script_args_offset > 0) {
135 int script_args_count = driver_args.Length - script_args_offset;
136 script_args = new string [script_args_count];
137 Array.Copy (driver_args, script_args_offset, script_args, 0, script_args_count);
139 script_args = Array.Empty<string> ();
141 Array.Resize (ref driver_args, driver_args_count);
142 if (script_file != null)
143 driver_args [driver_args_count - 1] = script_file;
148 static int HandleExtraArguments (string [] args, int pos)
150 switch (args [pos]) {
152 if (pos + 1 < args.Length) {
153 StartupEvalExpression = args[pos + 1];
158 if (pos + 1 < args.Length) {
159 attach = Int32.Parse (args[1]);
164 if (args [pos].StartsWith ("--server=")){
165 var hostport = args [pos].Substring (9);
166 int p = hostport.IndexOf (':');
168 target_host = hostport;
171 target_host = hostport.Substring (0,p);
172 if (!int.TryParse (hostport.Substring (p), out target_port)){
173 Console.Error.WriteLine ("Usage is: --server[=host[:port]");
174 Environment.Exit (1);
179 if (args [pos].StartsWith ("--client")){
180 target_host = "localhost";
184 if (args [pos].StartsWith ("--agent:")) {
196 public class InteractiveBaseShell : InteractiveBase {
197 static bool tab_at_start_completes;
199 static InteractiveBaseShell ()
201 tab_at_start_completes = false;
204 internal static Mono.Terminal.LineEditor Editor;
206 public static bool TabAtStartCompletes {
208 return tab_at_start_completes;
212 tab_at_start_completes = value;
214 Editor.TabAtStartCompletes = value;
218 public static new string help {
220 return InteractiveBase.help +
221 " TabAtStartCompletes - Whether tab will complete even on empty lines\n" +
222 " Args - Any command line arguments passed to csharp\n" +
223 " after the '--' (stop processing) argument";
227 public static string [] Args => Driver.ScriptArgs;
230 public class CSharpShell {
231 static bool isatty = true, is_unix = false;
232 protected string [] startup_files;
234 Mono.Terminal.LineEditor editor;
236 readonly Evaluator evaluator;
238 public CSharpShell (Evaluator evaluator)
240 this.evaluator = evaluator;
243 protected virtual void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
245 // Do not about our program
248 evaluator.Interrupt ();
254 string term = Environment.GetEnvironmentVariable ("TERM");
255 dumb = term == "dumb" || term == null || isatty == false;
259 editor = new Mono.Terminal.LineEditor ("csharp", 300) {
260 HeuristicsMode = "csharp"
262 InteractiveBaseShell.Editor = editor;
264 editor.AutoCompleteEvent += delegate (string s, int pos){
265 string prefix = null;
267 string complete = s.Substring (0, pos);
269 string [] completions = evaluator.GetCompletions (complete, out prefix);
271 return new Mono.Terminal.LineEditor.Completion (prefix, completions);
276 // This is a sample of how completions sould be implemented.
278 editor.AutoCompleteEvent += delegate (string s, int pos){
280 // Single match: "Substring": Sub-string
281 if (s.EndsWith ("Sub")){
282 return new string [] { "string" };
285 // Multiple matches: "ToString" and "ToLower"
286 if (s.EndsWith ("T")){
287 return new string [] { "ToString", "ToLower" };
293 Console.CancelKeyPress += ConsoleInterrupt;
296 string GetLine (bool primary)
298 string prompt = primary ? InteractiveBase.Prompt : InteractiveBase.ContinuationPrompt;
302 Console.Write (prompt);
304 return Console.ReadLine ();
306 return editor.Edit (prompt, "");
310 delegate string ReadLiner (bool primary);
312 void InitializeUsing ()
314 Evaluate ("using System; using System.Linq; using System.Collections.Generic; using System.Collections;");
317 void InitTerminal (bool show_banner)
319 int p = (int) Environment.OSVersion.Platform;
320 is_unix = (p == 4) || (p == 128);
322 isatty = !Console.IsInputRedirected && !Console.IsOutputRedirected;
324 // Work around, since Console is not accounting for
325 // cursor position when writing to Stderr. It also
326 // has the undesirable side effect of making
327 // errors plain, with no coloring.
328 // Report.Stderr = Console.Out;
331 if (isatty && show_banner)
332 Console.WriteLine ("Mono C# Shell, type \"help;\" for help\n\nEnter statements below.");
336 void ExecuteSources (IEnumerable<string> sources, bool ignore_errors)
338 foreach (string file in sources){
343 using (System.IO.StreamReader r = System.IO.File.OpenText (file)){
344 ReadEvalPrintLoopWith (p => {
345 var line = r.ReadLine ();
347 if (line.StartsWith ("#!"))
348 line = r.ReadLine ();
354 } catch (FileNotFoundException){
355 Console.Error.WriteLine ("cs2001: Source file `{0}' not found", file);
365 protected virtual void LoadStartupFiles ()
367 string dir = Path.Combine (
368 Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
370 if (!Directory.Exists (dir))
373 List<string> sources = new List<string> ();
374 List<string> libraries = new List<string> ();
376 foreach (string file in System.IO.Directory.GetFiles (dir)){
377 string l = file.ToLower ();
379 if (l.EndsWith (".cs"))
381 else if (l.EndsWith (".dll"))
382 libraries.Add (file);
385 foreach (string file in libraries)
386 evaluator.LoadAssembly (file);
388 ExecuteSources (sources, true);
391 void ReadEvalPrintLoopWith (ReadLiner readline)
394 while (!InteractiveBase.QuitRequested){
395 string input = readline (expr == null);
402 expr = expr == null ? input : expr + "\n" + input;
404 expr = Evaluate (expr);
408 public int ReadEvalPrintLoop ()
410 if (startup_files != null && startup_files.Length == 0)
411 InitTerminal (startup_files.Length == 0 && Driver.StartupEvalExpression == null);
417 if (startup_files != null && startup_files.Length != 0) {
418 ExecuteSources (startup_files, false);
420 if (Driver.StartupEvalExpression != null){
421 ReadEvalPrintLoopWith (p => {
422 var ret = Driver.StartupEvalExpression;
423 Driver.StartupEvalExpression = null;
427 ReadEvalPrintLoopWith (GetLine);
430 editor.SaveHistory ();
433 Console.CancelKeyPress -= ConsoleInterrupt;
438 protected virtual string Evaluate (string input)
444 input = evaluator.Evaluate (input, out result, out result_set);
447 PrettyPrint (Console.Out, result);
448 Console.WriteLine ();
450 } catch (Exception e){
451 Console.WriteLine (e);
458 static void p (TextWriter output, string s)
463 static void EscapeString (TextWriter output, string s)
465 foreach (var c in s){
472 output.Write ("\\\""); break;
474 output.Write ("\\a"); break;
476 output.Write ("\\b"); break;
482 output.Write ("\\v");
486 output.Write ("\\r");
490 output.Write ("\\f");
494 output.Write ("\\t");
498 output.Write ("\\x{0:x}", (int) c);
504 static void EscapeChar (TextWriter output, char c)
507 output.Write ("'\\''");
511 output.Write ("'{0}'", c);
516 output.Write ("'\\a'");
520 output.Write ("'\\b'");
524 output.Write ("'\\n'");
528 output.Write ("'\\v'");
532 output.Write ("'\\r'");
536 output.Write ("'\\f'");
540 output.Write ("'\\t");
544 output.Write ("'\\x{0:x}'", (int) c);
549 // Some types (System.Json.JsonPrimitive) implement
550 // IEnumerator and yet, throw an exception when we
551 // try to use them, helper function to check for that
553 static internal bool WorksAsEnumerable (object obj)
555 IEnumerable enumerable = obj as IEnumerable;
556 if (enumerable != null){
558 enumerable.GetEnumerator ();
561 // nothing, we return false below
567 internal static void PrettyPrint (TextWriter output, object result)
574 if (result is Array){
575 Array a = (Array) result;
578 int top = a.GetUpperBound (0);
579 for (int i = a.GetLowerBound (0); i <= top; i++){
580 PrettyPrint (output, a.GetValue (i));
585 } else if (result is bool){
590 } else if (result is string){
592 EscapeString (output, (string)result);
594 } else if (result is IDictionary){
595 IDictionary dict = (IDictionary) result;
596 int top = dict.Count, count = 0;
599 foreach (DictionaryEntry entry in dict){
602 PrettyPrint (output, entry.Key);
604 PrettyPrint (output, entry.Value);
611 } else if (WorksAsEnumerable (result)) {
614 foreach (object item in (IEnumerable) result) {
618 PrettyPrint (output, item);
621 } else if (result is char) {
622 EscapeChar (output, (char) result);
624 p (output, result.ToString ());
628 public virtual int Run (string [] startup_files)
630 this.startup_files = startup_files;
631 return ReadEvalPrintLoop ();
637 // Stream helper extension methods
639 public static class StreamHelper {
640 static DataConverter converter = DataConverter.LittleEndian;
642 static void GetBuffer (this Stream stream, byte [] b)
648 n = stream.Read (b, offset, len);
650 throw new IOException ("End reached");
657 public static int GetInt (this Stream stream)
659 byte [] b = new byte [4];
660 stream.GetBuffer (b);
661 return converter.GetInt32 (b, 0);
664 public static string GetString (this Stream stream)
666 int len = stream.GetInt ();
670 byte [] b = new byte [len];
671 stream.GetBuffer (b);
673 return Encoding.UTF8.GetString (b);
676 public static void WriteInt (this Stream stream, int n)
678 byte [] bytes = converter.GetBytes (n);
679 stream.Write (bytes, 0, bytes.Length);
682 public static void WriteString (this Stream stream, string s)
684 stream.WriteInt (s.Length);
685 byte [] bytes = Encoding.UTF8.GetBytes (s);
686 stream.Write (bytes, 0, bytes.Length);
690 public enum AgentStatus : byte {
691 // Received partial input, complete
694 // The result was set, expect the string with the result
697 // No result was set, complete
700 // Errors and warnings string follows
707 class ClientCSharpShell : CSharpShell {
711 public ClientCSharpShell (Evaluator evaluator, string target_host, int target_port) : base (evaluator)
713 this.target_port = target_port;
714 this.target_host = target_host;
717 T ConnectServer<T> (Func<NetworkStream,T> callback, Action<Exception> error)
720 var client = new TcpClient (target_host, target_port);
721 var ns = client.GetStream ();
722 T ret = callback (ns);
727 } catch (Exception e){
733 protected override string Evaluate (string input)
735 return ConnectServer<string> ((ns)=> {
737 ns.WriteString ("EVALTXT");
738 ns.WriteString (input);
741 AgentStatus s = (AgentStatus) ns.ReadByte ();
744 case AgentStatus.PARTIAL_INPUT:
747 case AgentStatus.ERROR:
748 string err = ns.GetString ();
749 Console.Error.WriteLine (err);
752 case AgentStatus.STDOUT:
753 string stdout = ns.GetString ();
754 Console.WriteLine (stdout);
757 case AgentStatus.RESULT_NOT_SET:
760 case AgentStatus.RESULT_SET:
761 string res = ns.GetString ();
762 Console.WriteLine (res);
766 } catch (Exception e){
767 Console.Error.WriteLine ("Error evaluating expression, exception: {0}", e);
771 Console.Error.WriteLine ("Error communicating with server {0}", e);
775 public override int Run (string [] startup_files)
777 // The difference is that we do not call Evaluator.Init, that is done on the target
778 this.startup_files = startup_files;
779 return ReadEvalPrintLoop ();
782 protected override void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
784 ConnectServer<int> ((ns)=> {
785 ns.WriteString ("INTERRUPT");
794 // A shell connected to a CSharpAgent running in a remote process.
795 // - maybe add 'class_name' and 'method_name' arguments to LoadAgent.
796 // - Support Gtk and Winforms main loops if detected, this should
797 // probably be done as a separate agent in a separate place.
799 class ClientCSharpShell_v1 : CSharpShell {
800 NetworkStream ns, interrupt_stream;
802 public ClientCSharpShell_v1 (Evaluator evaluator, int pid)
805 // Create a server socket we listen on whose address is passed to the agent
806 TcpListener listener = new TcpListener (new IPEndPoint (IPAddress.Loopback, 0));
808 TcpListener interrupt_listener = new TcpListener (new IPEndPoint (IPAddress.Loopback, 0));
809 interrupt_listener.Start ();
811 string agent_assembly = typeof (ClientCSharpShell).Assembly.Location;
812 string agent_arg = String.Format ("--agent:{0}:{1}" ,
813 ((IPEndPoint)listener.Server.LocalEndPoint).Port,
814 ((IPEndPoint)interrupt_listener.Server.LocalEndPoint).Port);
816 var vm = new Attach.VirtualMachine (pid);
817 vm.Attach (agent_assembly, agent_arg);
819 /* Wait for the client to connect */
820 TcpClient client = listener.AcceptTcpClient ();
821 ns = client.GetStream ();
822 TcpClient interrupt_client = interrupt_listener.AcceptTcpClient ();
823 interrupt_stream = interrupt_client.GetStream ();
825 Console.WriteLine ("Connected.");
829 // A remote version of Evaluate
831 protected override string Evaluate (string input)
833 ns.WriteString (input);
835 AgentStatus s = (AgentStatus) ns.ReadByte ();
838 case AgentStatus.PARTIAL_INPUT:
841 case AgentStatus.ERROR:
842 string err = ns.GetString ();
843 Console.Error.WriteLine (err);
846 case AgentStatus.RESULT_NOT_SET:
849 case AgentStatus.RESULT_SET:
850 string res = ns.GetString ();
851 Console.WriteLine (res);
857 public override int Run (string [] startup_files)
859 // The difference is that we do not call Evaluator.Init, that is done on the target
860 this.startup_files = startup_files;
861 return ReadEvalPrintLoop ();
864 protected override void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
866 // Do not about our program
869 interrupt_stream.WriteByte (0);
870 int c = interrupt_stream.ReadByte ();
872 Console.WriteLine ("Execution interrupted");
878 // This is the agent loaded into the target process when using --attach.
882 NetworkStream interrupt_stream;
883 readonly Evaluator evaluator;
886 public CSharpAgent (Evaluator evaluator, String arg, TextWriter stderr)
888 this.evaluator = evaluator;
889 this.stderr = stderr;
890 new Thread (new ParameterizedThreadStart (Run)).Start (arg);
893 public void InterruptListener ()
896 int b = interrupt_stream.ReadByte();
899 evaluator.Interrupt ();
900 interrupt_stream.WriteByte (0);
904 public void Run (object o)
906 string arg = (string)o;
907 string ports = arg.Substring (8);
908 int sp = ports.IndexOf (':');
909 int port = Int32.Parse (ports.Substring (0, sp));
910 int interrupt_port = Int32.Parse (ports.Substring (sp+1));
912 Console.WriteLine ("csharp-agent: started, connecting to localhost:" + port);
914 TcpClient client = new TcpClient ("127.0.0.1", port);
915 TcpClient interrupt_client = new TcpClient ("127.0.0.1", interrupt_port);
916 Console.WriteLine ("csharp-agent: connected.");
918 NetworkStream s = client.GetStream ();
919 interrupt_stream = interrupt_client.GetStream ();
920 new Thread (InterruptListener).Start ();
923 // Add all assemblies loaded later
924 AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded;
926 // Add all currently loaded assemblies
927 foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies ()) {
928 // Some assemblies seem to be already loaded, and loading them again causes 'defined multiple times' errors
929 if (a.GetName ().Name != "mscorlib" && a.GetName ().Name != "System.Core" && a.GetName ().Name != "System")
930 evaluator.ReferenceAssembly (a);
935 AppDomain.CurrentDomain.AssemblyLoad -= AssemblyLoaded;
937 interrupt_client.Close ();
938 Console.WriteLine ("csharp-agent: disconnected.");
942 void AssemblyLoaded (object sender, AssemblyLoadEventArgs e)
944 evaluator.ReferenceAssembly (e.LoadedAssembly);
947 public void RunRepl (NetworkStream s)
951 while (!InteractiveBase.QuitRequested) {
954 StringWriter error_output = (StringWriter)stderr;
956 string line = s.GetString ();
964 input = input + "\n" + line;
967 input = evaluator.Evaluate (input, out result, out result_set);
968 } catch (Exception e) {
969 s.WriteByte ((byte) AgentStatus.ERROR);
970 s.WriteString (e.ToString ());
971 s.WriteByte ((byte) AgentStatus.RESULT_NOT_SET);
976 s.WriteByte ((byte) AgentStatus.PARTIAL_INPUT);
980 // Send warnings and errors back
981 error_string = error_output.ToString ();
982 if (error_string.Length != 0){
983 s.WriteByte ((byte) AgentStatus.ERROR);
984 s.WriteString (error_output.ToString ());
985 error_output.GetStringBuilder ().Clear ();
989 s.WriteByte ((byte) AgentStatus.RESULT_SET);
990 StringWriter sr = new StringWriter ();
991 CSharpShell.PrettyPrint (sr, result);
992 s.WriteString (sr.ToString ());
994 s.WriteByte ((byte) AgentStatus.RESULT_NOT_SET);
996 } catch (IOException) {
998 } catch (Exception e){
999 Console.WriteLine (e);
1005 public class UnixUtils {
1006 [System.Runtime.InteropServices.DllImport ("libc", EntryPoint="isatty")]
1007 extern static int _isatty (int fd);
1009 public static bool isatty (int fd)
1012 return _isatty (fd) == 1;
1021 namespace Mono.Management
1023 interface IVirtualMachine {
1024 void LoadAgent (string filename, string args);