2010-05-19 Jb Evain <jbevain@novell.com>
[mcs.git] / tools / csharp / repl.cs
blob5137fcb4c4ad768a9b2ea9c3c8b468a0eb432df0
1 //
2 // repl.cs: Support for using the compiler in interactive mode (read-eval-print loop)
3 //
4 // Authors:
5 // Miguel de Icaza (miguel@gnome.org)
6 //
7 // Dual licensed under the terms of the MIT X11 or GNU GPL
8 //
9 // Copyright 2001, 2002, 2003 Ximian, Inc (http://www.ximian.com)
10 // Copyright 2004, 2005, 2006, 2007, 2008 Novell, Inc
13 // TODO:
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.
18 using System;
19 using System.IO;
20 using System.Text;
21 using System.Globalization;
22 using System.Collections;
23 using System.Reflection;
24 using System.Reflection.Emit;
25 using System.Threading;
26 using System.Net;
27 using System.Net.Sockets;
28 using System.Collections.Generic;
30 using Mono.CSharp;
32 namespace Mono {
34 public class Driver {
36 static int Main (string [] args)
38 #if !ON_DOTNET
39 if (args.Length > 0 && args [0] == "--attach") {
40 new ClientCSharpShell (Int32.Parse (args [1])).Run (null);
41 return 0;
44 if (args.Length > 0 && args [0].StartsWith ("--agent:")) {
45 new CSharpAgent (args [0]);
46 return 0;
48 #endif
49 return Startup(args);
52 static int Startup (string[] args)
54 string[] startup_files;
55 try {
56 startup_files = Evaluator.InitAndGetStartupFiles (args);
57 Evaluator.DescribeTypeExpressions = true;
58 Evaluator.SetInteractiveBaseClass (typeof (InteractiveBaseShell));
59 } catch {
60 return 1;
63 return new CSharpShell ().Run (startup_files);
67 public class InteractiveBaseShell : InteractiveBase {
68 static bool tab_at_start_completes;
70 static InteractiveBaseShell ()
72 tab_at_start_completes = false;
75 internal static Mono.Terminal.LineEditor Editor;
77 public static bool TabAtStartCompletes {
78 get {
79 return tab_at_start_completes;
82 set {
83 tab_at_start_completes = value;
84 if (Editor != null)
85 Editor.TabAtStartCompletes = value;
89 public static new string help {
90 get {
91 return InteractiveBase.help +
92 " TabAtStartCompletes - Whether tab will complete even on emtpy lines\n";
97 public class CSharpShell {
98 static bool isatty = true, is_unix = false;
99 string [] startup_files;
101 Mono.Terminal.LineEditor editor;
102 bool dumb;
104 protected virtual void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
106 // Do not about our program
107 a.Cancel = true;
109 Mono.CSharp.Evaluator.Interrupt ();
112 void SetupConsole ()
114 if (is_unix){
115 string term = Environment.GetEnvironmentVariable ("TERM");
116 dumb = term == "dumb" || term == null || isatty == false;
117 } else
118 dumb = false;
120 editor = new Mono.Terminal.LineEditor ("csharp", 300);
121 InteractiveBaseShell.Editor = editor;
123 editor.AutoCompleteEvent += delegate (string s, int pos){
124 string prefix = null;
126 string complete = s.Substring (0, pos);
128 string [] completions = Evaluator.GetCompletions (complete, out prefix);
130 return new Mono.Terminal.LineEditor.Completion (prefix, completions);
133 #if false
135 // This is a sample of how completions sould be implemented.
137 editor.AutoCompleteEvent += delegate (string s, int pos){
139 // Single match: "Substring": Sub-string
140 if (s.EndsWith ("Sub")){
141 return new string [] { "string" };
144 // Multiple matches: "ToString" and "ToLower"
145 if (s.EndsWith ("T")){
146 return new string [] { "ToString", "ToLower" };
148 return null;
150 #endif
152 Console.CancelKeyPress += ConsoleInterrupt;
155 string GetLine (bool primary)
157 string prompt = primary ? InteractiveBase.Prompt : InteractiveBase.ContinuationPrompt;
159 if (dumb){
160 if (isatty)
161 Console.Write (prompt);
163 return Console.ReadLine ();
164 } else {
165 return editor.Edit (prompt, "");
169 delegate string ReadLiner (bool primary);
171 void InitializeUsing ()
173 Evaluate ("using System; using System.Linq; using System.Collections.Generic; using System.Collections;");
176 void InitTerminal ()
178 #if ON_DOTNET
179 is_unix = false;
180 isatty = true;
181 #else
182 int p = (int) Environment.OSVersion.Platform;
183 is_unix = (p == 4) || (p == 128);
185 if (is_unix)
186 isatty = UnixUtils.isatty (0) && UnixUtils.isatty (1);
187 else
188 isatty = true;
189 #endif
191 // Work around, since Console is not accounting for
192 // cursor position when writing to Stderr. It also
193 // has the undesirable side effect of making
194 // errors plain, with no coloring.
195 // Report.Stderr = Console.Out;
196 SetupConsole ();
198 if (isatty)
199 Console.WriteLine ("Mono C# Shell, type \"help;\" for help\n\nEnter statements below.");
203 void ExecuteSources (IEnumerable<string> sources, bool ignore_errors)
205 foreach (string file in sources){
206 try {
207 try {
208 using (System.IO.StreamReader r = System.IO.File.OpenText (file)){
209 ReadEvalPrintLoopWith (p => r.ReadLine ());
211 } catch (FileNotFoundException){
212 Console.Error.WriteLine ("cs2001: Source file `{0}' not found", file);
213 return;
215 } catch {
216 if (!ignore_errors)
217 throw;
222 protected virtual void LoadStartupFiles ()
224 string dir = Path.Combine (
225 Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
226 "csharp");
227 if (!Directory.Exists (dir))
228 return;
230 List<string> sources = new List<string> ();
231 List<string> libraries = new List<string> ();
233 foreach (string file in System.IO.Directory.GetFiles (dir)){
234 string l = file.ToLower ();
236 if (l.EndsWith (".cs"))
237 sources.Add (file);
238 else if (l.EndsWith (".dll"))
239 libraries.Add (file);
242 foreach (string file in libraries)
243 Evaluator.LoadAssembly (file);
245 ExecuteSources (sources, true);
248 void ReadEvalPrintLoopWith (ReadLiner readline)
250 string expr = null;
251 while (!InteractiveBase.QuitRequested){
252 string input = readline (expr == null);
253 if (input == null)
254 return;
256 if (input == "")
257 continue;
259 expr = expr == null ? input : expr + "\n" + input;
261 expr = Evaluate (expr);
265 public int ReadEvalPrintLoop ()
267 if (startup_files != null && startup_files.Length == 0)
268 InitTerminal ();
270 InitializeUsing ();
272 LoadStartupFiles ();
275 // Interactive or startup files provided?
277 if (startup_files.Length != 0)
278 ExecuteSources (startup_files, false);
279 else
280 ReadEvalPrintLoopWith (GetLine);
282 return 0;
285 protected virtual string Evaluate (string input)
287 bool result_set;
288 object result;
290 try {
291 input = Evaluator.Evaluate (input, out result, out result_set);
293 if (result_set){
294 PrettyPrint (Console.Out, result);
295 Console.WriteLine ();
297 } catch (Exception e){
298 Console.WriteLine (e);
299 return null;
302 return input;
305 static void p (TextWriter output, string s)
307 output.Write (s);
310 static string EscapeString (string s)
312 return s.Replace ("\"", "\\\"");
315 static void EscapeChar (TextWriter output, char c)
317 if (c == '\''){
318 output.Write ("'\\''");
319 return;
321 if (c > 32){
322 output.Write ("'{0}'", c);
323 return;
325 switch (c){
326 case '\a':
327 output.Write ("'\\a'");
328 break;
330 case '\b':
331 output.Write ("'\\b'");
332 break;
334 case '\n':
335 output.Write ("'\\n'");
336 break;
338 case '\v':
339 output.Write ("'\\v'");
340 break;
342 case '\r':
343 output.Write ("'\\r'");
344 break;
346 case '\f':
347 output.Write ("'\\f'");
348 break;
350 case '\t':
351 output.Write ("'\\t");
352 break;
354 default:
355 output.Write ("'\\x{0:x}", (int) c);
356 break;
360 internal static void PrettyPrint (TextWriter output, object result)
362 if (result == null){
363 p (output, "null");
364 return;
367 if (result is Array){
368 Array a = (Array) result;
370 p (output, "{ ");
371 int top = a.GetUpperBound (0);
372 for (int i = a.GetLowerBound (0); i <= top; i++){
373 PrettyPrint (output, a.GetValue (i));
374 if (i != top)
375 p (output, ", ");
377 p (output, " }");
378 } else if (result is bool){
379 if ((bool) result)
380 p (output, "true");
381 else
382 p (output, "false");
383 } else if (result is string){
384 p (output, String.Format ("\"{0}\"", EscapeString ((string)result)));
385 } else if (result is IDictionary){
386 IDictionary dict = (IDictionary) result;
387 int top = dict.Count, count = 0;
389 p (output, "{");
390 foreach (DictionaryEntry entry in dict){
391 count++;
392 p (output, "{ ");
393 PrettyPrint (output, entry.Key);
394 p (output, ", ");
395 PrettyPrint (output, entry.Value);
396 if (count != top)
397 p (output, " }, ");
398 else
399 p (output, " }");
401 p (output, "}");
402 } else if (result is IEnumerable) {
403 int i = 0;
404 p (output, "{ ");
405 foreach (object item in (IEnumerable) result) {
406 if (i++ != 0)
407 p (output, ", ");
409 PrettyPrint (output, item);
411 p (output, " }");
412 } else if (result is char) {
413 EscapeChar (output, (char) result);
414 } else {
415 p (output, result.ToString ());
419 public CSharpShell ()
423 public virtual int Run (string [] startup_files)
425 this.startup_files = startup_files;
426 return ReadEvalPrintLoop ();
431 #if !ON_DOTNET
433 // A shell connected to a CSharpAgent running in a remote process.
434 // - maybe add 'class_name' and 'method_name' arguments to LoadAgent.
435 // - Support Gtk and Winforms main loops if detected, this should
436 // probably be done as a separate agent in a separate place.
438 class ClientCSharpShell : CSharpShell {
439 NetworkStream ns, interrupt_stream;
441 public ClientCSharpShell (int pid)
443 // Create a server socket we listen on whose address is passed to the agent
444 TcpListener listener = new TcpListener (new IPEndPoint (IPAddress.Loopback, 0));
445 listener.Start ();
446 TcpListener interrupt_listener = new TcpListener (new IPEndPoint (IPAddress.Loopback, 0));
447 interrupt_listener.Start ();
449 string agent_assembly = typeof (ClientCSharpShell).Assembly.Location;
450 string agent_arg = String.Format ("--agent:{0}:{1}" ,
451 ((IPEndPoint)listener.Server.LocalEndPoint).Port,
452 ((IPEndPoint)interrupt_listener.Server.LocalEndPoint).Port);
454 var vm = new Attach.VirtualMachine (pid);
455 vm.Attach (agent_assembly, agent_arg);
457 /* Wait for the client to connect */
458 TcpClient client = listener.AcceptTcpClient ();
459 ns = client.GetStream ();
460 TcpClient interrupt_client = interrupt_listener.AcceptTcpClient ();
461 interrupt_stream = interrupt_client.GetStream ();
463 Console.WriteLine ("Connected.");
467 // A remote version of Evaluate
469 protected override string Evaluate (string input)
471 ns.WriteString (input);
472 while (true) {
473 AgentStatus s = (AgentStatus) ns.ReadByte ();
475 switch (s){
476 case AgentStatus.PARTIAL_INPUT:
477 return input;
479 case AgentStatus.ERROR:
480 string err = ns.GetString ();
481 Console.Error.WriteLine (err);
482 break;
484 case AgentStatus.RESULT_NOT_SET:
485 return null;
487 case AgentStatus.RESULT_SET:
488 string res = ns.GetString ();
489 Console.WriteLine (res);
490 return null;
495 public override int Run (string [] startup_files)
497 // The difference is that we do not call Evaluator.Init, that is done on the target
498 return ReadEvalPrintLoop ();
501 protected override void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
503 // Do not about our program
504 a.Cancel = true;
506 interrupt_stream.WriteByte (0);
507 int c = interrupt_stream.ReadByte ();
508 if (c != -1)
509 Console.WriteLine ("Execution interrupted");
515 // Stream helper extension methods
517 public static class StreamHelper {
518 static DataConverter converter = DataConverter.LittleEndian;
520 public static int GetInt (this Stream stream)
522 byte [] b = new byte [4];
523 if (stream.Read (b, 0, 4) != 4)
524 throw new IOException ("End reached");
525 return converter.GetInt32 (b, 0);
528 public static string GetString (this Stream stream)
530 int len = stream.GetInt ();
531 byte [] b = new byte [len];
532 if (stream.Read (b, 0, len) != len)
533 throw new IOException ("End reached");
534 return Encoding.UTF8.GetString (b);
537 public static void WriteInt (this Stream stream, int n)
539 byte [] bytes = converter.GetBytes (n);
540 stream.Write (bytes, 0, bytes.Length);
543 public static void WriteString (this Stream stream, string s)
545 stream.WriteInt (s.Length);
546 byte [] bytes = Encoding.UTF8.GetBytes (s);
547 stream.Write (bytes, 0, bytes.Length);
551 public enum AgentStatus : byte {
552 // Received partial input, complete
553 PARTIAL_INPUT = 1,
555 // The result was set, expect the string with the result
556 RESULT_SET = 2,
558 // No result was set, complete
559 RESULT_NOT_SET = 3,
561 // Errors and warnings string follows
562 ERROR = 4,
566 // This is the agent loaded into the target process when using --attach.
568 class CSharpAgent
570 NetworkStream interrupt_stream;
572 public CSharpAgent (String arg)
574 new Thread (new ParameterizedThreadStart (Run)).Start (arg);
577 public void InterruptListener ()
579 while (true){
580 int b = interrupt_stream.ReadByte();
581 if (b == -1)
582 return;
583 Evaluator.Interrupt ();
584 interrupt_stream.WriteByte (0);
588 public void Run (object o)
590 string arg = (string)o;
591 string ports = arg.Substring (8);
592 int sp = ports.IndexOf (':');
593 int port = Int32.Parse (ports.Substring (0, sp));
594 int interrupt_port = Int32.Parse (ports.Substring (sp+1));
596 Console.WriteLine ("csharp-agent: started, connecting to localhost:" + port);
598 TcpClient client = new TcpClient ("127.0.0.1", port);
599 TcpClient interrupt_client = new TcpClient ("127.0.0.1", interrupt_port);
600 Console.WriteLine ("csharp-agent: connected.");
602 NetworkStream s = client.GetStream ();
603 interrupt_stream = interrupt_client.GetStream ();
604 new Thread (InterruptListener).Start ();
606 try {
607 Evaluator.Init (new string [0]);
608 } catch {
609 // TODO: send a result back.
610 Console.WriteLine ("csharp-agent: initialization failed");
611 return;
614 try {
615 // Add all assemblies loaded later
616 AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded;
618 // Add all currently loaded assemblies
619 foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies ())
620 Evaluator.ReferenceAssembly (a);
622 RunRepl (s);
623 } finally {
624 AppDomain.CurrentDomain.AssemblyLoad -= AssemblyLoaded;
625 client.Close ();
626 interrupt_client.Close ();
627 Console.WriteLine ("csharp-agent: disconnected.");
631 static void AssemblyLoaded (object sender, AssemblyLoadEventArgs e)
633 Evaluator.ReferenceAssembly (e.LoadedAssembly);
636 public void RunRepl (NetworkStream s)
638 string input = null;
640 while (!InteractiveBase.QuitRequested) {
641 try {
642 string error_string;
643 StringWriter error_output = new StringWriter ();
644 // Report.Stderr = error_output;
646 string line = s.GetString ();
648 bool result_set;
649 object result;
651 if (input == null)
652 input = line;
653 else
654 input = input + "\n" + line;
656 try {
657 input = Evaluator.Evaluate (input, out result, out result_set);
658 } catch (Exception e) {
659 s.WriteByte ((byte) AgentStatus.ERROR);
660 s.WriteString (e.ToString ());
661 s.WriteByte ((byte) AgentStatus.RESULT_NOT_SET);
662 continue;
665 if (input != null){
666 s.WriteByte ((byte) AgentStatus.PARTIAL_INPUT);
667 continue;
670 // Send warnings and errors back
671 error_string = error_output.ToString ();
672 if (error_string.Length != 0){
673 s.WriteByte ((byte) AgentStatus.ERROR);
674 s.WriteString (error_output.ToString ());
677 if (result_set){
678 s.WriteByte ((byte) AgentStatus.RESULT_SET);
679 StringWriter sr = new StringWriter ();
680 CSharpShell.PrettyPrint (sr, result);
681 s.WriteString (sr.ToString ());
682 } else {
683 s.WriteByte ((byte) AgentStatus.RESULT_NOT_SET);
685 } catch (IOException) {
686 break;
687 } catch (Exception e){
688 Console.WriteLine (e);
694 public class UnixUtils {
695 [System.Runtime.InteropServices.DllImport ("libc", EntryPoint="isatty")]
696 extern static int _isatty (int fd);
698 public static bool isatty (int fd)
700 try {
701 return _isatty (fd) == 1;
702 } catch {
703 return false;
707 #endif
710 namespace Mono.Management
712 interface IVirtualMachine {
713 void LoadAgent (string filename, string args);