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
14 // Do not print results in Evaluate, do that elsewhere in preparation for Eval refactoring.
15 // Driver.PartialReset should not reset the coretypes, nor the optional types, to avoid
16 // computing that on every call.
21 using System.Globalization;
22 using System.Collections;
23 using System.Reflection;
24 using System.Reflection.Emit;
25 using System.Threading;
27 using System.Net.Sockets;
28 using System.Collections.Generic;
35 public static string StartupEvalExpression;
39 static int Main (string [] args)
41 var cmd = new CommandLineParser (Console.Out);
42 cmd.UnknownOptionHandler += HandleExtraArguments;
44 var settings = cmd.ParseArguments (args);
47 var startup_files = new string [settings.SourceFiles.Count];
49 foreach (var source in settings.SourceFiles)
50 startup_files [i++] = source.FullPathName;
51 settings.SourceFiles.Clear ();
53 TextWriter agent_stderr = null;
54 ReportPrinter printer;
56 agent_stderr = new StringWriter ();
57 printer = new StreamReportPrinter (agent_stderr);
59 printer = new ConsoleReportPrinter ();
62 var eval = new Evaluator (new CompilerContext (settings, printer));
64 eval.InteractiveBaseClass = typeof (InteractiveBaseShell);
65 eval.DescribeTypeExpressions = true;
69 if (attach.HasValue) {
70 shell = new ClientCSharpShell (eval, attach.Value);
71 } else if (agent != null) {
72 new CSharpAgent (eval, agent, agent_stderr).Run (startup_files);
77 shell = new CSharpShell (eval);
79 return shell.Run (startup_files);
82 static int HandleExtraArguments (string [] args, int pos)
86 if (pos + 1 < args.Length) {
87 StartupEvalExpression = args[pos + 1];
92 if (pos + 1 < args.Length) {
93 attach = Int32.Parse (args[1]);
98 if (args [pos].StartsWith ("--agent:")) {
110 public class InteractiveBaseShell : InteractiveBase {
111 static bool tab_at_start_completes;
113 static InteractiveBaseShell ()
115 tab_at_start_completes = false;
118 internal static Mono.Terminal.LineEditor Editor;
120 public static bool TabAtStartCompletes {
122 return tab_at_start_completes;
126 tab_at_start_completes = value;
128 Editor.TabAtStartCompletes = value;
132 public static new string help {
134 return InteractiveBase.help +
135 " TabAtStartCompletes - Whether tab will complete even on empty lines\n";
140 public class CSharpShell {
141 static bool isatty = true, is_unix = false;
142 protected string [] startup_files;
144 Mono.Terminal.LineEditor editor;
146 readonly Evaluator evaluator;
148 public CSharpShell (Evaluator evaluator)
150 this.evaluator = evaluator;
153 protected virtual void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
155 // Do not about our program
158 evaluator.Interrupt ();
164 string term = Environment.GetEnvironmentVariable ("TERM");
165 dumb = term == "dumb" || term == null || isatty == false;
169 editor = new Mono.Terminal.LineEditor ("csharp", 300);
170 InteractiveBaseShell.Editor = editor;
172 editor.AutoCompleteEvent += delegate (string s, int pos){
173 string prefix = null;
175 string complete = s.Substring (0, pos);
177 string [] completions = evaluator.GetCompletions (complete, out prefix);
179 return new Mono.Terminal.LineEditor.Completion (prefix, completions);
184 // This is a sample of how completions sould be implemented.
186 editor.AutoCompleteEvent += delegate (string s, int pos){
188 // Single match: "Substring": Sub-string
189 if (s.EndsWith ("Sub")){
190 return new string [] { "string" };
193 // Multiple matches: "ToString" and "ToLower"
194 if (s.EndsWith ("T")){
195 return new string [] { "ToString", "ToLower" };
201 Console.CancelKeyPress += ConsoleInterrupt;
204 string GetLine (bool primary)
206 string prompt = primary ? InteractiveBase.Prompt : InteractiveBase.ContinuationPrompt;
210 Console.Write (prompt);
212 return Console.ReadLine ();
214 return editor.Edit (prompt, "");
218 delegate string ReadLiner (bool primary);
220 void InitializeUsing ()
222 Evaluate ("using System; using System.Linq; using System.Collections.Generic; using System.Collections;");
225 void InitTerminal (bool show_banner)
227 int p = (int) Environment.OSVersion.Platform;
228 is_unix = (p == 4) || (p == 128);
231 isatty = !Console.IsInputRedirected && !Console.IsOutputRedirected;
236 // Work around, since Console is not accounting for
237 // cursor position when writing to Stderr. It also
238 // has the undesirable side effect of making
239 // errors plain, with no coloring.
240 // Report.Stderr = Console.Out;
243 if (isatty && show_banner)
244 Console.WriteLine ("Mono C# Shell, type \"help;\" for help\n\nEnter statements below.");
248 void ExecuteSources (IEnumerable<string> sources, bool ignore_errors)
250 foreach (string file in sources){
255 using (System.IO.StreamReader r = System.IO.File.OpenText (file)){
256 ReadEvalPrintLoopWith (p => {
257 var line = r.ReadLine ();
259 if (line.StartsWith ("#!"))
260 line = r.ReadLine ();
266 } catch (FileNotFoundException){
267 Console.Error.WriteLine ("cs2001: Source file `{0}' not found", file);
277 protected virtual void LoadStartupFiles ()
279 string dir = Path.Combine (
280 Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
282 if (!Directory.Exists (dir))
285 List<string> sources = new List<string> ();
286 List<string> libraries = new List<string> ();
288 foreach (string file in System.IO.Directory.GetFiles (dir)){
289 string l = file.ToLower ();
291 if (l.EndsWith (".cs"))
293 else if (l.EndsWith (".dll"))
294 libraries.Add (file);
297 foreach (string file in libraries)
298 evaluator.LoadAssembly (file);
300 ExecuteSources (sources, true);
303 void ReadEvalPrintLoopWith (ReadLiner readline)
306 while (!InteractiveBase.QuitRequested){
307 string input = readline (expr == null);
314 expr = expr == null ? input : expr + "\n" + input;
316 expr = Evaluate (expr);
320 public int ReadEvalPrintLoop ()
322 if (startup_files != null && startup_files.Length == 0)
323 InitTerminal (startup_files.Length == 0 && Driver.StartupEvalExpression == null);
329 if (startup_files != null && startup_files.Length != 0)
330 ExecuteSources (startup_files, false);
331 else if (Driver.StartupEvalExpression != null){
332 ReadEvalPrintLoopWith (p => {
333 var ret = Driver.StartupEvalExpression;
334 Driver.StartupEvalExpression = null;
338 ReadEvalPrintLoopWith (GetLine);
343 protected virtual string Evaluate (string input)
349 input = evaluator.Evaluate (input, out result, out result_set);
352 PrettyPrint (Console.Out, result);
353 Console.WriteLine ();
355 } catch (Exception e){
356 Console.WriteLine (e);
363 static void p (TextWriter output, string s)
368 static string EscapeString (string s)
370 return s.Replace ("\"", "\\\"");
373 static void EscapeChar (TextWriter output, char c)
376 output.Write ("'\\''");
380 output.Write ("'{0}'", c);
385 output.Write ("'\\a'");
389 output.Write ("'\\b'");
393 output.Write ("'\\n'");
397 output.Write ("'\\v'");
401 output.Write ("'\\r'");
405 output.Write ("'\\f'");
409 output.Write ("'\\t");
413 output.Write ("'\\x{0:x}", (int) c);
418 // Some types (System.Json.JsonPrimitive) implement
419 // IEnumerator and yet, throw an exception when we
420 // try to use them, helper function to check for that
422 static internal bool WorksAsEnumerable (object obj)
424 IEnumerable enumerable = obj as IEnumerable;
425 if (enumerable != null){
427 enumerable.GetEnumerator ();
430 // nothing, we return false below
436 internal static void PrettyPrint (TextWriter output, object result)
443 if (result is Array){
444 Array a = (Array) result;
447 int top = a.GetUpperBound (0);
448 for (int i = a.GetLowerBound (0); i <= top; i++){
449 PrettyPrint (output, a.GetValue (i));
454 } else if (result is bool){
459 } else if (result is string){
460 p (output, String.Format ("\"{0}\"", EscapeString ((string)result)));
461 } else if (result is IDictionary){
462 IDictionary dict = (IDictionary) result;
463 int top = dict.Count, count = 0;
466 foreach (DictionaryEntry entry in dict){
469 PrettyPrint (output, entry.Key);
471 PrettyPrint (output, entry.Value);
478 } else if (WorksAsEnumerable (result)) {
481 foreach (object item in (IEnumerable) result) {
485 PrettyPrint (output, item);
488 } else if (result is char) {
489 EscapeChar (output, (char) result);
491 p (output, result.ToString ());
495 public virtual int Run (string [] startup_files)
497 this.startup_files = startup_files;
498 return ReadEvalPrintLoop ();
505 // A shell connected to a CSharpAgent running in a remote process.
506 // - maybe add 'class_name' and 'method_name' arguments to LoadAgent.
507 // - Support Gtk and Winforms main loops if detected, this should
508 // probably be done as a separate agent in a separate place.
510 class ClientCSharpShell : CSharpShell {
511 NetworkStream ns, interrupt_stream;
513 public ClientCSharpShell (Evaluator evaluator, int pid)
516 // Create a server socket we listen on whose address is passed to the agent
517 TcpListener listener = new TcpListener (new IPEndPoint (IPAddress.Loopback, 0));
519 TcpListener interrupt_listener = new TcpListener (new IPEndPoint (IPAddress.Loopback, 0));
520 interrupt_listener.Start ();
522 string agent_assembly = typeof (ClientCSharpShell).Assembly.Location;
523 string agent_arg = String.Format ("--agent:{0}:{1}" ,
524 ((IPEndPoint)listener.Server.LocalEndPoint).Port,
525 ((IPEndPoint)interrupt_listener.Server.LocalEndPoint).Port);
527 var vm = new Attach.VirtualMachine (pid);
528 vm.Attach (agent_assembly, agent_arg);
530 /* Wait for the client to connect */
531 TcpClient client = listener.AcceptTcpClient ();
532 ns = client.GetStream ();
533 TcpClient interrupt_client = interrupt_listener.AcceptTcpClient ();
534 interrupt_stream = interrupt_client.GetStream ();
536 Console.WriteLine ("Connected.");
540 // A remote version of Evaluate
542 protected override string Evaluate (string input)
544 ns.WriteString (input);
546 AgentStatus s = (AgentStatus) ns.ReadByte ();
549 case AgentStatus.PARTIAL_INPUT:
552 case AgentStatus.ERROR:
553 string err = ns.GetString ();
554 Console.Error.WriteLine (err);
557 case AgentStatus.RESULT_NOT_SET:
560 case AgentStatus.RESULT_SET:
561 string res = ns.GetString ();
562 Console.WriteLine (res);
568 public override int Run (string [] startup_files)
570 // The difference is that we do not call Evaluator.Init, that is done on the target
571 this.startup_files = startup_files;
572 return ReadEvalPrintLoop ();
575 protected override void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
577 // Do not about our program
580 interrupt_stream.WriteByte (0);
581 int c = interrupt_stream.ReadByte ();
583 Console.WriteLine ("Execution interrupted");
589 // Stream helper extension methods
591 public static class StreamHelper {
592 static DataConverter converter = DataConverter.LittleEndian;
594 public static int GetInt (this Stream stream)
596 byte [] b = new byte [4];
597 if (stream.Read (b, 0, 4) != 4)
598 throw new IOException ("End reached");
599 return converter.GetInt32 (b, 0);
602 public static string GetString (this Stream stream)
604 int len = stream.GetInt ();
605 byte [] b = new byte [len];
606 if (stream.Read (b, 0, len) != len)
607 throw new IOException ("End reached");
608 return Encoding.UTF8.GetString (b);
611 public static void WriteInt (this Stream stream, int n)
613 byte [] bytes = converter.GetBytes (n);
614 stream.Write (bytes, 0, bytes.Length);
617 public static void WriteString (this Stream stream, string s)
619 stream.WriteInt (s.Length);
620 byte [] bytes = Encoding.UTF8.GetBytes (s);
621 stream.Write (bytes, 0, bytes.Length);
625 public enum AgentStatus : byte {
626 // Received partial input, complete
629 // The result was set, expect the string with the result
632 // No result was set, complete
635 // Errors and warnings string follows
640 // This is the agent loaded into the target process when using --attach.
644 NetworkStream interrupt_stream;
645 readonly Evaluator evaluator;
648 public CSharpAgent (Evaluator evaluator, String arg, TextWriter stderr)
650 this.evaluator = evaluator;
651 this.stderr = stderr;
652 new Thread (new ParameterizedThreadStart (Run)).Start (arg);
655 public void InterruptListener ()
658 int b = interrupt_stream.ReadByte();
661 evaluator.Interrupt ();
662 interrupt_stream.WriteByte (0);
666 public void Run (object o)
668 string arg = (string)o;
669 string ports = arg.Substring (8);
670 int sp = ports.IndexOf (':');
671 int port = Int32.Parse (ports.Substring (0, sp));
672 int interrupt_port = Int32.Parse (ports.Substring (sp+1));
674 Console.WriteLine ("csharp-agent: started, connecting to localhost:" + port);
676 TcpClient client = new TcpClient ("127.0.0.1", port);
677 TcpClient interrupt_client = new TcpClient ("127.0.0.1", interrupt_port);
678 Console.WriteLine ("csharp-agent: connected.");
680 NetworkStream s = client.GetStream ();
681 interrupt_stream = interrupt_client.GetStream ();
682 new Thread (InterruptListener).Start ();
685 // Add all assemblies loaded later
686 AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded;
688 // Add all currently loaded assemblies
689 foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies ()) {
690 // Some assemblies seem to be already loaded, and loading them again causes 'defined multiple times' errors
691 if (a.GetName ().Name != "mscorlib" && a.GetName ().Name != "System.Core" && a.GetName ().Name != "System")
692 evaluator.ReferenceAssembly (a);
697 AppDomain.CurrentDomain.AssemblyLoad -= AssemblyLoaded;
699 interrupt_client.Close ();
700 Console.WriteLine ("csharp-agent: disconnected.");
704 void AssemblyLoaded (object sender, AssemblyLoadEventArgs e)
706 evaluator.ReferenceAssembly (e.LoadedAssembly);
709 public void RunRepl (NetworkStream s)
713 while (!InteractiveBase.QuitRequested) {
716 StringWriter error_output = (StringWriter)stderr;
718 string line = s.GetString ();
726 input = input + "\n" + line;
729 input = evaluator.Evaluate (input, out result, out result_set);
730 } catch (Exception e) {
731 s.WriteByte ((byte) AgentStatus.ERROR);
732 s.WriteString (e.ToString ());
733 s.WriteByte ((byte) AgentStatus.RESULT_NOT_SET);
738 s.WriteByte ((byte) AgentStatus.PARTIAL_INPUT);
742 // Send warnings and errors back
743 error_string = error_output.ToString ();
744 if (error_string.Length != 0){
745 s.WriteByte ((byte) AgentStatus.ERROR);
746 s.WriteString (error_output.ToString ());
747 error_output.GetStringBuilder ().Clear ();
751 s.WriteByte ((byte) AgentStatus.RESULT_SET);
752 StringWriter sr = new StringWriter ();
753 CSharpShell.PrettyPrint (sr, result);
754 s.WriteString (sr.ToString ());
756 s.WriteByte ((byte) AgentStatus.RESULT_NOT_SET);
758 } catch (IOException) {
760 } catch (Exception e){
761 Console.WriteLine (e);
767 public class UnixUtils {
768 [System.Runtime.InteropServices.DllImport ("libc", EntryPoint="isatty")]
769 extern static int _isatty (int fd);
771 public static bool isatty (int fd)
774 return _isatty (fd) == 1;
783 namespace Mono.Management
785 interface IVirtualMachine {
786 void LoadAgent (string filename, string args);