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;
37 static int Main (string [] args)
39 if (args.Length > 0 && args [0] == "--attach") {
40 new ClientCSharpShell (Int32.Parse (args [1])).Run (null);
42 } else if (args.Length > 0 && args [0].StartsWith ("--agent:")) {
43 new CSharpAgent (args [0]);
46 string [] startup_files;
48 startup_files = Evaluator.InitAndGetStartupFiles (args);
49 Evaluator.InteractiveBaseClass = typeof (InteractiveBaseShell);
54 return new CSharpShell ().Run (startup_files);
59 public class InteractiveBaseShell : InteractiveBase {
60 static bool tab_at_start_completes;
62 static InteractiveBaseShell ()
64 tab_at_start_completes = false;
67 internal static Mono.Terminal.LineEditor Editor;
69 public static bool TabAtStartCompletes {
71 return tab_at_start_completes;
75 tab_at_start_completes = value;
77 Editor.TabAtStartCompletes = value;
81 public static new string help {
83 return InteractiveBase.help +
84 " TabAtStartCompletes - Whether tab will complete even on emtpy lines\n";
89 public class CSharpShell {
90 static bool isatty = true;
91 string [] startup_files;
93 Mono.Terminal.LineEditor editor;
96 protected virtual void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
98 // Do not about our program
101 Mono.CSharp.Evaluator.Interrupt ();
106 string term = Environment.GetEnvironmentVariable ("TERM");
107 dumb = term == "dumb" || term == null || isatty == false;
109 editor = new Mono.Terminal.LineEditor ("csharp", 300);
110 InteractiveBaseShell.Editor = editor;
112 editor.AutoCompleteEvent += delegate (string s, int pos){
113 string prefix = null;
115 string complete = s.Substring (0, pos);
117 string [] completions = Evaluator.GetCompletions (complete, out prefix);
119 return new Mono.Terminal.LineEditor.Completion (prefix, completions);
124 // This is a sample of how completions sould be implemented.
126 editor.AutoCompleteEvent += delegate (string s, int pos){
128 // Single match: "Substring": Sub-string
129 if (s.EndsWith ("Sub")){
130 return new string [] { "string" };
133 // Multiple matches: "ToString" and "ToLower"
134 if (s.EndsWith ("T")){
135 return new string [] { "ToString", "ToLower" };
141 Console.CancelKeyPress += ConsoleInterrupt;
144 string GetLine (bool primary)
146 string prompt = primary ? InteractiveBase.Prompt : InteractiveBase.ContinuationPrompt;
150 Console.Write (prompt);
152 return Console.ReadLine ();
154 return editor.Edit (prompt, "");
158 delegate string ReadLiner (bool primary);
160 void InitializeUsing ()
162 Evaluate ("using System; using System.Linq; using System.Collections.Generic; using System.Collections;");
167 isatty = UnixUtils.isatty (0) && UnixUtils.isatty (1);
169 // Work around, since Console is not accounting for
170 // cursor position when writing to Stderr. It also
171 // has the undesirable side effect of making
172 // errors plain, with no coloring.
173 // Report.Stderr = Console.Out;
177 Console.WriteLine ("Mono C# Shell, type \"help;\" for help\n\nEnter statements below.");
181 void ExecuteSources (IEnumerable<string> sources, bool ignore_errors)
183 foreach (string file in sources){
186 using (System.IO.StreamReader r = System.IO.File.OpenText (file)){
187 ReadEvalPrintLoopWith (p => r.ReadLine ());
189 } catch (FileNotFoundException){
190 Console.Error.WriteLine ("cs2001: Source file `{0}' not found", file);
200 protected virtual void LoadStartupFiles ()
202 string dir = Path.Combine (
203 Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
205 if (!Directory.Exists (dir))
208 List<string> sources = new List<string> ();
209 List<string> libraries = new List<string> ();
211 foreach (string file in System.IO.Directory.GetFiles (dir)){
212 string l = file.ToLower ();
214 if (l.EndsWith (".cs"))
216 else if (l.EndsWith (".dll"))
217 libraries.Add (file);
220 foreach (string file in libraries)
221 Evaluator.LoadAssembly (file);
223 ExecuteSources (sources, true);
226 void ReadEvalPrintLoopWith (ReadLiner readline)
229 while (!InteractiveBase.QuitRequested){
230 string input = readline (expr == null);
237 expr = expr == null ? input : expr + "\n" + input;
239 expr = Evaluate (expr);
243 public int ReadEvalPrintLoop ()
245 if (startup_files != null && startup_files.Length == 0)
253 // Interactive or startup files provided?
255 if (startup_files.Length != 0)
256 ExecuteSources (startup_files, false);
258 ReadEvalPrintLoopWith (GetLine);
263 protected virtual string Evaluate (string input)
269 input = Evaluator.Evaluate (input, out result, out result_set);
272 PrettyPrint (Console.Out, result);
273 Console.WriteLine ();
275 } catch (Exception e){
276 Console.WriteLine (e);
283 static void p (TextWriter output, string s)
288 static string EscapeString (string s)
290 return s.Replace ("\"", "\\\"");
293 static void EscapeChar (TextWriter output, char c)
296 output.Write ("'\\''");
300 output.Write ("'{0}'", c);
305 output.Write ("'\\a'");
309 output.Write ("'\\b'");
313 output.Write ("'\\n'");
317 output.Write ("'\\v'");
321 output.Write ("'\\r'");
325 output.Write ("'\\f'");
329 output.Write ("'\\t");
333 output.Write ("'\\x{0:x}", (int) c);
338 internal static void PrettyPrint (TextWriter output, object result)
345 if (result is Array){
346 Array a = (Array) result;
349 int top = a.GetUpperBound (0);
350 for (int i = a.GetLowerBound (0); i <= top; i++){
351 PrettyPrint (output, a.GetValue (i));
356 } else if (result is bool){
361 } else if (result is string){
362 p (output, String.Format ("\"{0}\"", EscapeString ((string)result)));
363 } else if (result is IDictionary){
364 IDictionary dict = (IDictionary) result;
365 int top = dict.Count, count = 0;
368 foreach (DictionaryEntry entry in dict){
371 PrettyPrint (output, entry.Key);
373 PrettyPrint (output, entry.Value);
380 } else if (result is IEnumerable) {
383 foreach (object item in (IEnumerable) result) {
387 PrettyPrint (output, item);
390 } else if (result is char) {
391 EscapeChar (output, (char) result);
393 p (output, result.ToString ());
397 public CSharpShell ()
401 public virtual int Run (string [] startup_files)
403 this.startup_files = startup_files;
404 return ReadEvalPrintLoop ();
410 // A shell connected to a CSharpAgent running in a remote process.
411 // - maybe add 'class_name' and 'method_name' arguments to LoadAgent.
412 // - Support Gtk and Winforms main loops if detected, this should
413 // probably be done as a separate agent in a separate place.
415 class ClientCSharpShell : CSharpShell {
416 NetworkStream ns, interrupt_stream;
418 public ClientCSharpShell (int pid)
420 // Create a server socket we listen on whose address is passed to the agent
421 TcpListener listener = new TcpListener (new IPEndPoint (IPAddress.Loopback, 0));
423 TcpListener interrupt_listener = new TcpListener (new IPEndPoint (IPAddress.Loopback, 0));
424 interrupt_listener.Start ();
426 string agent_assembly = typeof (ClientCSharpShell).Assembly.Location;
427 string agent_arg = String.Format ("--agent:{0}:{1}" ,
428 ((IPEndPoint)listener.Server.LocalEndPoint).Port,
429 ((IPEndPoint)interrupt_listener.Server.LocalEndPoint).Port);
431 VirtualMachine vm = new VirtualMachine (pid);
432 vm.Attach (agent_assembly, agent_arg);
434 /* Wait for the client to connect */
435 TcpClient client = listener.AcceptTcpClient ();
436 ns = client.GetStream ();
437 TcpClient interrupt_client = interrupt_listener.AcceptTcpClient ();
438 interrupt_stream = interrupt_client.GetStream ();
440 Console.WriteLine ("Connected.");
444 // A remote version of Evaluate
446 protected override string Evaluate (string input)
448 ns.WriteString (input);
450 AgentStatus s = (AgentStatus) ns.ReadByte ();
453 case AgentStatus.PARTIAL_INPUT:
456 case AgentStatus.ERROR:
457 string err = ns.GetString ();
458 Console.Error.WriteLine (err);
461 case AgentStatus.RESULT_NOT_SET:
464 case AgentStatus.RESULT_SET:
465 string res = ns.GetString ();
466 Console.WriteLine (res);
472 public override int Run (string [] startup_files)
474 // The difference is that we do not call Evaluator.Init, that is done on the target
475 return ReadEvalPrintLoop ();
478 protected override void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
480 // Do not about our program
483 interrupt_stream.WriteByte (0);
484 int c = interrupt_stream.ReadByte ();
486 Console.WriteLine ("Execution interrupted");
492 // Stream helper extension methods
494 public static class StreamHelper {
495 static DataConverter converter = DataConverter.LittleEndian;
497 public static int GetInt (this Stream stream)
499 byte [] b = new byte [4];
500 if (stream.Read (b, 0, 4) != 4)
501 throw new IOException ("End reached");
502 return converter.GetInt32 (b, 0);
505 public static string GetString (this Stream stream)
507 int len = stream.GetInt ();
508 byte [] b = new byte [len];
509 if (stream.Read (b, 0, len) != len)
510 throw new IOException ("End reached");
511 return Encoding.UTF8.GetString (b);
514 public static void WriteInt (this Stream stream, int n)
516 byte [] bytes = converter.GetBytes (n);
517 stream.Write (bytes, 0, bytes.Length);
520 public static void WriteString (this Stream stream, string s)
522 stream.WriteInt (s.Length);
523 byte [] bytes = Encoding.UTF8.GetBytes (s);
524 stream.Write (bytes, 0, bytes.Length);
528 public enum AgentStatus : byte {
529 // Received partial input, complete
532 // The result was set, expect the string with the result
535 // No result was set, complete
538 // Errors and warnings string follows
543 // This is the agent loaded into the target process when using --attach.
547 NetworkStream interrupt_stream;
549 public CSharpAgent (String arg)
551 new Thread (new ParameterizedThreadStart (Run)).Start (arg);
554 public void InterruptListener ()
557 int b = interrupt_stream.ReadByte();
560 Evaluator.Interrupt ();
561 interrupt_stream.WriteByte (0);
565 public void Run (object o)
567 string arg = (string)o;
568 string ports = arg.Substring (8);
569 int sp = ports.IndexOf (':');
570 int port = Int32.Parse (ports.Substring (0, sp));
571 int interrupt_port = Int32.Parse (ports.Substring (sp+1));
573 Console.WriteLine ("csharp-agent: started, connecting to localhost:" + port);
575 TcpClient client = new TcpClient ("127.0.0.1", port);
576 TcpClient interrupt_client = new TcpClient ("127.0.0.1", interrupt_port);
577 Console.WriteLine ("csharp-agent: connected.");
579 NetworkStream s = client.GetStream ();
580 interrupt_stream = interrupt_client.GetStream ();
581 new Thread (InterruptListener).Start ();
584 Evaluator.Init (new string [0]);
586 // TODO: send a result back.
587 Console.WriteLine ("csharp-agent: initialization failed");
592 // Add all assemblies loaded later
593 AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded;
595 // Add all currently loaded assemblies
596 foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies ())
597 Evaluator.ReferenceAssembly (a);
601 AppDomain.CurrentDomain.AssemblyLoad -= AssemblyLoaded;
603 interrupt_client.Close ();
604 Console.WriteLine ("csharp-agent: disconnected.");
608 static void AssemblyLoaded (object sender, AssemblyLoadEventArgs e)
610 Evaluator.ReferenceAssembly (e.LoadedAssembly);
613 public void RunRepl (NetworkStream s)
617 while (!InteractiveBase.QuitRequested) {
620 StringWriter error_output = new StringWriter ();
621 // Report.Stderr = error_output;
623 string line = s.GetString ();
631 input = input + "\n" + line;
634 input = Evaluator.Evaluate (input, out result, out result_set);
635 } catch (Exception e) {
636 s.WriteByte ((byte) AgentStatus.ERROR);
637 s.WriteString (e.ToString ());
638 s.WriteByte ((byte) AgentStatus.RESULT_NOT_SET);
643 s.WriteByte ((byte) AgentStatus.PARTIAL_INPUT);
647 // Send warnings and errors back
648 error_string = error_output.ToString ();
649 if (error_string.Length != 0){
650 s.WriteByte ((byte) AgentStatus.ERROR);
651 s.WriteString (error_output.ToString ());
655 s.WriteByte ((byte) AgentStatus.RESULT_SET);
656 StringWriter sr = new StringWriter ();
657 CSharpShell.PrettyPrint (sr, result);
658 s.WriteString (sr.ToString ());
660 s.WriteByte ((byte) AgentStatus.RESULT_NOT_SET);
662 } catch (IOException) {
664 } catch (Exception e){
665 Console.WriteLine (e);
671 public class UnixUtils {
672 [System.Runtime.InteropServices.DllImport ("libc", EntryPoint="isatty")]
673 extern static int _isatty (int fd);
675 public static bool isatty (int fd)
678 return _isatty (fd) == 1;
686 namespace Mono.Management
688 interface IVirtualMachine {
689 void LoadAgent (string filename, string args);