2010-01-20 Zoltan Varga <vargaz@gmail.com>
[mono.git] / mcs / tools / csharp / repl.cs
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
11 //
12 //
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.
17 //
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;
29
30 using Mono.CSharp;
31 using Mono.Attach;
32
33 namespace Mono {
34
35         public class Driver {
36                 
37                 static int Main (string [] args)
38                 {
39                         if (args.Length > 0 && args [0] == "--attach") {
40                                 new ClientCSharpShell (Int32.Parse (args [1])).Run (null);
41                                 return 0;
42                         } else if (args.Length > 0 && args [0].StartsWith ("--agent:")) {
43                                 new CSharpAgent (args [0]);
44                                 return 0;
45                         } else {
46                                 string [] startup_files;
47                                 try {
48                                         startup_files = Evaluator.InitAndGetStartupFiles (args);
49                                         Evaluator.InteractiveBaseClass = typeof (InteractiveBaseShell);
50                                 } catch {
51                                         return 1;
52                                 }
53
54                                 return new CSharpShell ().Run (startup_files);
55                         }
56                 }
57         }
58
59         public class InteractiveBaseShell : InteractiveBase {
60                 static bool tab_at_start_completes;
61                 
62                 static InteractiveBaseShell ()
63                 {
64                         tab_at_start_completes = false;
65                 }
66
67                 internal static Mono.Terminal.LineEditor Editor;
68                 
69                 public static bool TabAtStartCompletes {
70                         get {
71                                 return tab_at_start_completes;
72                         }
73
74                         set {
75                                 tab_at_start_completes = value;
76                                 if (Editor != null)
77                                         Editor.TabAtStartCompletes = value;
78                         }
79                 }
80
81                 public static new string help {
82                         get {
83                                 return InteractiveBase.help +
84                                         "  TabAtStartCompletes - Whether tab will complete even on emtpy lines\n";
85                         }
86                 }
87         }
88         
89         public class CSharpShell {
90                 static bool isatty = true;
91                 string [] startup_files;
92                 
93                 Mono.Terminal.LineEditor editor;
94                 bool dumb;
95
96                 protected virtual void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
97                 {
98                         // Do not about our program
99                         a.Cancel = true;
100
101                         Mono.CSharp.Evaluator.Interrupt ();
102                 }
103                 
104                 void SetupConsole ()
105                 {
106                         string term = Environment.GetEnvironmentVariable ("TERM");
107                         dumb = term == "dumb" || term == null || isatty == false;
108                         
109                         editor = new Mono.Terminal.LineEditor ("csharp", 300);
110                         InteractiveBaseShell.Editor = editor;
111
112                         editor.AutoCompleteEvent += delegate (string s, int pos){
113                                 string prefix = null;
114
115                                 string complete = s.Substring (0, pos);
116                                 
117                                 string [] completions = Evaluator.GetCompletions (complete, out prefix);
118                                 
119                                 return new Mono.Terminal.LineEditor.Completion (prefix, completions);
120                         };
121                         
122 #if false
123                         //
124                         // This is a sample of how completions sould be implemented.
125                         //
126                         editor.AutoCompleteEvent += delegate (string s, int pos){
127
128                                 // Single match: "Substring": Sub-string
129                                 if (s.EndsWith ("Sub")){
130                                         return new string [] { "string" };
131                                 }
132
133                                 // Multiple matches: "ToString" and "ToLower"
134                                 if (s.EndsWith ("T")){
135                                         return new string [] { "ToString", "ToLower" };
136                                 }
137                                 return null;
138                         };
139 #endif
140                         
141                         Console.CancelKeyPress += ConsoleInterrupt;
142                 }
143
144                 string GetLine (bool primary)
145                 {
146                         string prompt = primary ? InteractiveBase.Prompt : InteractiveBase.ContinuationPrompt;
147
148                         if (dumb){
149                                 if (isatty)
150                                         Console.Write (prompt);
151
152                                 return Console.ReadLine ();
153                         } else {
154                                 return editor.Edit (prompt, "");
155                         }
156                 }
157
158                 delegate string ReadLiner (bool primary);
159
160                 void InitializeUsing ()
161                 {
162                         Evaluate ("using System; using System.Linq; using System.Collections.Generic; using System.Collections;");
163                 }
164
165                 void InitTerminal ()
166                 {
167                         isatty = UnixUtils.isatty (0) && UnixUtils.isatty (1);
168
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;
174                         SetupConsole ();
175
176                         if (isatty)
177                                 Console.WriteLine ("Mono C# Shell, type \"help;\" for help\n\nEnter statements below.");
178
179                 }
180
181                 void ExecuteSources (IEnumerable<string> sources, bool ignore_errors)
182                 {
183                         foreach (string file in sources){
184                                 try {
185                                         try {
186                                                 using (System.IO.StreamReader r = System.IO.File.OpenText (file)){
187                                                         ReadEvalPrintLoopWith (p => r.ReadLine ());
188                                                 }
189                                         } catch (FileNotFoundException){
190                                                 Console.Error.WriteLine ("cs2001: Source file `{0}' not found", file);
191                                                 return;
192                                         }
193                                 } catch {
194                                         if (!ignore_errors)
195                                                 throw;
196                                 }
197                         }
198                 }
199                 
200                 protected virtual void LoadStartupFiles ()
201                 {
202                         string dir = Path.Combine (
203                                 Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
204                                 "csharp");
205                         if (!Directory.Exists (dir))
206                                 return;
207
208                         List<string> sources = new List<string> ();
209                         List<string> libraries = new List<string> ();
210                         
211                         foreach (string file in System.IO.Directory.GetFiles (dir)){
212                                 string l = file.ToLower ();
213                                 
214                                 if (l.EndsWith (".cs"))
215                                         sources.Add (file);
216                                 else if (l.EndsWith (".dll"))
217                                         libraries.Add (file);
218                         }
219
220                         foreach (string file in libraries)
221                                 Evaluator.LoadAssembly (file);
222
223                         ExecuteSources (sources, true);
224                 }
225
226                 void ReadEvalPrintLoopWith (ReadLiner readline)
227                 {
228                         string expr = null;
229                         while (!InteractiveBase.QuitRequested){
230                                 string input = readline (expr == null);
231                                 if (input == null)
232                                         return;
233
234                                 if (input == "")
235                                         continue;
236
237                                 expr = expr == null ? input : expr + "\n" + input;
238                                 
239                                 expr = Evaluate (expr);
240                         } 
241                 }
242
243                 public int ReadEvalPrintLoop ()
244                 {
245                         if (startup_files != null && startup_files.Length == 0)
246                                 InitTerminal ();
247
248                         InitializeUsing ();
249
250                         LoadStartupFiles ();
251
252                         //
253                         // Interactive or startup files provided?
254                         //
255                         if (startup_files.Length != 0)
256                                 ExecuteSources (startup_files, false);
257                         else
258                                 ReadEvalPrintLoopWith (GetLine);
259
260                         return 0;
261                 }
262
263                 protected virtual string Evaluate (string input)
264                 {
265                         bool result_set;
266                         object result;
267
268                         try {
269                                 input = Evaluator.Evaluate (input, out result, out result_set);
270
271                                 if (result_set){
272                                         PrettyPrint (Console.Out, result);
273                                         Console.WriteLine ();
274                                 }
275                         } catch (Exception e){
276                                 Console.WriteLine (e);
277                                 return null;
278                         }
279                         
280                         return input;
281                 }
282
283                 static void p (TextWriter output, string s)
284                 {
285                         output.Write (s);
286                 }
287
288                 static string EscapeString (string s)
289                 {
290                         return s.Replace ("\"", "\\\"");
291                 }
292                 
293                 static void EscapeChar (TextWriter output, char c)
294                 {
295                         if (c == '\''){
296                                 output.Write ("'\\''");
297                                 return;
298                         }
299                         if (c > 32){
300                                 output.Write ("'{0}'", c);
301                                 return;
302                         }
303                         switch (c){
304                         case '\a':
305                                 output.Write ("'\\a'");
306                                 break;
307
308                         case '\b':
309                                 output.Write ("'\\b'");
310                                 break;
311                                 
312                         case '\n':
313                                 output.Write ("'\\n'");
314                                 break;
315                                 
316                         case '\v':
317                                 output.Write ("'\\v'");
318                                 break;
319                                 
320                         case '\r':
321                                 output.Write ("'\\r'");
322                                 break;
323                                 
324                         case '\f':
325                                 output.Write ("'\\f'");
326                                 break;
327                                 
328                         case '\t':
329                                 output.Write ("'\\t");
330                                 break;
331
332                         default:
333                                 output.Write ("'\\x{0:x}", (int) c);
334                                 break;
335                         }
336                 }
337                 
338                 internal static void PrettyPrint (TextWriter output, object result)
339                 {
340                         if (result == null){
341                                 p (output, "null");
342                                 return;
343                         }
344                         
345                         if (result is Array){
346                                 Array a = (Array) result;
347                                 
348                                 p (output, "{ ");
349                                 int top = a.GetUpperBound (0);
350                                 for (int i = a.GetLowerBound (0); i <= top; i++){
351                                         PrettyPrint (output, a.GetValue (i));
352                                         if (i != top)
353                                                 p (output, ", ");
354                                 }
355                                 p (output, " }");
356                         } else if (result is bool){
357                                 if ((bool) result)
358                                         p (output, "true");
359                                 else
360                                         p (output, "false");
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;
366                                 
367                                 p (output, "{");
368                                 foreach (DictionaryEntry entry in dict){
369                                         count++;
370                                         p (output, "{ ");
371                                         PrettyPrint (output, entry.Key);
372                                         p (output, ", ");
373                                         PrettyPrint (output, entry.Value);
374                                         if (count != top)
375                                                 p (output, " }, ");
376                                         else
377                                                 p (output, " }");
378                                 }
379                                 p (output, "}");
380                         } else if (result is IEnumerable) {
381                                 int i = 0;
382                                 p (output, "{ ");
383                                 foreach (object item in (IEnumerable) result) {
384                                         if (i++ != 0)
385                                                 p (output, ", ");
386
387                                         PrettyPrint (output, item);
388                                 }
389                                 p (output, " }");
390                         } else if (result is char) {
391                                 EscapeChar (output, (char) result);
392                         } else {
393                                 p (output, result.ToString ());
394                         }
395                 }
396
397                 public CSharpShell ()
398                 {
399                 }
400
401                 public virtual int Run (string [] startup_files)
402                 {
403                         this.startup_files = startup_files;
404                         return ReadEvalPrintLoop ();
405                 }
406                 
407         }
408
409         //
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.
414         //
415         class ClientCSharpShell : CSharpShell {
416                 NetworkStream ns, interrupt_stream;
417                 
418                 public ClientCSharpShell (int pid)
419                 {
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));
422                         listener.Start ();
423                         TcpListener interrupt_listener = new TcpListener (new IPEndPoint (IPAddress.Loopback, 0));
424                         interrupt_listener.Start ();
425         
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);
430         
431                         VirtualMachine vm = new VirtualMachine (pid);
432                         vm.Attach (agent_assembly, agent_arg);
433         
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 ();
439         
440                         Console.WriteLine ("Connected.");
441                 }
442         
443                 //
444                 // A remote version of Evaluate
445                 //
446                 protected override string Evaluate (string input)
447                 {
448                         ns.WriteString (input);
449                         while (true) {
450                                 AgentStatus s = (AgentStatus) ns.ReadByte ();
451         
452                                 switch (s){
453                                 case AgentStatus.PARTIAL_INPUT:
454                                         return input;
455         
456                                 case AgentStatus.ERROR:
457                                         string err = ns.GetString ();
458                                         Console.Error.WriteLine (err);
459                                         break;
460         
461                                 case AgentStatus.RESULT_NOT_SET:
462                                         return null;
463         
464                                 case AgentStatus.RESULT_SET:
465                                         string res = ns.GetString ();
466                                         Console.WriteLine (res);
467                                         return null;
468                                 }
469                         }
470                 }
471                 
472                 public override int Run (string [] startup_files)
473                 {
474                         // The difference is that we do not call Evaluator.Init, that is done on the target
475                         return ReadEvalPrintLoop ();
476                 }
477         
478                 protected override void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
479                 {
480                         // Do not about our program
481                         a.Cancel = true;
482         
483                         interrupt_stream.WriteByte (0);
484                         int c = interrupt_stream.ReadByte ();
485                         if (c != -1)
486                                 Console.WriteLine ("Execution interrupted");
487                 }
488                         
489         }
490
491         //
492         // Stream helper extension methods
493         //
494         public static class StreamHelper {
495                 static DataConverter converter = DataConverter.LittleEndian;
496                 
497                 public static int GetInt (this Stream stream)
498                 {
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);
503                 }
504                 
505                 public static string GetString (this Stream stream)
506                 {
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);
512                 }
513         
514                 public static void WriteInt (this Stream stream, int n)
515                 {
516                         byte [] bytes = converter.GetBytes (n);
517                         stream.Write (bytes, 0, bytes.Length);
518                 }
519         
520                 public static void WriteString (this Stream stream, string s)
521                 {
522                         stream.WriteInt (s.Length);
523                         byte [] bytes = Encoding.UTF8.GetBytes (s);
524                         stream.Write (bytes, 0, bytes.Length);
525                 }
526         }
527         
528         public enum AgentStatus : byte {
529                 // Received partial input, complete
530                 PARTIAL_INPUT  = 1,
531         
532                 // The result was set, expect the string with the result
533                 RESULT_SET     = 2,
534         
535                 // No result was set, complete
536                 RESULT_NOT_SET = 3,
537         
538                 // Errors and warnings string follows
539                 ERROR          = 4, 
540         }
541         
542         //
543         // This is the agent loaded into the target process when using --attach.
544         //
545         class CSharpAgent
546         {
547                 NetworkStream interrupt_stream;
548                 
549                 public CSharpAgent (String arg)
550                 {
551                         new Thread (new ParameterizedThreadStart (Run)).Start (arg);
552                 }
553
554                 public void InterruptListener ()
555                 {
556                         while (true){
557                                 int b = interrupt_stream.ReadByte();
558                                 if (b == -1)
559                                         return;
560                                 Evaluator.Interrupt ();
561                                 interrupt_stream.WriteByte (0);
562                         }
563                 }
564                 
565                 public void Run (object o)
566                 {
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));
572         
573                         Console.WriteLine ("csharp-agent: started, connecting to localhost:" + port);
574         
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.");
578         
579                         NetworkStream s = client.GetStream ();
580                         interrupt_stream = interrupt_client.GetStream ();
581                         new Thread (InterruptListener).Start ();
582
583                         try {
584                                 Evaluator.Init (new string [0]);
585                         } catch {
586                                 // TODO: send a result back.
587                                 Console.WriteLine ("csharp-agent: initialization failed");
588                                 return;
589                         }
590         
591                         try {
592                                 // Add all assemblies loaded later
593                                 AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded;
594         
595                                 // Add all currently loaded assemblies
596                                 foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies ())
597                                         Evaluator.ReferenceAssembly (a);
598         
599                                 RunRepl (s);
600                         } finally {
601                                 AppDomain.CurrentDomain.AssemblyLoad -= AssemblyLoaded;
602                                 client.Close ();
603                                 interrupt_client.Close ();
604                                 Console.WriteLine ("csharp-agent: disconnected.");                      
605                         }
606                 }
607         
608                 static void AssemblyLoaded (object sender, AssemblyLoadEventArgs e)
609                 {
610                         Evaluator.ReferenceAssembly (e.LoadedAssembly);
611                 }
612         
613                 public void RunRepl (NetworkStream s)
614                 {
615                         string input = null;
616
617                         while (!InteractiveBase.QuitRequested) {
618                                 try {
619                                         string error_string;
620                                         StringWriter error_output = new StringWriter ();
621 //                                      Report.Stderr = error_output;
622                                         
623                                         string line = s.GetString ();
624         
625                                         bool result_set;
626                                         object result;
627         
628                                         if (input == null)
629                                                 input = line;
630                                         else
631                                                 input = input + "\n" + line;
632         
633                                         try {
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);
639                                                 continue;
640                                         }
641                                         
642                                         if (input != null){
643                                                 s.WriteByte ((byte) AgentStatus.PARTIAL_INPUT);
644                                                 continue;
645                                         }
646         
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 ());
652                                         }
653         
654                                         if (result_set){
655                                                 s.WriteByte ((byte) AgentStatus.RESULT_SET);
656                                                 StringWriter sr = new StringWriter ();
657                                                 CSharpShell.PrettyPrint (sr, result);
658                                                 s.WriteString (sr.ToString ());
659                                         } else {
660                                                 s.WriteByte ((byte) AgentStatus.RESULT_NOT_SET);
661                                         }
662                                 } catch (IOException) {
663                                         break;
664                                 } catch (Exception e){
665                                         Console.WriteLine (e);
666                                 }
667                         }
668                 }
669         }
670
671         public class UnixUtils {
672                 [System.Runtime.InteropServices.DllImport ("libc", EntryPoint="isatty")]
673                 extern static int _isatty (int fd);
674                         
675                 public static bool isatty (int fd)
676                 {
677                         try {
678                                 return _isatty (fd) == 1;
679                         } catch {
680                                 return false;
681                         }
682                 }
683         }
684 }
685         
686 namespace Mono.Management
687 {
688         interface IVirtualMachine {
689                 void LoadAgent (string filename, string args);
690         }
691 }
692
693