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
;
36 static int Main (string [] args
)
39 if (args
.Length
> 0 && args
[0] == "--attach") {
40 new ClientCSharpShell (Int32
.Parse (args
[1])).Run (null);
44 if (args
.Length
> 0 && args
[0].StartsWith ("--agent:")) {
45 new CSharpAgent (args
[0]);
52 static int Startup (string[] args
)
54 string[] startup_files
;
56 startup_files
= Evaluator
.InitAndGetStartupFiles (args
);
57 Evaluator
.DescribeTypeExpressions
= true;
58 Evaluator
.SetInteractiveBaseClass (typeof (InteractiveBaseShell
));
63 return new CSharpShell ().Run (startup_files
);
67 public class InteractiveBaseShell
: InteractiveBase
{
68 static bool tab_at_start_completes
;
70 static InteractiveBaseShell ()
72 tab_at_start_completes
= false;
75 internal static Mono
.Terminal
.LineEditor Editor
;
77 public static bool TabAtStartCompletes
{
79 return tab_at_start_completes
;
83 tab_at_start_completes
= value;
85 Editor
.TabAtStartCompletes
= value;
89 public static new string help
{
91 return InteractiveBase
.help
+
92 " TabAtStartCompletes - Whether tab will complete even on emtpy lines\n";
97 public class CSharpShell
{
98 static bool isatty
= true, is_unix
= false;
99 string [] startup_files
;
101 Mono
.Terminal
.LineEditor editor
;
104 protected virtual void ConsoleInterrupt (object sender
, ConsoleCancelEventArgs a
)
106 // Do not about our program
109 Mono
.CSharp
.Evaluator
.Interrupt ();
115 string term
= Environment
.GetEnvironmentVariable ("TERM");
116 dumb
= term
== "dumb" || term
== null || isatty
== false;
120 editor
= new Mono
.Terminal
.LineEditor ("csharp", 300);
121 InteractiveBaseShell
.Editor
= editor
;
123 editor
.AutoCompleteEvent
+= delegate (string s
, int pos
){
124 string prefix
= null;
126 string complete
= s
.Substring (0, pos
);
128 string [] completions
= Evaluator
.GetCompletions (complete
, out prefix
);
130 return new Mono
.Terminal
.LineEditor
.Completion (prefix
, completions
);
135 // This is a sample of how completions sould be implemented.
137 editor
.AutoCompleteEvent
+= delegate (string s
, int pos
){
139 // Single match: "Substring": Sub-string
140 if (s
.EndsWith ("Sub")){
141 return new string [] { "string" }
;
144 // Multiple matches: "ToString" and "ToLower"
145 if (s
.EndsWith ("T")){
146 return new string [] { "ToString", "ToLower" }
;
152 Console
.CancelKeyPress
+= ConsoleInterrupt
;
155 string GetLine (bool primary
)
157 string prompt
= primary
? InteractiveBase
.Prompt
: InteractiveBase
.ContinuationPrompt
;
161 Console
.Write (prompt
);
163 return Console
.ReadLine ();
165 return editor
.Edit (prompt
, "");
169 delegate string ReadLiner (bool primary
);
171 void InitializeUsing ()
173 Evaluate ("using System; using System.Linq; using System.Collections.Generic; using System.Collections;");
182 int p
= (int) Environment
.OSVersion
.Platform
;
183 is_unix
= (p
== 4) || (p
== 128);
186 isatty
= UnixUtils
.isatty (0) && UnixUtils
.isatty (1);
191 // Work around, since Console is not accounting for
192 // cursor position when writing to Stderr. It also
193 // has the undesirable side effect of making
194 // errors plain, with no coloring.
195 // Report.Stderr = Console.Out;
199 Console
.WriteLine ("Mono C# Shell, type \"help;\" for help\n\nEnter statements below.");
203 void ExecuteSources (IEnumerable
<string> sources
, bool ignore_errors
)
205 foreach (string file
in sources
){
208 using (System
.IO
.StreamReader r
= System
.IO
.File
.OpenText (file
)){
209 ReadEvalPrintLoopWith (p
=> r
.ReadLine ());
211 } catch (FileNotFoundException
){
212 Console
.Error
.WriteLine ("cs2001: Source file `{0}' not found", file
);
222 protected virtual void LoadStartupFiles ()
224 string dir
= Path
.Combine (
225 Environment
.GetFolderPath (Environment
.SpecialFolder
.ApplicationData
),
227 if (!Directory
.Exists (dir
))
230 List
<string> sources
= new List
<string> ();
231 List
<string> libraries
= new List
<string> ();
233 foreach (string file
in System
.IO
.Directory
.GetFiles (dir
)){
234 string l
= file
.ToLower ();
236 if (l
.EndsWith (".cs"))
238 else if (l
.EndsWith (".dll"))
239 libraries
.Add (file
);
242 foreach (string file
in libraries
)
243 Evaluator
.LoadAssembly (file
);
245 ExecuteSources (sources
, true);
248 void ReadEvalPrintLoopWith (ReadLiner readline
)
251 while (!InteractiveBase
.QuitRequested
){
252 string input
= readline (expr
== null);
259 expr
= expr
== null ? input
: expr
+ "\n" + input
;
261 expr
= Evaluate (expr
);
265 public int ReadEvalPrintLoop ()
267 if (startup_files
!= null && startup_files
.Length
== 0)
275 // Interactive or startup files provided?
277 if (startup_files
.Length
!= 0)
278 ExecuteSources (startup_files
, false);
280 ReadEvalPrintLoopWith (GetLine
);
285 protected virtual string Evaluate (string input
)
291 input
= Evaluator
.Evaluate (input
, out result
, out result_set
);
294 PrettyPrint (Console
.Out
, result
);
295 Console
.WriteLine ();
297 } catch (Exception e
){
298 Console
.WriteLine (e
);
305 static void p (TextWriter output
, string s
)
310 static string EscapeString (string s
)
312 return s
.Replace ("\"", "\\\"");
315 static void EscapeChar (TextWriter output
, char c
)
318 output
.Write ("'\\''");
322 output
.Write ("'{0}'", c
);
327 output
.Write ("'\\a'");
331 output
.Write ("'\\b'");
335 output
.Write ("'\\n'");
339 output
.Write ("'\\v'");
343 output
.Write ("'\\r'");
347 output
.Write ("'\\f'");
351 output
.Write ("'\\t");
355 output
.Write ("'\\x{0:x}", (int) c
);
360 internal static void PrettyPrint (TextWriter output
, object result
)
367 if (result
is Array
){
368 Array a
= (Array
) result
;
371 int top
= a
.GetUpperBound (0);
372 for (int i
= a
.GetLowerBound (0); i
<= top
; i
++){
373 PrettyPrint (output
, a
.GetValue (i
));
378 } else if (result
is bool){
383 } else if (result
is string){
384 p (output
, String
.Format ("\"{0}\"", EscapeString ((string)result
)));
385 } else if (result
is IDictionary
){
386 IDictionary dict
= (IDictionary
) result
;
387 int top
= dict
.Count
, count
= 0;
390 foreach (DictionaryEntry entry
in dict
){
393 PrettyPrint (output
, entry
.Key
);
395 PrettyPrint (output
, entry
.Value
);
402 } else if (result
is IEnumerable
) {
405 foreach (object item
in (IEnumerable
) result
) {
409 PrettyPrint (output
, item
);
412 } else if (result
is char) {
413 EscapeChar (output
, (char) result
);
415 p (output
, result
.ToString ());
419 public CSharpShell ()
423 public virtual int Run (string [] startup_files
)
425 this.startup_files
= startup_files
;
426 return ReadEvalPrintLoop ();
433 // A shell connected to a CSharpAgent running in a remote process.
434 // - maybe add 'class_name' and 'method_name' arguments to LoadAgent.
435 // - Support Gtk and Winforms main loops if detected, this should
436 // probably be done as a separate agent in a separate place.
438 class ClientCSharpShell
: CSharpShell
{
439 NetworkStream ns
, interrupt_stream
;
441 public ClientCSharpShell (int pid
)
443 // Create a server socket we listen on whose address is passed to the agent
444 TcpListener listener
= new TcpListener (new IPEndPoint (IPAddress
.Loopback
, 0));
446 TcpListener interrupt_listener
= new TcpListener (new IPEndPoint (IPAddress
.Loopback
, 0));
447 interrupt_listener
.Start ();
449 string agent_assembly
= typeof (ClientCSharpShell
).Assembly
.Location
;
450 string agent_arg
= String
.Format ("--agent:{0}:{1}" ,
451 ((IPEndPoint
)listener
.Server
.LocalEndPoint
).Port
,
452 ((IPEndPoint
)interrupt_listener
.Server
.LocalEndPoint
).Port
);
454 var vm
= new Attach
.VirtualMachine (pid
);
455 vm
.Attach (agent_assembly
, agent_arg
);
457 /* Wait for the client to connect */
458 TcpClient client
= listener
.AcceptTcpClient ();
459 ns
= client
.GetStream ();
460 TcpClient interrupt_client
= interrupt_listener
.AcceptTcpClient ();
461 interrupt_stream
= interrupt_client
.GetStream ();
463 Console
.WriteLine ("Connected.");
467 // A remote version of Evaluate
469 protected override string Evaluate (string input
)
471 ns
.WriteString (input
);
473 AgentStatus s
= (AgentStatus
) ns
.ReadByte ();
476 case AgentStatus
.PARTIAL_INPUT
:
479 case AgentStatus
.ERROR
:
480 string err
= ns
.GetString ();
481 Console
.Error
.WriteLine (err
);
484 case AgentStatus
.RESULT_NOT_SET
:
487 case AgentStatus
.RESULT_SET
:
488 string res
= ns
.GetString ();
489 Console
.WriteLine (res
);
495 public override int Run (string [] startup_files
)
497 // The difference is that we do not call Evaluator.Init, that is done on the target
498 return ReadEvalPrintLoop ();
501 protected override void ConsoleInterrupt (object sender
, ConsoleCancelEventArgs a
)
503 // Do not about our program
506 interrupt_stream
.WriteByte (0);
507 int c
= interrupt_stream
.ReadByte ();
509 Console
.WriteLine ("Execution interrupted");
515 // Stream helper extension methods
517 public static class StreamHelper
{
518 static DataConverter converter
= DataConverter
.LittleEndian
;
520 public static int GetInt (this Stream stream
)
522 byte [] b
= new byte [4];
523 if (stream
.Read (b
, 0, 4) != 4)
524 throw new IOException ("End reached");
525 return converter
.GetInt32 (b
, 0);
528 public static string GetString (this Stream stream
)
530 int len
= stream
.GetInt ();
531 byte [] b
= new byte [len
];
532 if (stream
.Read (b
, 0, len
) != len
)
533 throw new IOException ("End reached");
534 return Encoding
.UTF8
.GetString (b
);
537 public static void WriteInt (this Stream stream
, int n
)
539 byte [] bytes
= converter
.GetBytes (n
);
540 stream
.Write (bytes
, 0, bytes
.Length
);
543 public static void WriteString (this Stream stream
, string s
)
545 stream
.WriteInt (s
.Length
);
546 byte [] bytes
= Encoding
.UTF8
.GetBytes (s
);
547 stream
.Write (bytes
, 0, bytes
.Length
);
551 public enum AgentStatus
: byte {
552 // Received partial input, complete
555 // The result was set, expect the string with the result
558 // No result was set, complete
561 // Errors and warnings string follows
566 // This is the agent loaded into the target process when using --attach.
570 NetworkStream interrupt_stream
;
572 public CSharpAgent (String arg
)
574 new Thread (new ParameterizedThreadStart (Run
)).Start (arg
);
577 public void InterruptListener ()
580 int b
= interrupt_stream
.ReadByte();
583 Evaluator
.Interrupt ();
584 interrupt_stream
.WriteByte (0);
588 public void Run (object o
)
590 string arg
= (string)o
;
591 string ports
= arg
.Substring (8);
592 int sp
= ports
.IndexOf (':');
593 int port
= Int32
.Parse (ports
.Substring (0, sp
));
594 int interrupt_port
= Int32
.Parse (ports
.Substring (sp
+1));
596 Console
.WriteLine ("csharp-agent: started, connecting to localhost:" + port
);
598 TcpClient client
= new TcpClient ("127.0.0.1", port
);
599 TcpClient interrupt_client
= new TcpClient ("127.0.0.1", interrupt_port
);
600 Console
.WriteLine ("csharp-agent: connected.");
602 NetworkStream s
= client
.GetStream ();
603 interrupt_stream
= interrupt_client
.GetStream ();
604 new Thread (InterruptListener
).Start ();
607 Evaluator
.Init (new string [0]);
609 // TODO: send a result back.
610 Console
.WriteLine ("csharp-agent: initialization failed");
615 // Add all assemblies loaded later
616 AppDomain
.CurrentDomain
.AssemblyLoad
+= AssemblyLoaded
;
618 // Add all currently loaded assemblies
619 foreach (Assembly a
in AppDomain
.CurrentDomain
.GetAssemblies ())
620 Evaluator
.ReferenceAssembly (a
);
624 AppDomain
.CurrentDomain
.AssemblyLoad
-= AssemblyLoaded
;
626 interrupt_client
.Close ();
627 Console
.WriteLine ("csharp-agent: disconnected.");
631 static void AssemblyLoaded (object sender
, AssemblyLoadEventArgs e
)
633 Evaluator
.ReferenceAssembly (e
.LoadedAssembly
);
636 public void RunRepl (NetworkStream s
)
640 while (!InteractiveBase
.QuitRequested
) {
643 StringWriter error_output
= new StringWriter ();
644 // Report.Stderr = error_output;
646 string line
= s
.GetString ();
654 input
= input
+ "\n" + line
;
657 input
= Evaluator
.Evaluate (input
, out result
, out result_set
);
658 } catch (Exception e
) {
659 s
.WriteByte ((byte) AgentStatus
.ERROR
);
660 s
.WriteString (e
.ToString ());
661 s
.WriteByte ((byte) AgentStatus
.RESULT_NOT_SET
);
666 s
.WriteByte ((byte) AgentStatus
.PARTIAL_INPUT
);
670 // Send warnings and errors back
671 error_string
= error_output
.ToString ();
672 if (error_string
.Length
!= 0){
673 s
.WriteByte ((byte) AgentStatus
.ERROR
);
674 s
.WriteString (error_output
.ToString ());
678 s
.WriteByte ((byte) AgentStatus
.RESULT_SET
);
679 StringWriter sr
= new StringWriter ();
680 CSharpShell
.PrettyPrint (sr
, result
);
681 s
.WriteString (sr
.ToString ());
683 s
.WriteByte ((byte) AgentStatus
.RESULT_NOT_SET
);
685 } catch (IOException
) {
687 } catch (Exception e
){
688 Console
.WriteLine (e
);
694 public class UnixUtils
{
695 [System
.Runtime
.InteropServices
.DllImport ("libc", EntryPoint
="isatty")]
696 extern static int _isatty (int fd
);
698 public static bool isatty (int fd
)
701 return _isatty (fd
) == 1;
710 namespace Mono
.Management
712 interface IVirtualMachine
{
713 void LoadAgent (string filename
, string args
);