2 // repl.cs: Support for using the compiler in interactive mode (read-eval-print loop)
5 // Miguel de Icaza (miguel@gnome.org)
7 // Dual licensed under the terms of the MIT X11 or GNU GPL
9 // Copyright 2001, 2002, 2003 Ximian, Inc (http://www.ximian.com)
10 // Copyright 2004, 2005, 2006, 2007, 2008 Novell, Inc
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.
21 using System
.Globalization
;
22 using System
.Collections
;
23 using System
.Reflection
;
24 using System
.Reflection
.Emit
;
25 using System
.Threading
;
27 using System
.Net
.Sockets
;
28 using System
.Collections
.Generic
;
37 static int Main (string [] args
)
39 if (args
.Length
> 0 && args
[0] == "--attach") {
40 new ClientCSharpShell (Int32
.Parse (args
[1])).Run (null);
42 } else if (args
.Length
> 0 && args
[0].StartsWith ("--agent:")) {
43 new CSharpAgent (args
[0]);
46 string [] startup_files
;
48 startup_files
= Evaluator
.InitAndGetStartupFiles (args
);
49 Evaluator
.DescribeTypeExpressions
= true;
50 Evaluator
.InteractiveBaseClass
= typeof (InteractiveBaseShell
);
55 return new CSharpShell ().Run (startup_files
);
60 public class InteractiveBaseShell
: InteractiveBase
{
61 static bool tab_at_start_completes
;
63 static InteractiveBaseShell ()
65 tab_at_start_completes
= false;
68 internal static Mono
.Terminal
.LineEditor Editor
;
70 public static bool TabAtStartCompletes
{
72 return tab_at_start_completes
;
76 tab_at_start_completes
= value;
78 Editor
.TabAtStartCompletes
= value;
82 public static new string help
{
84 return InteractiveBase
.help
+
85 " TabAtStartCompletes - Whether tab will complete even on emtpy lines\n";
90 public class CSharpShell
{
91 static bool isatty
= true;
92 string [] startup_files
;
94 Mono
.Terminal
.LineEditor editor
;
97 protected virtual void ConsoleInterrupt (object sender
, ConsoleCancelEventArgs a
)
99 // Do not about our program
102 Mono
.CSharp
.Evaluator
.Interrupt ();
107 string term
= Environment
.GetEnvironmentVariable ("TERM");
108 dumb
= term
== "dumb" || term
== null || isatty
== false;
110 editor
= new Mono
.Terminal
.LineEditor ("csharp", 300);
111 InteractiveBaseShell
.Editor
= editor
;
113 editor
.AutoCompleteEvent
+= delegate (string s
, int pos
){
114 string prefix
= null;
116 string complete
= s
.Substring (0, pos
);
118 string [] completions
= Evaluator
.GetCompletions (complete
, out prefix
);
120 return new Mono
.Terminal
.LineEditor
.Completion (prefix
, completions
);
125 // This is a sample of how completions sould be implemented.
127 editor
.AutoCompleteEvent
+= delegate (string s
, int pos
){
129 // Single match: "Substring": Sub-string
130 if (s
.EndsWith ("Sub")){
131 return new string [] { "string" }
;
134 // Multiple matches: "ToString" and "ToLower"
135 if (s
.EndsWith ("T")){
136 return new string [] { "ToString", "ToLower" }
;
142 Console
.CancelKeyPress
+= ConsoleInterrupt
;
145 string GetLine (bool primary
)
147 string prompt
= primary
? InteractiveBase
.Prompt
: InteractiveBase
.ContinuationPrompt
;
151 Console
.Write (prompt
);
153 return Console
.ReadLine ();
155 return editor
.Edit (prompt
, "");
159 delegate string ReadLiner (bool primary
);
161 void InitializeUsing ()
163 Evaluate ("using System; using System.Linq; using System.Collections.Generic; using System.Collections;");
168 isatty
= UnixUtils
.isatty (0) && UnixUtils
.isatty (1);
170 // Work around, since Console is not accounting for
171 // cursor position when writing to Stderr. It also
172 // has the undesirable side effect of making
173 // errors plain, with no coloring.
174 // Report.Stderr = Console.Out;
178 Console
.WriteLine ("Mono C# Shell, type \"help;\" for help\n\nEnter statements below.");
182 void ExecuteSources (IEnumerable
<string> sources
, bool ignore_errors
)
184 foreach (string file
in sources
){
187 using (System
.IO
.StreamReader r
= System
.IO
.File
.OpenText (file
)){
188 ReadEvalPrintLoopWith (p
=> r
.ReadLine ());
190 } catch (FileNotFoundException
){
191 Console
.Error
.WriteLine ("cs2001: Source file `{0}' not found", file
);
201 protected virtual void LoadStartupFiles ()
203 string dir
= Path
.Combine (
204 Environment
.GetFolderPath (Environment
.SpecialFolder
.ApplicationData
),
206 if (!Directory
.Exists (dir
))
209 List
<string> sources
= new List
<string> ();
210 List
<string> libraries
= new List
<string> ();
212 foreach (string file
in System
.IO
.Directory
.GetFiles (dir
)){
213 string l
= file
.ToLower ();
215 if (l
.EndsWith (".cs"))
217 else if (l
.EndsWith (".dll"))
218 libraries
.Add (file
);
221 foreach (string file
in libraries
)
222 Evaluator
.LoadAssembly (file
);
224 ExecuteSources (sources
, true);
227 void ReadEvalPrintLoopWith (ReadLiner readline
)
230 while (!InteractiveBase
.QuitRequested
){
231 string input
= readline (expr
== null);
238 expr
= expr
== null ? input
: expr
+ "\n" + input
;
240 expr
= Evaluate (expr
);
244 public int ReadEvalPrintLoop ()
246 if (startup_files
!= null && startup_files
.Length
== 0)
254 // Interactive or startup files provided?
256 if (startup_files
.Length
!= 0)
257 ExecuteSources (startup_files
, false);
259 ReadEvalPrintLoopWith (GetLine
);
264 protected virtual string Evaluate (string input
)
270 input
= Evaluator
.Evaluate (input
, out result
, out result_set
);
273 PrettyPrint (Console
.Out
, result
);
274 Console
.WriteLine ();
276 } catch (Exception e
){
277 Console
.WriteLine (e
);
284 static void p (TextWriter output
, string s
)
289 static string EscapeString (string s
)
291 return s
.Replace ("\"", "\\\"");
294 static void EscapeChar (TextWriter output
, char c
)
297 output
.Write ("'\\''");
301 output
.Write ("'{0}'", c
);
306 output
.Write ("'\\a'");
310 output
.Write ("'\\b'");
314 output
.Write ("'\\n'");
318 output
.Write ("'\\v'");
322 output
.Write ("'\\r'");
326 output
.Write ("'\\f'");
330 output
.Write ("'\\t");
334 output
.Write ("'\\x{0:x}", (int) c
);
339 internal static void PrettyPrint (TextWriter output
, object result
)
346 if (result
is Array
){
347 Array a
= (Array
) result
;
350 int top
= a
.GetUpperBound (0);
351 for (int i
= a
.GetLowerBound (0); i
<= top
; i
++){
352 PrettyPrint (output
, a
.GetValue (i
));
357 } else if (result
is bool){
362 } else if (result
is string){
363 p (output
, String
.Format ("\"{0}\"", EscapeString ((string)result
)));
364 } else if (result
is IDictionary
){
365 IDictionary dict
= (IDictionary
) result
;
366 int top
= dict
.Count
, count
= 0;
369 foreach (DictionaryEntry entry
in dict
){
372 PrettyPrint (output
, entry
.Key
);
374 PrettyPrint (output
, entry
.Value
);
381 } else if (result
is IEnumerable
) {
384 foreach (object item
in (IEnumerable
) result
) {
388 PrettyPrint (output
, item
);
391 } else if (result
is char) {
392 EscapeChar (output
, (char) result
);
394 p (output
, result
.ToString ());
398 public CSharpShell ()
402 public virtual int Run (string [] startup_files
)
404 this.startup_files
= startup_files
;
405 return ReadEvalPrintLoop ();
411 // A shell connected to a CSharpAgent running in a remote process.
412 // - maybe add 'class_name' and 'method_name' arguments to LoadAgent.
413 // - Support Gtk and Winforms main loops if detected, this should
414 // probably be done as a separate agent in a separate place.
416 class ClientCSharpShell
: CSharpShell
{
417 NetworkStream ns
, interrupt_stream
;
419 public ClientCSharpShell (int pid
)
421 // Create a server socket we listen on whose address is passed to the agent
422 TcpListener listener
= new TcpListener (new IPEndPoint (IPAddress
.Loopback
, 0));
424 TcpListener interrupt_listener
= new TcpListener (new IPEndPoint (IPAddress
.Loopback
, 0));
425 interrupt_listener
.Start ();
427 string agent_assembly
= typeof (ClientCSharpShell
).Assembly
.Location
;
428 string agent_arg
= String
.Format ("--agent:{0}:{1}" ,
429 ((IPEndPoint
)listener
.Server
.LocalEndPoint
).Port
,
430 ((IPEndPoint
)interrupt_listener
.Server
.LocalEndPoint
).Port
);
432 VirtualMachine vm
= new VirtualMachine (pid
);
433 vm
.Attach (agent_assembly
, agent_arg
);
435 /* Wait for the client to connect */
436 TcpClient client
= listener
.AcceptTcpClient ();
437 ns
= client
.GetStream ();
438 TcpClient interrupt_client
= interrupt_listener
.AcceptTcpClient ();
439 interrupt_stream
= interrupt_client
.GetStream ();
441 Console
.WriteLine ("Connected.");
445 // A remote version of Evaluate
447 protected override string Evaluate (string input
)
449 ns
.WriteString (input
);
451 AgentStatus s
= (AgentStatus
) ns
.ReadByte ();
454 case AgentStatus
.PARTIAL_INPUT
:
457 case AgentStatus
.ERROR
:
458 string err
= ns
.GetString ();
459 Console
.Error
.WriteLine (err
);
462 case AgentStatus
.RESULT_NOT_SET
:
465 case AgentStatus
.RESULT_SET
:
466 string res
= ns
.GetString ();
467 Console
.WriteLine (res
);
473 public override int Run (string [] startup_files
)
475 // The difference is that we do not call Evaluator.Init, that is done on the target
476 return ReadEvalPrintLoop ();
479 protected override void ConsoleInterrupt (object sender
, ConsoleCancelEventArgs a
)
481 // Do not about our program
484 interrupt_stream
.WriteByte (0);
485 int c
= interrupt_stream
.ReadByte ();
487 Console
.WriteLine ("Execution interrupted");
493 // Stream helper extension methods
495 public static class StreamHelper
{
496 static DataConverter converter
= DataConverter
.LittleEndian
;
498 public static int GetInt (this Stream stream
)
500 byte [] b
= new byte [4];
501 if (stream
.Read (b
, 0, 4) != 4)
502 throw new IOException ("End reached");
503 return converter
.GetInt32 (b
, 0);
506 public static string GetString (this Stream stream
)
508 int len
= stream
.GetInt ();
509 byte [] b
= new byte [len
];
510 if (stream
.Read (b
, 0, len
) != len
)
511 throw new IOException ("End reached");
512 return Encoding
.UTF8
.GetString (b
);
515 public static void WriteInt (this Stream stream
, int n
)
517 byte [] bytes
= converter
.GetBytes (n
);
518 stream
.Write (bytes
, 0, bytes
.Length
);
521 public static void WriteString (this Stream stream
, string s
)
523 stream
.WriteInt (s
.Length
);
524 byte [] bytes
= Encoding
.UTF8
.GetBytes (s
);
525 stream
.Write (bytes
, 0, bytes
.Length
);
529 public enum AgentStatus
: byte {
530 // Received partial input, complete
533 // The result was set, expect the string with the result
536 // No result was set, complete
539 // Errors and warnings string follows
544 // This is the agent loaded into the target process when using --attach.
548 NetworkStream interrupt_stream
;
550 public CSharpAgent (String arg
)
552 new Thread (new ParameterizedThreadStart (Run
)).Start (arg
);
555 public void InterruptListener ()
558 int b
= interrupt_stream
.ReadByte();
561 Evaluator
.Interrupt ();
562 interrupt_stream
.WriteByte (0);
566 public void Run (object o
)
568 string arg
= (string)o
;
569 string ports
= arg
.Substring (8);
570 int sp
= ports
.IndexOf (':');
571 int port
= Int32
.Parse (ports
.Substring (0, sp
));
572 int interrupt_port
= Int32
.Parse (ports
.Substring (sp
+1));
574 Console
.WriteLine ("csharp-agent: started, connecting to localhost:" + port
);
576 TcpClient client
= new TcpClient ("127.0.0.1", port
);
577 TcpClient interrupt_client
= new TcpClient ("127.0.0.1", interrupt_port
);
578 Console
.WriteLine ("csharp-agent: connected.");
580 NetworkStream s
= client
.GetStream ();
581 interrupt_stream
= interrupt_client
.GetStream ();
582 new Thread (InterruptListener
).Start ();
585 Evaluator
.Init (new string [0]);
587 // TODO: send a result back.
588 Console
.WriteLine ("csharp-agent: initialization failed");
593 // Add all assemblies loaded later
594 AppDomain
.CurrentDomain
.AssemblyLoad
+= AssemblyLoaded
;
596 // Add all currently loaded assemblies
597 foreach (Assembly a
in AppDomain
.CurrentDomain
.GetAssemblies ())
598 Evaluator
.ReferenceAssembly (a
);
602 AppDomain
.CurrentDomain
.AssemblyLoad
-= AssemblyLoaded
;
604 interrupt_client
.Close ();
605 Console
.WriteLine ("csharp-agent: disconnected.");
609 static void AssemblyLoaded (object sender
, AssemblyLoadEventArgs e
)
611 Evaluator
.ReferenceAssembly (e
.LoadedAssembly
);
614 public void RunRepl (NetworkStream s
)
618 while (!InteractiveBase
.QuitRequested
) {
621 StringWriter error_output
= new StringWriter ();
622 // Report.Stderr = error_output;
624 string line
= s
.GetString ();
632 input
= input
+ "\n" + line
;
635 input
= Evaluator
.Evaluate (input
, out result
, out result_set
);
636 } catch (Exception e
) {
637 s
.WriteByte ((byte) AgentStatus
.ERROR
);
638 s
.WriteString (e
.ToString ());
639 s
.WriteByte ((byte) AgentStatus
.RESULT_NOT_SET
);
644 s
.WriteByte ((byte) AgentStatus
.PARTIAL_INPUT
);
648 // Send warnings and errors back
649 error_string
= error_output
.ToString ();
650 if (error_string
.Length
!= 0){
651 s
.WriteByte ((byte) AgentStatus
.ERROR
);
652 s
.WriteString (error_output
.ToString ());
656 s
.WriteByte ((byte) AgentStatus
.RESULT_SET
);
657 StringWriter sr
= new StringWriter ();
658 CSharpShell
.PrettyPrint (sr
, result
);
659 s
.WriteString (sr
.ToString ());
661 s
.WriteByte ((byte) AgentStatus
.RESULT_NOT_SET
);
663 } catch (IOException
) {
665 } catch (Exception e
){
666 Console
.WriteLine (e
);
672 public class UnixUtils
{
673 [System
.Runtime
.InteropServices
.DllImport ("libc", EntryPoint
="isatty")]
674 extern static int _isatty (int fd
);
676 public static bool isatty (int fd
)
679 return _isatty (fd
) == 1;
687 namespace Mono
.Management
689 interface IVirtualMachine
{
690 void LoadAgent (string filename
, string args
);