do not check order sequence if option /order was not used
[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
32 namespace Mono {
33
34         public class Driver {
35                 public static string StartupEvalExpression;
36                 static int? attach;
37                 static string agent;
38                 
39                 static int Main (string [] args)
40                 {
41                         var cmd = new CommandLineParser (Console.Out);
42                         cmd.UnknownOptionHandler += HandleExtraArguments;
43
44                         var settings = cmd.ParseArguments (args);
45                         if (settings == null)
46                                 return 1;
47                         var startup_files = new string [settings.SourceFiles.Count];
48                         int i = 0;
49                         foreach (var source in settings.SourceFiles)
50                                 startup_files [i++] = source.FullPathName;
51                         settings.SourceFiles.Clear ();
52
53                         TextWriter agent_stderr = null;
54                         ReportPrinter printer;
55                         if (agent != null) {
56                                 agent_stderr = new StringWriter ();
57                                 printer = new StreamReportPrinter (agent_stderr);
58                         } else {
59                                 printer = new ConsoleReportPrinter ();
60                         }
61
62                         var eval = new Evaluator (new CompilerContext (settings, printer));
63
64                         eval.InteractiveBaseClass = typeof (InteractiveBaseShell);
65                         eval.DescribeTypeExpressions = true;
66
67                         CSharpShell shell;
68 #if !ON_DOTNET
69                         if (attach.HasValue) {
70                                 shell = new ClientCSharpShell (eval, attach.Value);
71                         } else if (agent != null) {
72                                 new CSharpAgent (eval, agent, agent_stderr).Run (startup_files);
73                                 return 0;
74                         } else
75 #endif
76                         {
77                                 shell = new CSharpShell (eval);
78                         }
79                         return shell.Run (startup_files);
80                 }
81
82                 static int HandleExtraArguments (string [] args, int pos)
83                 {
84                         switch (args [pos]) {
85                         case "-e":
86                                 if (pos + 1 < args.Length) {
87                                         StartupEvalExpression = args[pos + 1];
88                                         return pos + 1;
89                                 }
90                                 break;
91                         case "--attach":
92                                 if (pos + 1 < args.Length) {
93                                         attach = Int32.Parse (args[1]);
94                                         return pos + 1;
95                                 }
96                                 break;
97                         default:
98                                 if (args [pos].StartsWith ("--agent:")) {
99                                         agent = args[pos];
100                                         return pos + 1;
101                                 } else {
102                                         return -1;
103                                 }
104                         }
105                         return -1;
106                 }
107                 
108         }
109
110         public class InteractiveBaseShell : InteractiveBase {
111                 static bool tab_at_start_completes;
112                 
113                 static InteractiveBaseShell ()
114                 {
115                         tab_at_start_completes = false;
116                 }
117
118                 internal static Mono.Terminal.LineEditor Editor;
119                 
120                 public static bool TabAtStartCompletes {
121                         get {
122                                 return tab_at_start_completes;
123                         }
124
125                         set {
126                                 tab_at_start_completes = value;
127                                 if (Editor != null)
128                                         Editor.TabAtStartCompletes = value;
129                         }
130                 }
131
132                 public static new string help {
133                         get {
134                                 return InteractiveBase.help +
135                                         "  TabAtStartCompletes      - Whether tab will complete even on empty lines\n";
136                         }
137                 }
138         }
139         
140         public class CSharpShell {
141                 static bool isatty = true, is_unix = false;
142                 protected string [] startup_files;
143                 
144                 Mono.Terminal.LineEditor editor;
145                 bool dumb;
146                 readonly Evaluator evaluator;
147
148                 public CSharpShell (Evaluator evaluator)
149                 {
150                         this.evaluator = evaluator;
151                 }
152
153                 protected virtual void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
154                 {
155                         // Do not about our program
156                         a.Cancel = true;
157
158                         evaluator.Interrupt ();
159                 }
160                 
161                 void SetupConsole ()
162                 {
163                         if (is_unix){
164                                 string term = Environment.GetEnvironmentVariable ("TERM");
165                                 dumb = term == "dumb" || term == null || isatty == false;
166                         } else
167                                 dumb = false;
168                         
169                         editor = new Mono.Terminal.LineEditor ("csharp", 300);
170                         InteractiveBaseShell.Editor = editor;
171
172                         editor.AutoCompleteEvent += delegate (string s, int pos){
173                                 string prefix = null;
174
175                                 string complete = s.Substring (0, pos);
176                                 
177                                 string [] completions = evaluator.GetCompletions (complete, out prefix);
178                                 
179                                 return new Mono.Terminal.LineEditor.Completion (prefix, completions);
180                         };
181                         
182 #if false
183                         //
184                         // This is a sample of how completions sould be implemented.
185                         //
186                         editor.AutoCompleteEvent += delegate (string s, int pos){
187
188                                 // Single match: "Substring": Sub-string
189                                 if (s.EndsWith ("Sub")){
190                                         return new string [] { "string" };
191                                 }
192
193                                 // Multiple matches: "ToString" and "ToLower"
194                                 if (s.EndsWith ("T")){
195                                         return new string [] { "ToString", "ToLower" };
196                                 }
197                                 return null;
198                         };
199 #endif
200                         
201                         Console.CancelKeyPress += ConsoleInterrupt;
202                 }
203
204                 string GetLine (bool primary)
205                 {
206                         string prompt = primary ? InteractiveBase.Prompt : InteractiveBase.ContinuationPrompt;
207
208                         if (dumb){
209                                 if (isatty)
210                                         Console.Write (prompt);
211
212                                 return Console.ReadLine ();
213                         } else {
214                                 return editor.Edit (prompt, "");
215                         }
216                 }
217
218                 delegate string ReadLiner (bool primary);
219
220                 void InitializeUsing ()
221                 {
222                         Evaluate ("using System; using System.Linq; using System.Collections.Generic; using System.Collections;");
223                 }
224
225                 void InitTerminal (bool show_banner)
226                 {
227                         int p = (int) Environment.OSVersion.Platform;
228                         is_unix = (p == 4) || (p == 128);
229
230 #if NET_4_5
231                         isatty = !Console.IsInputRedirected && !Console.IsOutputRedirected;
232 #else
233                         isatty = true;
234 #endif
235
236                         // Work around, since Console is not accounting for
237                         // cursor position when writing to Stderr.  It also
238                         // has the undesirable side effect of making
239                         // errors plain, with no coloring.
240 //                      Report.Stderr = Console.Out;
241                         SetupConsole ();
242
243                         if (isatty && show_banner)
244                                 Console.WriteLine ("Mono C# Shell, type \"help;\" for help\n\nEnter statements below.");
245
246                 }
247
248                 void ExecuteSources (IEnumerable<string> sources, bool ignore_errors)
249                 {
250                         foreach (string file in sources){
251                                 try {
252                                         try {
253                                                 bool first = true;
254                         
255                                                 using (System.IO.StreamReader r = System.IO.File.OpenText (file)){
256                                                         ReadEvalPrintLoopWith (p => {
257                                                                 var line = r.ReadLine ();
258                                                                 if (first){
259                                                                         if (line.StartsWith ("#!"))
260                                                                                 line = r.ReadLine ();
261                                                                         first = false;
262                                                                 }
263                                                                 return line;
264                                                         });
265                                                 }
266                                         } catch (FileNotFoundException){
267                                                 Console.Error.WriteLine ("cs2001: Source file `{0}' not found", file);
268                                                 return;
269                                         }
270                                 } catch {
271                                         if (!ignore_errors)
272                                                 throw;
273                                 }
274                         }
275                 }
276                 
277                 protected virtual void LoadStartupFiles ()
278                 {
279                         string dir = Path.Combine (
280                                 Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
281                                 "csharp");
282                         if (!Directory.Exists (dir))
283                                 return;
284
285                         List<string> sources = new List<string> ();
286                         List<string> libraries = new List<string> ();
287                         
288                         foreach (string file in System.IO.Directory.GetFiles (dir)){
289                                 string l = file.ToLower ();
290                                 
291                                 if (l.EndsWith (".cs"))
292                                         sources.Add (file);
293                                 else if (l.EndsWith (".dll"))
294                                         libraries.Add (file);
295                         }
296
297                         foreach (string file in libraries)
298                                 evaluator.LoadAssembly (file);
299
300                         ExecuteSources (sources, true);
301                 }
302
303                 void ReadEvalPrintLoopWith (ReadLiner readline)
304                 {
305                         string expr = null;
306                         while (!InteractiveBase.QuitRequested){
307                                 string input = readline (expr == null);
308                                 if (input == null)
309                                         return;
310
311                                 if (input == "")
312                                         continue;
313
314                                 expr = expr == null ? input : expr + "\n" + input;
315                                 
316                                 expr = Evaluate (expr);
317                         } 
318                 }
319
320                 public int ReadEvalPrintLoop ()
321                 {
322                         if (startup_files != null && startup_files.Length == 0)
323                                 InitTerminal (startup_files.Length == 0 && Driver.StartupEvalExpression == null);
324
325                         InitializeUsing ();
326
327                         LoadStartupFiles ();
328
329                         if (startup_files != null && startup_files.Length != 0)
330                                 ExecuteSources (startup_files, false);
331                         else if (Driver.StartupEvalExpression != null){
332                                 ReadEvalPrintLoopWith (p => {
333                                         var ret = Driver.StartupEvalExpression;
334                                         Driver.StartupEvalExpression = null;
335                                         return ret;
336                                         });
337                         } else
338                                 ReadEvalPrintLoopWith (GetLine);
339
340                         return 0;
341                 }
342
343                 protected virtual string Evaluate (string input)
344                 {
345                         bool result_set;
346                         object result;
347
348                         try {
349                                 input = evaluator.Evaluate (input, out result, out result_set);
350
351                                 if (result_set){
352                                         PrettyPrint (Console.Out, result);
353                                         Console.WriteLine ();
354                                 }
355                         } catch (Exception e){
356                                 Console.WriteLine (e);
357                                 return null;
358                         }
359                         
360                         return input;
361                 }
362
363                 static void p (TextWriter output, string s)
364                 {
365                         output.Write (s);
366                 }
367
368                 static string EscapeString (string s)
369                 {
370                         return s.Replace ("\"", "\\\"");
371                 }
372                 
373                 static void EscapeChar (TextWriter output, char c)
374                 {
375                         if (c == '\''){
376                                 output.Write ("'\\''");
377                                 return;
378                         }
379                         if (c > 32){
380                                 output.Write ("'{0}'", c);
381                                 return;
382                         }
383                         switch (c){
384                         case '\a':
385                                 output.Write ("'\\a'");
386                                 break;
387
388                         case '\b':
389                                 output.Write ("'\\b'");
390                                 break;
391                                 
392                         case '\n':
393                                 output.Write ("'\\n'");
394                                 break;
395                                 
396                         case '\v':
397                                 output.Write ("'\\v'");
398                                 break;
399                                 
400                         case '\r':
401                                 output.Write ("'\\r'");
402                                 break;
403                                 
404                         case '\f':
405                                 output.Write ("'\\f'");
406                                 break;
407                                 
408                         case '\t':
409                                 output.Write ("'\\t");
410                                 break;
411
412                         default:
413                                 output.Write ("'\\x{0:x}", (int) c);
414                                 break;
415                         }
416                 }
417
418                 // Some types (System.Json.JsonPrimitive) implement
419                 // IEnumerator and yet, throw an exception when we
420                 // try to use them, helper function to check for that
421                 // condition
422                 static internal bool WorksAsEnumerable (object obj)
423                 {
424                         IEnumerable enumerable = obj as IEnumerable;
425                         if (enumerable != null){
426                                 try {
427                                         enumerable.GetEnumerator ();
428                                         return true;
429                                 } catch {
430                                         // nothing, we return false below
431                                 }
432                         }
433                         return false;
434                 }
435                 
436                 internal static void PrettyPrint (TextWriter output, object result)
437                 {
438                         if (result == null){
439                                 p (output, "null");
440                                 return;
441                         }
442                         
443                         if (result is Array){
444                                 Array a = (Array) result;
445                                 
446                                 p (output, "{ ");
447                                 int top = a.GetUpperBound (0);
448                                 for (int i = a.GetLowerBound (0); i <= top; i++){
449                                         PrettyPrint (output, a.GetValue (i));
450                                         if (i != top)
451                                                 p (output, ", ");
452                                 }
453                                 p (output, " }");
454                         } else if (result is bool){
455                                 if ((bool) result)
456                                         p (output, "true");
457                                 else
458                                         p (output, "false");
459                         } else if (result is string){
460                                 p (output, String.Format ("\"{0}\"", EscapeString ((string)result)));
461                         } else if (result is IDictionary){
462                                 IDictionary dict = (IDictionary) result;
463                                 int top = dict.Count, count = 0;
464                                 
465                                 p (output, "{");
466                                 foreach (DictionaryEntry entry in dict){
467                                         count++;
468                                         p (output, "{ ");
469                                         PrettyPrint (output, entry.Key);
470                                         p (output, ", ");
471                                         PrettyPrint (output, entry.Value);
472                                         if (count != top)
473                                                 p (output, " }, ");
474                                         else
475                                                 p (output, " }");
476                                 }
477                                 p (output, "}");
478                         } else if (WorksAsEnumerable (result)) {
479                                 int i = 0;
480                                 p (output, "{ ");
481                                 foreach (object item in (IEnumerable) result) {
482                                         if (i++ != 0)
483                                                 p (output, ", ");
484
485                                         PrettyPrint (output, item);
486                                 }
487                                 p (output, " }");
488                         } else if (result is char) {
489                                 EscapeChar (output, (char) result);
490                         } else {
491                                 p (output, result.ToString ());
492                         }
493                 }
494
495                 public virtual int Run (string [] startup_files)
496                 {
497                         this.startup_files = startup_files;
498                         return ReadEvalPrintLoop ();
499                 }
500                 
501         }
502
503 #if !ON_DOTNET
504         //
505         // A shell connected to a CSharpAgent running in a remote process.
506         //  - maybe add 'class_name' and 'method_name' arguments to LoadAgent.
507         //  - Support Gtk and Winforms main loops if detected, this should
508         //    probably be done as a separate agent in a separate place.
509         //
510         class ClientCSharpShell : CSharpShell {
511                 NetworkStream ns, interrupt_stream;
512                 
513                 public ClientCSharpShell (Evaluator evaluator, int pid)
514                         : base (evaluator)
515                 {
516                         // Create a server socket we listen on whose address is passed to the agent
517                         TcpListener listener = new TcpListener (new IPEndPoint (IPAddress.Loopback, 0));
518                         listener.Start ();
519                         TcpListener interrupt_listener = new TcpListener (new IPEndPoint (IPAddress.Loopback, 0));
520                         interrupt_listener.Start ();
521         
522                         string agent_assembly = typeof (ClientCSharpShell).Assembly.Location;
523                         string agent_arg = String.Format ("--agent:{0}:{1}" ,
524                                                           ((IPEndPoint)listener.Server.LocalEndPoint).Port,
525                                                           ((IPEndPoint)interrupt_listener.Server.LocalEndPoint).Port);
526         
527                         var vm = new Attach.VirtualMachine (pid);
528                         vm.Attach (agent_assembly, agent_arg);
529         
530                         /* Wait for the client to connect */
531                         TcpClient client = listener.AcceptTcpClient ();
532                         ns = client.GetStream ();
533                         TcpClient interrupt_client = interrupt_listener.AcceptTcpClient ();
534                         interrupt_stream = interrupt_client.GetStream ();
535         
536                         Console.WriteLine ("Connected.");
537                 }
538         
539                 //
540                 // A remote version of Evaluate
541                 //
542                 protected override string Evaluate (string input)
543                 {
544                         ns.WriteString (input);
545                         while (true) {
546                                 AgentStatus s = (AgentStatus) ns.ReadByte ();
547         
548                                 switch (s){
549                                 case AgentStatus.PARTIAL_INPUT:
550                                         return input;
551         
552                                 case AgentStatus.ERROR:
553                                         string err = ns.GetString ();
554                                         Console.Error.WriteLine (err);
555                                         break;
556         
557                                 case AgentStatus.RESULT_NOT_SET:
558                                         return null;
559         
560                                 case AgentStatus.RESULT_SET:
561                                         string res = ns.GetString ();
562                                         Console.WriteLine (res);
563                                         return null;
564                                 }
565                         }
566                 }
567                 
568                 public override int Run (string [] startup_files)
569                 {
570                         // The difference is that we do not call Evaluator.Init, that is done on the target
571                         this.startup_files = startup_files;
572                         return ReadEvalPrintLoop ();
573                 }
574         
575                 protected override void ConsoleInterrupt (object sender, ConsoleCancelEventArgs a)
576                 {
577                         // Do not about our program
578                         a.Cancel = true;
579         
580                         interrupt_stream.WriteByte (0);
581                         int c = interrupt_stream.ReadByte ();
582                         if (c != -1)
583                                 Console.WriteLine ("Execution interrupted");
584                 }
585                         
586         }
587
588         //
589         // Stream helper extension methods
590         //
591         public static class StreamHelper {
592                 static DataConverter converter = DataConverter.LittleEndian;
593                 
594                 public static int GetInt (this Stream stream)
595                 {
596                         byte [] b = new byte [4];
597                         if (stream.Read (b, 0, 4) != 4)
598                                 throw new IOException ("End reached");
599                         return converter.GetInt32 (b, 0);
600                 }
601                 
602                 public static string GetString (this Stream stream)
603                 {
604                         int len = stream.GetInt ();
605                         byte [] b = new byte [len];
606                         if (stream.Read (b, 0, len) != len)
607                                 throw new IOException ("End reached");
608                         return Encoding.UTF8.GetString (b);
609                 }
610         
611                 public static void WriteInt (this Stream stream, int n)
612                 {
613                         byte [] bytes = converter.GetBytes (n);
614                         stream.Write (bytes, 0, bytes.Length);
615                 }
616         
617                 public static void WriteString (this Stream stream, string s)
618                 {
619                         stream.WriteInt (s.Length);
620                         byte [] bytes = Encoding.UTF8.GetBytes (s);
621                         stream.Write (bytes, 0, bytes.Length);
622                 }
623         }
624         
625         public enum AgentStatus : byte {
626                 // Received partial input, complete
627                 PARTIAL_INPUT  = 1,
628         
629                 // The result was set, expect the string with the result
630                 RESULT_SET     = 2,
631         
632                 // No result was set, complete
633                 RESULT_NOT_SET = 3,
634         
635                 // Errors and warnings string follows
636                 ERROR          = 4, 
637         }
638         
639         //
640         // This is the agent loaded into the target process when using --attach.
641         //
642         class CSharpAgent
643         {
644                 NetworkStream interrupt_stream;
645                 readonly Evaluator evaluator;
646                 TextWriter stderr;
647                 
648                 public CSharpAgent (Evaluator evaluator, String arg, TextWriter stderr)
649                 {
650                         this.evaluator = evaluator;
651                         this.stderr = stderr;
652                         new Thread (new ParameterizedThreadStart (Run)).Start (arg);
653                 }
654
655                 public void InterruptListener ()
656                 {
657                         while (true){
658                                 int b = interrupt_stream.ReadByte();
659                                 if (b == -1)
660                                         return;
661                                 evaluator.Interrupt ();
662                                 interrupt_stream.WriteByte (0);
663                         }
664                 }
665                 
666                 public void Run (object o)
667                 {
668                         string arg = (string)o;
669                         string ports = arg.Substring (8);
670                         int sp = ports.IndexOf (':');
671                         int port = Int32.Parse (ports.Substring (0, sp));
672                         int interrupt_port = Int32.Parse (ports.Substring (sp+1));
673         
674                         Console.WriteLine ("csharp-agent: started, connecting to localhost:" + port);
675         
676                         TcpClient client = new TcpClient ("127.0.0.1", port);
677                         TcpClient interrupt_client = new TcpClient ("127.0.0.1", interrupt_port);
678                         Console.WriteLine ("csharp-agent: connected.");
679         
680                         NetworkStream s = client.GetStream ();
681                         interrupt_stream = interrupt_client.GetStream ();
682                         new Thread (InterruptListener).Start ();
683
684                         try {
685                                 // Add all assemblies loaded later
686                                 AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded;
687         
688                                 // Add all currently loaded assemblies
689                                 foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies ()) {
690                                         // Some assemblies seem to be already loaded, and loading them again causes 'defined multiple times' errors
691                                         if (a.GetName ().Name != "mscorlib" && a.GetName ().Name != "System.Core" && a.GetName ().Name != "System")
692                                                 evaluator.ReferenceAssembly (a);
693                                 }
694         
695                                 RunRepl (s);
696                         } finally {
697                                 AppDomain.CurrentDomain.AssemblyLoad -= AssemblyLoaded;
698                                 client.Close ();
699                                 interrupt_client.Close ();
700                                 Console.WriteLine ("csharp-agent: disconnected.");                      
701                         }
702                 }
703         
704                 void AssemblyLoaded (object sender, AssemblyLoadEventArgs e)
705                 {
706                         evaluator.ReferenceAssembly (e.LoadedAssembly);
707                 }
708         
709                 public void RunRepl (NetworkStream s)
710                 {
711                         string input = null;
712
713                         while (!InteractiveBase.QuitRequested) {
714                                 try {
715                                         string error_string;
716                                         StringWriter error_output = (StringWriter)stderr;
717
718                                         string line = s.GetString ();
719         
720                                         bool result_set;
721                                         object result;
722         
723                                         if (input == null)
724                                                 input = line;
725                                         else
726                                                 input = input + "\n" + line;
727         
728                                         try {
729                                                 input = evaluator.Evaluate (input, out result, out result_set);
730                                         } catch (Exception e) {
731                                                 s.WriteByte ((byte) AgentStatus.ERROR);
732                                                 s.WriteString (e.ToString ());
733                                                 s.WriteByte ((byte) AgentStatus.RESULT_NOT_SET);
734                                                 continue;
735                                         }
736                                         
737                                         if (input != null){
738                                                 s.WriteByte ((byte) AgentStatus.PARTIAL_INPUT);
739                                                 continue;
740                                         }
741         
742                                         // Send warnings and errors back
743                                         error_string = error_output.ToString ();
744                                         if (error_string.Length != 0){
745                                                 s.WriteByte ((byte) AgentStatus.ERROR);
746                                                 s.WriteString (error_output.ToString ());
747                                                 error_output.GetStringBuilder ().Clear ();
748                                         }
749         
750                                         if (result_set){
751                                                 s.WriteByte ((byte) AgentStatus.RESULT_SET);
752                                                 StringWriter sr = new StringWriter ();
753                                                 CSharpShell.PrettyPrint (sr, result);
754                                                 s.WriteString (sr.ToString ());
755                                         } else {
756                                                 s.WriteByte ((byte) AgentStatus.RESULT_NOT_SET);
757                                         }
758                                 } catch (IOException) {
759                                         break;
760                                 } catch (Exception e){
761                                         Console.WriteLine (e);
762                                 }
763                         }
764                 }
765         }
766
767         public class UnixUtils {
768                 [System.Runtime.InteropServices.DllImport ("libc", EntryPoint="isatty")]
769                 extern static int _isatty (int fd);
770                         
771                 public static bool isatty (int fd)
772                 {
773                         try {
774                                 return _isatty (fd) == 1;
775                         } catch {
776                                 return false;
777                         }
778                 }
779         }
780 #endif
781 }
782         
783 namespace Mono.Management
784 {
785         interface IVirtualMachine {
786                 void LoadAgent (string filename, string args);
787         }
788 }
789