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;
36 static int Main (string [] args)
38 if (args.Length > 0 && args [0] == "--attach") {
39 new ClientCSharpShell (Int32.Parse (args [1])).Run ();
41 } else if (args.Length > 0 && args [0].StartsWith ("--agent:")) {
42 new CSharpAgent (args [0]);
46 Evaluator.Init (args);
51 return new CSharpShell ().Run ();
56 public class CSharpShell {
57 static bool isatty = true;
59 Mono.Terminal.LineEditor editor;
62 protected virtual void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
64 // Do not about our program
67 Mono.CSharp.Evaluator.Interrupt ();
72 string term = Environment.GetEnvironmentVariable ("TERM");
73 dumb = term == "dumb" || term == null || isatty == false;
75 editor = new Mono.Terminal.LineEditor ("csharp", 300);
78 // This is a sample of how completions sould be implemented.
80 editor.AutoCompleteEvent += delegate (string s, int pos){
82 // Single match: "Substring": Sub-string
83 if (s.EndsWith ("Sub")){
84 return new string [] { "string" };
87 // Multiple matches: "ToString" and "ToLower"
88 if (s.EndsWith ("T")){
89 return new string [] { "ToString", "ToLower" };
95 Console.CancelKeyPress += ConsoleInterrupt;
98 string GetLine (bool primary)
100 string prompt = primary ? InteractiveBase.Prompt : InteractiveBase.ContinuationPrompt;
104 Console.Write (prompt);
106 return Console.ReadLine ();
108 return editor.Edit (prompt, "");
112 delegate string ReadLiner (bool primary);
114 void InitializeUsing ()
116 Evaluate ("using System; using System.Linq; using System.Collections.Generic; using System.Collections;");
121 isatty = UnixUtils.isatty (0) && UnixUtils.isatty (1);
123 // Work around, since Console is not accounting for
124 // cursor position when writing to Stderr. It also
125 // has the undesirable side effect of making
126 // errors plain, with no coloring.
127 Report.Stderr = Console.Out;
131 Console.WriteLine ("Mono C# Shell, type \"help;\" for help\n\nEnter statements below.");
135 protected virtual void LoadStartupFiles ()
137 string dir = Path.Combine (
138 Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
140 if (!Directory.Exists (dir))
143 foreach (string file in Directory.GetFiles (dir)){
144 string l = file.ToLower ();
146 if (l.EndsWith (".cs")){
148 using (StreamReader r = File.OpenText (file)){
149 ReadEvalPrintLoopWith (p => r.ReadLine ());
153 } else if (l.EndsWith (".dll")){
154 Evaluator.LoadAssembly (file);
159 void ReadEvalPrintLoopWith (ReadLiner readline)
163 string input = readline (expr == null);
170 expr = expr == null ? input : expr + "\n" + input;
172 expr = Evaluate (expr);
176 public int ReadEvalPrintLoop ()
183 ReadEvalPrintLoopWith (GetLine);
188 protected virtual string Evaluate (string input)
194 input = Evaluator.Evaluate (input, out result, out result_set);
197 PrettyPrint (Console.Out, result);
198 Console.WriteLine ();
200 } catch (Exception e){
201 Console.WriteLine (e);
208 static void p (TextWriter output, string s)
213 static string EscapeString (string s)
215 return s.Replace ("\"", "\\\"");
218 static void EscapeChar (TextWriter output, char c)
221 output.Write ("'\\''");
225 output.Write ("'{0}'", c);
230 output.Write ("'\\a'");
234 output.Write ("'\\b'");
238 output.Write ("'\\n'");
242 output.Write ("'\\v'");
246 output.Write ("'\\r'");
250 output.Write ("'\\f'");
254 output.Write ("'\\t");
258 output.Write ("'\\x{0:x}", (int) c);
263 internal static void PrettyPrint (TextWriter output, object result)
270 if (result is Array){
271 Array a = (Array) result;
274 int top = a.GetUpperBound (0);
275 for (int i = a.GetLowerBound (0); i <= top; i++){
276 PrettyPrint (output, a.GetValue (i));
281 } else if (result is bool){
286 } else if (result is string){
287 p (output, String.Format ("\"{0}\"", EscapeString ((string)result)));
288 } else if (result is IDictionary){
289 IDictionary dict = (IDictionary) result;
290 int top = dict.Count, count = 0;
293 foreach (DictionaryEntry entry in dict){
296 PrettyPrint (output, entry.Key);
298 PrettyPrint (output, entry.Value);
305 } else if (result is IEnumerable) {
308 foreach (object item in (IEnumerable) result) {
312 PrettyPrint (output, item);
315 } else if (result is char) {
316 EscapeChar (output, (char) result);
318 p (output, result.ToString ());
322 public CSharpShell ()
326 public virtual int Run ()
328 return ReadEvalPrintLoop ();
334 // A shell connected to a CSharpAgent running in a remote process.
335 // - maybe add 'class_name' and 'method_name' arguments to LoadAgent.
336 // - Support Gtk and Winforms main loops if detected, this should
337 // probably be done as a separate agent in a separate place.
339 class ClientCSharpShell : CSharpShell {
340 NetworkStream ns, interrupt_stream;
342 public ClientCSharpShell (int pid)
344 // Create a server socket we listen on whose address is passed to the agent
345 TcpListener listener = new TcpListener (new IPEndPoint (IPAddress.Loopback, 0));
347 TcpListener interrupt_listener = new TcpListener (new IPEndPoint (IPAddress.Loopback, 0));
348 interrupt_listener.Start ();
350 string agent_assembly = typeof (ClientCSharpShell).Assembly.Location;
351 string agent_arg = String.Format ("--agent:{0}:{1}" ,
352 ((IPEndPoint)listener.Server.LocalEndPoint).Port,
353 ((IPEndPoint)interrupt_listener.Server.LocalEndPoint).Port);
355 VirtualMachine vm = new VirtualMachine (pid);
356 vm.Attach (agent_assembly, agent_arg);
358 /* Wait for the client to connect */
359 TcpClient client = listener.AcceptTcpClient ();
360 ns = client.GetStream ();
361 TcpClient interrupt_client = interrupt_listener.AcceptTcpClient ();
362 interrupt_stream = interrupt_client.GetStream ();
364 Console.WriteLine ("Connected.");
368 // A remote version of Evaluate
370 protected override string Evaluate (string input)
372 ns.WriteString (input);
374 AgentStatus s = (AgentStatus) ns.ReadByte ();
377 case AgentStatus.PARTIAL_INPUT:
380 case AgentStatus.ERROR:
381 string err = ns.GetString ();
382 Console.Error.WriteLine (err);
385 case AgentStatus.RESULT_NOT_SET:
388 case AgentStatus.RESULT_SET:
389 string res = ns.GetString ();
390 Console.WriteLine (res);
396 public override int Run ()
398 // The difference is that we do not call Evaluator.Init, that is done on the target
399 return ReadEvalPrintLoop ();
402 protected override void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
404 // Do not about our program
407 interrupt_stream.WriteByte (0);
408 int c = interrupt_stream.ReadByte ();
410 Console.WriteLine ("Execution interrupted");
416 // Stream helper extension methods
418 public static class StreamHelper {
419 static DataConverter converter = DataConverter.LittleEndian;
421 public static int GetInt (this Stream stream)
423 byte [] b = new byte [4];
424 if (stream.Read (b, 0, 4) != 4)
425 throw new IOException ("End reached");
426 return converter.GetInt32 (b, 0);
429 public static string GetString (this Stream stream)
431 int len = stream.GetInt ();
432 byte [] b = new byte [len];
433 if (stream.Read (b, 0, len) != len)
434 throw new IOException ("End reached");
435 return Encoding.UTF8.GetString (b);
438 public static void WriteInt (this Stream stream, int n)
440 byte [] bytes = converter.GetBytes (n);
441 stream.Write (bytes, 0, bytes.Length);
444 public static void WriteString (this Stream stream, string s)
446 stream.WriteInt (s.Length);
447 byte [] bytes = Encoding.UTF8.GetBytes (s);
448 stream.Write (bytes, 0, bytes.Length);
452 public enum AgentStatus : byte {
453 // Received partial input, complete
456 // The result was set, expect the string with the result
459 // No result was set, complete
462 // Errors and warnings string follows
467 // This is the agent loaded into the target process when using --attach.
471 NetworkStream interrupt_stream;
473 public CSharpAgent (String arg)
475 new Thread (new ParameterizedThreadStart (Run)).Start (arg);
478 public void InterruptListener ()
481 int b = interrupt_stream.ReadByte();
484 Evaluator.Interrupt ();
485 interrupt_stream.WriteByte (0);
489 public void Run (object o)
491 string arg = (string)o;
492 string ports = arg.Substring (8);
493 int sp = ports.IndexOf (':');
494 int port = Int32.Parse (ports.Substring (0, sp));
495 int interrupt_port = Int32.Parse (ports.Substring (sp+1));
497 Console.WriteLine ("csharp-agent: started, connecting to localhost:" + port);
499 TcpClient client = new TcpClient ("127.0.0.1", port);
500 TcpClient interrupt_client = new TcpClient ("127.0.0.1", interrupt_port);
501 Console.WriteLine ("csharp-agent: connected.");
503 NetworkStream s = client.GetStream ();
504 interrupt_stream = interrupt_client.GetStream ();
505 new Thread (InterruptListener).Start ();
508 Evaluator.Init (new string [0]);
510 // TODO: send a result back.
511 Console.WriteLine ("csharp-agent: initialization failed");
516 // Add all assemblies loaded later
517 AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded;
519 // Add all currently loaded assemblies
520 foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies ())
521 Evaluator.ReferenceAssembly (a);
525 AppDomain.CurrentDomain.AssemblyLoad -= AssemblyLoaded;
527 interrupt_client.Close ();
528 Console.WriteLine ("csharp-agent: disconnected.");
532 static void AssemblyLoaded (object sender, AssemblyLoadEventArgs e)
534 Evaluator.ReferenceAssembly (e.LoadedAssembly);
537 public void RunRepl (NetworkStream s)
541 while (!InteractiveBase.QuitRequested) {
544 StringWriter error_output = new StringWriter ();
545 Report.Stderr = error_output;
547 string line = s.GetString ();
555 input = input + "\n" + line;
558 input = Evaluator.Evaluate (input, out result, out result_set);
559 } catch (Exception e) {
560 s.WriteByte ((byte) AgentStatus.ERROR);
561 s.WriteString (e.ToString ());
562 s.WriteByte ((byte) AgentStatus.RESULT_NOT_SET);
567 s.WriteByte ((byte) AgentStatus.PARTIAL_INPUT);
571 // Send warnings and errors back
572 error_string = error_output.ToString ();
573 if (error_string.Length != 0){
574 s.WriteByte ((byte) AgentStatus.ERROR);
575 s.WriteString (error_output.ToString ());
579 s.WriteByte ((byte) AgentStatus.RESULT_SET);
580 StringWriter sr = new StringWriter ();
581 CSharpShell.PrettyPrint (sr, result);
582 s.WriteString (sr.ToString ());
584 s.WriteByte ((byte) AgentStatus.RESULT_NOT_SET);
586 } catch (IOException) {
588 } catch (Exception e){
589 Console.WriteLine (e);
596 namespace Mono.Management
598 interface IVirtualMachine {
599 void LoadAgent (string filename, string args);