During the last HackWeek, I had a chance to work on a project that we had been discussing in the Mono team: an interactive C# shell.
The idea was simple: create an interactive C# shell by altering the compiler to generate and execute code dynamically as opposed to merely generating static code.
The result is the csharp command. The command provides an read-eval-print loop for entering C# statements and expressions:
csharp> 1; 1 csharp> "Hello, World".IndexOf (","); 5 csharp> Console.WriteLine ("Hello"); Hello
Statements are executed; Expressions are evaluated, and the result displayed. There is support for rendering arrays, IEnumerables and dictionaries specially, consider the following C# 3 declaration:
csharp> new Hashtable () { { "/bin", "executable files" }, { "/etc", "configuration files" } };
You will get this back when rendered:
{{ "/tmp", "Temporary files" }, { "/bin", "executable files" }}
Statements can span multiple lines; In those cases the interactive shell will use a different prompt to indicate that more input is expected, for example:
csharp> 1 + > 2; 3 csharp>
One of the main advantages of this shell is that you can try out your LINQ expressions directly on the shell, for example, the following query works on the result from Directory.GetFiles:
csharp> using System.IO; csharp> var last_week = DateTime.Now - TimeSpan.FromDays (7); csharp> from f in Directory.GetFiles ("/etc") > let fi = new FileInfo (f) > where fi.LastWriteTime < last_week > select f; { "/etc/adjtime", "/etc/asound.state", "/etc/ld.so.cache", "/etc/mtab", "/etc/printcap", "/etc/resolv.conf" }
The LINQ expressions are not limited to working on IEnumerables, you can also use LINQ to XML or LINQ to any database provider supported by Mono by loading the assembly:
csharp> LoadLibrary ("System.Xml.Linq"); csharp> using System.Xml.Linq; csharp> var xml = new XElement("CompilerSources", > from f in Directory.GetFiles ("/cvs/mcs/mcs") > let fi = new FileInfo (f) > orderby fi.Length > select new XElement ("file", new XAttribute ("name", f), new XAttribute ("size", fi.Length))); csharp> xml; <CompilerSources> <file name="/cvs/mcs/mcs/mcs.exe.config" size="395" /> <file name="/cvs/mcs/mcs/gmcs.exe.config" size="464" /> <file name="/cvs/mcs/mcs/OPTIMIZE" size="498" /> <file name="/cvs/mcs/mcs/lambda.todo" size="658" /> <file name="/cvs/mcs/mcs/smcs.exe.sources" size="726" /> [...] </CompilerSources>
A differences between csharp and the C# language is that I felt that for interactive use, it would be important to change the type of a variable, so the following is allowed:
csharp> var a = 1; csharp> a; 1 csharp> a.GetType (); System.Int32 csharp> var a = "Foo"; csharp> a; "Foo" csharp> a.GetType (); System.String csharp>
To load code interactive I added two methods: LoadAssembly and LoadPackage.
LoadAssembly is the equivalent of passing the -r command line argument to csharp or mcs, this example shows System.Xml.Linq in use:
csharp> LoadAssembly ("System.Xml.Linq"); csharp> using System.Xml.Linq; csharp> XDocument doc = new XDocument( > new XDeclaration("1.0", "utf-8", "yes"), > new XComment("Sample RSS Feed"), > new XElement("rss", > new XAttribute("version", "2.0"), > new XElement ("channel", > new XElement("title", "RSS Channel Title"), > new XElement("description", "RSS Channel Description."), > new XElement("link", "https://tirania.org"), > new XElement("item", > new XElement("title", "First article title"), > new XElement("description", "First Article Description"), > new XElement("pubDate", DateTime.Now.ToUniversalTime()), > new XElement("guid", Guid.NewGuid())), > new XElement("item", > new XElement("title", "Second article title"), > new XElement("description", "Second Article Description"), > new XElement("pubDate", DateTime.Now.ToUniversalTime()), > new XElement("guid", Guid.NewGuid())) > ) > ) > );
The variable doc is then rendered:
csharp> doc; <?xml version="1.0" encoding="utf-16" standalone="yes"?> <!--Sample RSS Feed--> <rss version="2.0"> <channel> <title>RSS Channel Title</title> <description>RSS Channel Description.</description> <link>https://tirania.org</link> <item> <title>First article title</title> <description>First Article Description</description> <pubDate>9/8/2008 5:13:34 PM</pubDate> <guid>bc6825ab-f1ab-4347-ad6e-3cf076011379</guid> </item> <item> <title>Second article title</title> <description>Second Article Description</description> <pubDate>9/8/2008 5:13:34 PM</pubDate> <guid>a474b2bb-deba-4973-9581-762857b24b53</guid> </item> </channel> </rss>
LoadPackage is the equivalent of invoking the compiler with the -pkg: flag. This is a Mono-ism that integrates Mono libraries with Unix's pkg-config. Packages allow definitions and multiple assemblies to be loaded in a single call, for example, this loads the Gtk# 2.0 package:
csharp> LoadPackage ("gtk-sharp-2.0"); csharp> using Gtk; csharp> Application.Init (); csharp> var b = new Button ("Hello Interactive Shell"); csharp> var w = new Window ("So cute!"); csharp> b.Clicked += delegate { Application.Quit (); }; csharp> w.Add (b); csharp> w.ShowAll (); csharp> Application.Run ();
This shell has been incredibly useful to debug things in the last few weeks, as it avoids the tedious typing to try out APIs and to see what some function might return. Launch csharp and test things away.
To improve the experience of the command line editing, I wrote a managed readline replacement, it provides history, keyboard navigation and searching.
Also the System, System.Linq, System.Collections, and System.Collections.Generic namespaces are imported by default.
Additionally, the csharp command will load any assemblies and C# scripts that you have in the ~/.config/csharp directory. If you are working constantly say on LINQ, you can put there all of your using and LoadLibrary statements.
The csharp command will be available in Mono 2.2, which is still a few months away, or it can be obtained by compiling Mono from SVN today.
Currently there are a few static methods and properties that you can invoke from the command line, like the "help" property that will display some help, and the "quit" property that terminates the shell as well as things like LoadLibrary and LoadPackage that I have described before. A complete list is available on the CsharpRepl page.
I am interested in hearing which other features should be added to the shell. I think we need some special commands to describe types and objects, like monop.
Additionally, currently values are displayed by using ToString(), but perhaps we need a method Inspect(object) that would show the values of all public fields and properties, like a debugger would.
Which other commands should be added?
Posted on 08 Sep 2008