Prepping for mdoc et. al. move from monodoc/tools to mcs/tools/mdoc...
[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
29 using Mono.CSharp;
30 using Mono.Attach;
31
32 namespace Mono {
33
34         public class Driver {
35                 
36                 static int Main (string [] args)
37                 {
38                         if (args.Length > 0 && args [0] == "--attach") {
39                                 new ClientCSharpShell (Int32.Parse (args [1])).Run ();
40                                 return 0;
41                         } else if (args.Length > 0 && args [0].StartsWith ("--agent:")) {
42                                 new CSharpAgent (args [0]);
43                                 return 0;
44                         } else {
45                                 try {
46                                         Evaluator.Init (args);
47                                 } catch {
48                                         return 1;
49                                 }
50                         
51                                 return new CSharpShell ().Run ();
52                         }
53                 }
54         }
55         
56         public class CSharpShell {
57                 static bool isatty = true;
58                 
59                 Mono.Terminal.LineEditor editor;
60                 bool dumb;
61
62                 protected virtual void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
63                 {
64                         // Do not about our program
65                         a.Cancel = true;
66
67                         Mono.CSharp.Evaluator.Interrupt ();
68                 }
69                 
70                 void SetupConsole ()
71                 {
72                         string term = Environment.GetEnvironmentVariable ("TERM");
73                         dumb = term == "dumb" || term == null || isatty == false;
74                         
75                         editor = new Mono.Terminal.LineEditor ("csharp", 300);
76 #if false
77                         //
78                         // This is a sample of how completions sould be implemented.
79                         //
80                         editor.AutoCompleteEvent += delegate (string s, int pos){
81
82                                 // Single match: "Substring": Sub-string
83                                 if (s.EndsWith ("Sub")){
84                                         return new string [] { "string" };
85                                 }
86
87                                 // Multiple matches: "ToString" and "ToLower"
88                                 if (s.EndsWith ("T")){
89                                         return new string [] { "ToString", "ToLower" };
90                                 }
91                                 return null;
92                         };
93 #endif
94                         
95                         Console.CancelKeyPress += ConsoleInterrupt;
96                 }
97
98                 string GetLine (bool primary)
99                 {
100                         string prompt = primary ? InteractiveBase.Prompt : InteractiveBase.ContinuationPrompt;
101
102                         if (dumb){
103                                 if (isatty)
104                                         Console.Write (prompt);
105
106                                 return Console.ReadLine ();
107                         } else {
108                                 return editor.Edit (prompt, "");
109                         }
110                 }
111
112                 delegate string ReadLiner (bool primary);
113
114                 void InitializeUsing ()
115                 {
116                         Evaluate ("using System; using System.Linq; using System.Collections.Generic; using System.Collections;");
117                 }
118
119                 void InitTerminal ()
120                 {
121                         isatty = UnixUtils.isatty (0) && UnixUtils.isatty (1);
122
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;
128                         SetupConsole ();
129
130                         if (isatty)
131                                 Console.WriteLine ("Mono C# Shell, type \"help;\" for help\n\nEnter statements below.");
132
133                 }
134
135                 protected virtual void LoadStartupFiles ()
136                 {
137                         string dir = Path.Combine (
138                                 Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
139                                 "csharp");
140                         if (!Directory.Exists (dir))
141                                 return;
142
143                         foreach (string file in Directory.GetFiles (dir)){
144                                 string l = file.ToLower ();
145                                 
146                                 if (l.EndsWith (".cs")){
147                                         try {
148                                                 using (StreamReader r = File.OpenText (file)){
149                                                         ReadEvalPrintLoopWith (p => r.ReadLine ());
150                                                 }
151                                         } catch {
152                                         }
153                                 } else if (l.EndsWith (".dll")){
154                                         Evaluator.LoadAssembly (file);
155                                 }
156                         }
157                 }
158
159                 void ReadEvalPrintLoopWith (ReadLiner readline)
160                 {
161                         string expr = null;
162                         while (true){
163                                 string input = readline (expr == null);
164                                 if (input == null)
165                                         return;
166
167                                 if (input == "")
168                                         continue;
169
170                                 expr = expr == null ? input : expr + "\n" + input;
171                                 
172                                 expr = Evaluate (expr);
173                         } 
174                 }
175
176                 public int ReadEvalPrintLoop ()
177                 {
178                         InitTerminal ();
179
180                         InitializeUsing ();
181
182                         LoadStartupFiles ();
183                         ReadEvalPrintLoopWith (GetLine);
184
185                         return 0;
186                 }
187
188                 protected virtual string Evaluate (string input)
189                 {
190                         bool result_set;
191                         object result;
192
193                         try {
194                                 input = Evaluator.Evaluate (input, out result, out result_set);
195
196                                 if (result_set){
197                                         PrettyPrint (Console.Out, result);
198                                         Console.WriteLine ();
199                                 }
200                         } catch (Exception e){
201                                 Console.WriteLine (e);
202                                 return null;
203                         }
204                         
205                         return input;
206                 }
207
208                 static void p (TextWriter output, string s)
209                 {
210                         output.Write (s);
211                 }
212
213                 static string EscapeString (string s)
214                 {
215                         return s.Replace ("\"", "\\\"");
216                 }
217                 
218                 static void EscapeChar (TextWriter output, char c)
219                 {
220                         if (c == '\''){
221                                 output.Write ("'\\''");
222                                 return;
223                         }
224                         if (c > 32){
225                                 output.Write ("'{0}'", c);
226                                 return;
227                         }
228                         switch (c){
229                         case '\a':
230                                 output.Write ("'\\a'");
231                                 break;
232
233                         case '\b':
234                                 output.Write ("'\\b'");
235                                 break;
236                                 
237                         case '\n':
238                                 output.Write ("'\\n'");
239                                 break;
240                                 
241                         case '\v':
242                                 output.Write ("'\\v'");
243                                 break;
244                                 
245                         case '\r':
246                                 output.Write ("'\\r'");
247                                 break;
248                                 
249                         case '\f':
250                                 output.Write ("'\\f'");
251                                 break;
252                                 
253                         case '\t':
254                                 output.Write ("'\\t");
255                                 break;
256
257                         default:
258                                 output.Write ("'\\x{0:x}", (int) c);
259                                 break;
260                         }
261                 }
262                 
263                 internal static void PrettyPrint (TextWriter output, object result)
264                 {
265                         if (result == null){
266                                 p (output, "null");
267                                 return;
268                         }
269                         
270                         if (result is Array){
271                                 Array a = (Array) result;
272                                 
273                                 p (output, "{ ");
274                                 int top = a.GetUpperBound (0);
275                                 for (int i = a.GetLowerBound (0); i <= top; i++){
276                                         PrettyPrint (output, a.GetValue (i));
277                                         if (i != top)
278                                                 p (output, ", ");
279                                 }
280                                 p (output, " }");
281                         } else if (result is bool){
282                                 if ((bool) result)
283                                         p (output, "true");
284                                 else
285                                         p (output, "false");
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;
291                                 
292                                 p (output, "{");
293                                 foreach (DictionaryEntry entry in dict){
294                                         count++;
295                                         p (output, "{ ");
296                                         PrettyPrint (output, entry.Key);
297                                         p (output, ", ");
298                                         PrettyPrint (output, entry.Value);
299                                         if (count != top)
300                                                 p (output, " }, ");
301                                         else
302                                                 p (output, " }");
303                                 }
304                                 p (output, "}");
305                         } else if (result is IEnumerable) {
306                                 int i = 0;
307                                 p (output, "{ ");
308                                 foreach (object item in (IEnumerable) result) {
309                                         if (i++ != 0)
310                                                 p (output, ", ");
311
312                                         PrettyPrint (output, item);
313                                 }
314                                 p (output, " }");
315                         } else if (result is char) {
316                                 EscapeChar (output, (char) result);
317                         } else {
318                                 p (output, result.ToString ());
319                         }
320                 }
321
322                 public CSharpShell ()
323                 {
324                 }
325
326                 public virtual int Run ()
327                 {
328                         return ReadEvalPrintLoop ();
329                 }
330                 
331         }
332
333         //
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.
338         //
339         class ClientCSharpShell : CSharpShell {
340                 NetworkStream ns, interrupt_stream;
341                 
342                 public ClientCSharpShell (int pid)
343                 {
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));
346                         listener.Start ();
347                         TcpListener interrupt_listener = new TcpListener (new IPEndPoint (IPAddress.Loopback, 0));
348                         interrupt_listener.Start ();
349         
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);
354         
355                         VirtualMachine vm = new VirtualMachine (pid);
356                         vm.Attach (agent_assembly, agent_arg);
357         
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 ();
363         
364                         Console.WriteLine ("Connected.");
365                 }
366         
367                 //
368                 // A remote version of Evaluate
369                 //
370                 protected override string Evaluate (string input)
371                 {
372                         ns.WriteString (input);
373                         while (true) {
374                                 AgentStatus s = (AgentStatus) ns.ReadByte ();
375         
376                                 switch (s){
377                                 case AgentStatus.PARTIAL_INPUT:
378                                         return input;
379         
380                                 case AgentStatus.ERROR:
381                                         string err = ns.GetString ();
382                                         Console.Error.WriteLine (err);
383                                         break;
384         
385                                 case AgentStatus.RESULT_NOT_SET:
386                                         return null;
387         
388                                 case AgentStatus.RESULT_SET:
389                                         string res = ns.GetString ();
390                                         Console.WriteLine (res);
391                                         return null;
392                                 }
393                         }
394                 }
395                 
396                 public override int Run ()
397                 {
398                         // The difference is that we do not call Evaluator.Init, that is done on the target
399                         return ReadEvalPrintLoop ();
400                 }
401         
402                 protected override void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
403                 {
404                         // Do not about our program
405                         a.Cancel = true;
406         
407                         interrupt_stream.WriteByte (0);
408                         int c = interrupt_stream.ReadByte ();
409                         if (c != -1)
410                                 Console.WriteLine ("Execution interrupted");
411                 }
412                         
413         }
414
415         //
416         // Stream helper extension methods
417         //
418         public static class StreamHelper {
419                 static DataConverter converter = DataConverter.LittleEndian;
420                 
421                 public static int GetInt (this Stream stream)
422                 {
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);
427                 }
428                 
429                 public static string GetString (this Stream stream)
430                 {
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);
436                 }
437         
438                 public static void WriteInt (this Stream stream, int n)
439                 {
440                         byte [] bytes = converter.GetBytes (n);
441                         stream.Write (bytes, 0, bytes.Length);
442                 }
443         
444                 public static void WriteString (this Stream stream, string s)
445                 {
446                         stream.WriteInt (s.Length);
447                         byte [] bytes = Encoding.UTF8.GetBytes (s);
448                         stream.Write (bytes, 0, bytes.Length);
449                 }
450         }
451         
452         public enum AgentStatus : byte {
453                 // Received partial input, complete
454                 PARTIAL_INPUT  = 1,
455         
456                 // The result was set, expect the string with the result
457                 RESULT_SET     = 2,
458         
459                 // No result was set, complete
460                 RESULT_NOT_SET = 3,
461         
462                 // Errors and warnings string follows
463                 ERROR          = 4, 
464         }
465         
466         //
467         // This is the agent loaded into the target process when using --attach.
468         //
469         class CSharpAgent
470         {
471                 NetworkStream interrupt_stream;
472                 
473                 public CSharpAgent (String arg)
474                 {
475                         new Thread (new ParameterizedThreadStart (Run)).Start (arg);
476                 }
477
478                 public void InterruptListener ()
479                 {
480                         while (true){
481                                 int b = interrupt_stream.ReadByte();
482                                 if (b == -1)
483                                         return;
484                                 Evaluator.Interrupt ();
485                                 interrupt_stream.WriteByte (0);
486                         }
487                 }
488                 
489                 public void Run (object o)
490                 {
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));
496         
497                         Console.WriteLine ("csharp-agent: started, connecting to localhost:" + port);
498         
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.");
502         
503                         NetworkStream s = client.GetStream ();
504                         interrupt_stream = interrupt_client.GetStream ();
505                         new Thread (InterruptListener).Start ();
506                         
507                         try {
508                                 Evaluator.Init (new string [0]);
509                         } catch {
510                                 // TODO: send a result back.
511                                 Console.WriteLine ("csharp-agent: initialization failed");
512                                 return;
513                         }
514         
515                         try {
516                                 // Add all assemblies loaded later
517                                 AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded;
518         
519                                 // Add all currently loaded assemblies
520                                 foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies ())
521                                         Evaluator.ReferenceAssembly (a);
522         
523                                 RunRepl (s);
524                         } finally {
525                                 AppDomain.CurrentDomain.AssemblyLoad -= AssemblyLoaded;
526                                 client.Close ();
527                                 interrupt_client.Close ();
528                                 Console.WriteLine ("csharp-agent: disconnected.");                      
529                         }
530                 }
531         
532                 static void AssemblyLoaded (object sender, AssemblyLoadEventArgs e)
533                 {
534                         Evaluator.ReferenceAssembly (e.LoadedAssembly);
535                 }
536         
537                 public void RunRepl (NetworkStream s)
538                 {
539                         string input = null;
540
541                         while (!InteractiveBase.QuitRequested) {
542                                 try {
543                                         string error_string;
544                                         StringWriter error_output = new StringWriter ();
545                                         Report.Stderr = error_output;
546                                         
547                                         string line = s.GetString ();
548         
549                                         bool result_set;
550                                         object result;
551         
552                                         if (input == null)
553                                                 input = line;
554                                         else
555                                                 input = input + "\n" + line;
556         
557                                         try {
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);
563                                                 continue;
564                                         }
565                                         
566                                         if (input != null){
567                                                 s.WriteByte ((byte) AgentStatus.PARTIAL_INPUT);
568                                                 continue;
569                                         }
570         
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 ());
576                                         }
577         
578                                         if (result_set){
579                                                 s.WriteByte ((byte) AgentStatus.RESULT_SET);
580                                                 StringWriter sr = new StringWriter ();
581                                                 CSharpShell.PrettyPrint (sr, result);
582                                                 s.WriteString (sr.ToString ());
583                                         } else {
584                                                 s.WriteByte ((byte) AgentStatus.RESULT_NOT_SET);
585                                         }
586                                 } catch (IOException) {
587                                         break;
588                                 } catch (Exception e){
589                                         Console.WriteLine (e);
590                                 }
591                         }
592                 }
593         }
594 }
595         
596 namespace Mono.Management
597 {
598         interface IVirtualMachine {
599                 void LoadAgent (string filename, string args);
600         }
601 }
602
603