2008-09-25 Miguel de Icaza <miguel@novell.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 Mono.CSharp;
29
30 using Mono.Attach;
31
32 namespace Mono {
33
34         public static class CSharpShell {
35                 static bool isatty = true;
36                 
37                 static Mono.Terminal.LineEditor editor;
38                 static bool dumb;
39
40                 static void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
41                 {
42                         // Do not about our program
43                         a.Cancel = true;
44
45                         Mono.CSharp.Evaluator.Interrupt ();
46                 }
47                 
48                 static void SetupConsole ()
49                 {
50                         string term = Environment.GetEnvironmentVariable ("TERM");
51                         dumb = term == "dumb" || term == null || isatty == false;
52                         
53                         editor = new Mono.Terminal.LineEditor ("csharp", 300);
54                         Console.CancelKeyPress += ConsoleInterrupt;
55                 }
56
57                 static string GetLine (bool primary)
58                 {
59                         string prompt = primary ? InteractiveBase.Prompt : InteractiveBase.ContinuationPrompt;
60
61                         if (dumb){
62                                 if (isatty)
63                                         Console.Write (prompt);
64
65                                 return Console.ReadLine ();
66                         } else {
67                                 return editor.Edit (prompt, "");
68                         }
69                 }
70
71                 delegate string ReadLiner (bool primary);
72
73                 static void InitializeUsing ()
74                 {
75                         Evaluate ("using System; using System.Linq; using System.Collections.Generic; using System.Collections;");
76                 }
77
78                 static void InitTerminal ()
79                 {
80                         isatty = UnixUtils.isatty (0) && UnixUtils.isatty (1);
81
82                         // Work around, since Console is not accounting for
83                         // cursor position when writing to Stderr.  It also
84                         // has the undesirable side effect of making
85                         // errors plain, with no coloring.
86                         Report.Stderr = Console.Out;
87                         SetupConsole ();
88
89                         if (isatty)
90                                 Console.WriteLine ("Mono C# Shell, type \"help;\" for help\n\nEnter statements below.");
91
92                 }
93
94                 static void LoadStartupFiles ()
95                 {
96                         string dir = Path.Combine (
97                                 Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
98                                 "csharp");
99                         if (!Directory.Exists (dir))
100                                 return;
101
102                         foreach (string file in Directory.GetFiles (dir)){
103                                 string l = file.ToLower ();
104                                 
105                                 if (l.EndsWith (".cs")){
106                                         try {
107                                                 using (StreamReader r = File.OpenText (file)){
108                                                         ReadEvalPrintLoopWith (p => r.ReadLine ());
109                                                 }
110                                         } catch {
111                                         }
112                                 } else if (l.EndsWith (".dll")){
113                                         Evaluator.LoadAssembly (file);
114                                 }
115                         }
116                 }
117
118                 static void ReadEvalPrintLoopWith (ReadLiner readline)
119                 {
120                         string expr = null;
121                         while (true){
122                                 string input = readline (expr == null);
123                                 if (input == null)
124                                         return;
125
126                                 if (input == "")
127                                         continue;
128
129                                 expr = expr == null ? input : expr + "\n" + input;
130                                 
131                                 expr = Evaluate (expr);
132                         } 
133                 }
134
135                 static public int ReadEvalPrintLoop ()
136                 {
137                         InitTerminal ();
138
139                         InitializeUsing ();
140
141                         LoadStartupFiles ();
142                         ReadEvalPrintLoopWith (GetLine);
143
144                         return 0;
145                 }
146
147                 static string Evaluate (string input)
148                 {
149                         bool result_set;
150                         object result;
151
152                         try {
153                                 input = Evaluator.Evaluate (input, out result, out result_set);
154
155                                 if (result_set){
156                                         PrettyPrint (result);
157                                         Console.WriteLine ();
158                                 }
159                         } catch (Exception e){
160                                 Console.WriteLine (e);
161                                 return null;
162                         }
163                         
164                         return input;
165                 }
166
167                 static void p (string s)
168                 {
169                         Console.Write (s);
170                 }
171
172                 static string EscapeString (string s)
173                 {
174                         return s.Replace ("\"", "\\\"");
175                 }
176                 
177                 static void PrettyPrint (object result)
178                 {
179                         if (result == null){
180                                 p ("null");
181                                 return;
182                         }
183                         
184                         if (result is Array){
185                                 Array a = (Array) result;
186                                 
187                                 p ("{ ");
188                                 int top = a.GetUpperBound (0);
189                                 for (int i = a.GetLowerBound (0); i <= top; i++){
190                                         PrettyPrint (a.GetValue (i));
191                                         if (i != top)
192                                                 p (", ");
193                                 }
194                                 p (" }");
195                         } else if (result is bool){
196                                 if ((bool) result)
197                                         p ("true");
198                                 else
199                                         p ("false");
200                         } else if (result is string){
201                                 p (String.Format ("\"{0}\"", EscapeString ((string)result)));
202                         } else if (result is IDictionary){
203                                 IDictionary dict = (IDictionary) result;
204                                 int top = dict.Count, count = 0;
205                                 
206                                 p ("{");
207                                 foreach (DictionaryEntry entry in dict){
208                                         count++;
209                                         p ("{ ");
210                                         PrettyPrint (entry.Key);
211                                         p (", ");
212                                         PrettyPrint (entry.Value);
213                                         if (count != top)
214                                                 p (" }, ");
215                                         else
216                                                 p (" }");
217                                 }
218                                 p ("}");
219                         } else if (result is IEnumerable) {
220                                 int i = 0;
221                                 p ("{ ");
222                                 foreach (object item in (IEnumerable) result) {
223                                         if (i++ != 0)
224                                                 p (", ");
225
226                                         PrettyPrint (item);
227                                 }
228                                 p (" }");
229                         } else {
230                                 p (result.ToString ());
231                         }
232                 }
233
234                 static int Main (string [] args)
235                 {
236                         if (args.Length > 0 && args [0] == "--attach") {
237                                 new AttachedCSharpShell (Int32.Parse (args [1]));
238                                 return 0;
239                         } else if (args.Length > 0 && args [0].StartsWith ("--agent")) {
240                                 new CSharpAgent (args [0]);
241                                 return 0;
242                         }
243
244                         try {
245                                 Evaluator.Init (args);
246                         } catch {
247                                 return 1;
248                         }
249                         
250                         return ReadEvalPrintLoop ();
251                 }
252         }
253 }
254
255 /*
256  * A shell connected to a CSharpAgent running in a remote process.
257  * FIXME:
258  * - using NOT.EXISTS works, but leads to an error later when mcs tries to search
259  *   that namespace.
260  * - it would be nice to provide some kind of autocompletion even in remote mode.
261  * - maybe add 'class_name' and 'method_name' arguments to LoadAgent.
262  */
263 class AttachedCSharpShell {
264
265         public AttachedCSharpShell (int pid) {
266                 /* Create a server socket we listen on whose address is passed to the agent */
267                 TcpListener listener = new TcpListener (new IPEndPoint (IPAddress.Loopback, 0));
268                 listener.Start ();
269
270                 string agent_assembly = typeof (AttachedCSharpShell).Assembly.Location;
271                 string agent_arg = "--agent:" + ((IPEndPoint)listener.Server.LocalEndPoint).Port;
272
273                 VirtualMachine vm = new VirtualMachine (pid);
274                 vm.Attach (agent_assembly, agent_arg);
275
276                 /* Wait for the client to connect */
277                 TcpClient client = listener.AcceptTcpClient ();
278                 NetworkStream s = client.GetStream ();
279                 StreamReader sr = new StreamReader (s);
280                 StreamWriter sw = new StreamWriter (s);
281
282                 Console.WriteLine ("Connected.");
283
284                 InitTerminal ();
285
286                 sw.WriteLine ("using System; using System.Linq; using System.Collections.Generic; using System.Collections;");
287                 sw.Flush ();
288                 /* Read result */
289                 while (true) {
290                         string line = sr.ReadLine ();
291                         if (line == "<END>")
292                                 break;
293                 }
294
295                 //LoadStartupFiles ();
296
297                 string expr = "";
298                 bool eof = false;
299                 while (!eof) {
300                         string input = GetLine (expr == "");
301                         if (input == null)
302                                 break;
303
304                         if (input == "")
305                                 continue;
306
307                         sw.WriteLine (input);
308                         sw.Flush ();
309
310                         /* Read the (possible) error messages */
311                         while (true) {
312                                 string line = sr.ReadLine ();
313                                 if (line == null) {
314                                         eof = true;
315                                         break;
316                                 }
317                                 if (line == "<RESULT>")
318                                         break;
319                                 else
320                                         // FIXME: Colorize
321                                         Console.WriteLine (line);
322                         }
323                         /* Read the result */
324                         while (true) {
325                                 string line = sr.ReadLine ();
326                                 if (line == null) {
327                                         eof = true;
328                                         break;
329                                 }
330                                 if (line == "<INPUT>")
331                                         break;
332                                 else
333                                         Console.WriteLine (line);
334                         }
335                         /* Read the (possible) incomplete input */
336                         expr = "";
337                         while (true) {
338                                 string line = sr.ReadLine ();
339                                 if (line == null) {
340                                         eof = true;
341                                         break;
342                                 }
343                                 if (line == "<END>")
344                                         break;
345                                 else
346                                         expr += line;
347                         }
348                 }
349         }
350
351         static bool isatty = true;
352                 
353         static Mono.Terminal.LineEditor editor;
354         static bool dumb;
355
356         static void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
357         {
358                 // Do not about our program
359                 a.Cancel = true;
360
361                 Mono.CSharp.Evaluator.Interrupt ();
362     }
363                 
364         static void SetupConsole ()
365         {
366                 string term = Environment.GetEnvironmentVariable ("TERM");
367                 dumb = term == "dumb" || term == null || isatty == false;
368                         
369                 editor = new Mono.Terminal.LineEditor ("csharp", 300);
370                 Console.CancelKeyPress += ConsoleInterrupt;
371     }
372
373         static string GetLine (bool primary)
374         {
375                 string prompt = primary ? InteractiveBase.Prompt : InteractiveBase.ContinuationPrompt;
376
377                 if (dumb){
378                         if (isatty)
379                                 Console.Write (prompt);
380
381                         return Console.ReadLine ();
382                 } else {
383                         return editor.Edit (prompt, "");
384                 }
385     }
386
387         static void InitTerminal ()
388         {
389                 isatty = UnixUtils.isatty (0) && UnixUtils.isatty (1);
390
391                 SetupConsole ();
392
393                 if (isatty)
394                         Console.WriteLine ("Mono C# Shell, type \"help;\" for help\n\nEnter statements below.");
395     }
396 }
397
398 namespace Mono.Management
399 {
400         interface IVirtualMachine {
401                 void LoadAgent (string filename, string args);
402         }
403 }
404
405 /*
406  * This is the agent loaded into the target process when using --attach.
407  */
408 class CSharpAgent
409 {
410         public CSharpAgent (String arg) {
411                 new Thread (new ParameterizedThreadStart (Run)).Start (arg);
412         }
413
414         public void Run (object o) {
415                 string arg = (string)o;
416                 int port = Int32.Parse (arg.Substring (arg.IndexOf (":") + 1));
417
418                 Console.WriteLine ("csharp-agent: started, connecting to localhost:" + port);
419
420                 TcpClient client = new TcpClient ("127.0.0.1", port);
421                 Console.WriteLine ("csharp-agent: connected.");
422
423                 NetworkStream s = client.GetStream ();
424
425                 try {
426                         Evaluator.Init (new string [0]);
427                 } catch {
428                         return;
429                 }
430
431                 try {
432                         // Add all assemblies loaded later
433                         AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded;
434
435                         // Add all currently loaded assemblies
436                         foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies ())
437                                 Evaluator.ReferenceAssembly (a);
438
439                         RunRepl (s);
440                 } finally {
441                         AppDomain.CurrentDomain.AssemblyLoad -= AssemblyLoaded;
442                         client.Close ();
443                         Console.WriteLine ("csharp-agent: disconnected.");                      
444                 }
445         }
446
447         static void AssemblyLoaded (object sender, AssemblyLoadEventArgs e) {
448                 Evaluator.ReferenceAssembly (e.LoadedAssembly);
449         }
450
451         public void RunRepl (NetworkStream s) {
452                 StreamReader r = new StreamReader (s);
453                 StreamWriter w = new StreamWriter (s);
454                 string input = null;
455
456                 Report.Stderr = w;
457
458                 while (true) {
459                         try {
460                                 string line = r.ReadLine ();
461
462                                 bool result_set;
463                                 object result;
464
465                                 if (input == null)
466                                         input = line;
467                                 else
468                                         input = input + "\n" + line;
469
470                                 // This will print any error messages to w
471                                 input = Evaluator.Evaluate (input, out result, out result_set);
472
473                                 // FIXME: Emit XML
474
475                                 // This separates the result from the possible error messages
476                                 w.WriteLine ("<RESULT>");
477                                 if (result_set) {
478                                         if (result == null)
479                                                 w.Write ("null");
480                                         else
481                                                 w.Write (result.ToString ());
482                                         w.WriteLine ();
483                                 }
484                                 // FIXME: This might occur in the output as well.
485                                 w.WriteLine ("<INPUT>");
486                                 /* The rest of the input */
487                                 w.WriteLine (input);
488                                 w.WriteLine ("<END>");
489                                 w.Flush ();
490                         } catch (IOException) {
491                                 break;
492                         } catch (Exception e){
493                                 Console.WriteLine (e);
494                         }
495                 }
496         }
497 }