CSREPL - REPL for C#

While ChrisAn is busy hosting Python inside of AvPad, I wonder why he isn't hosting C# as well.
 
As a viability test, I hacked out a simple REPL program in ~10 minutes that supports command-line evaluation of C# expressions and statements.
 
Here's the basic usage:
 
> 1 + 2 + 3
6
> DateTime.Now.ToString("T")
4:12:36 PM
 
To support cross-expression variables, I defined two built-in functions, Set and Get:
 
> Set("x", 32)
32
> Get("x")
32
 
And to support invoking arbitrary blocks, there's an Invoke function that evals a void(void) delegate:
 
> Invoke(delegate { for (int i = 0; i < 6; i++) Console.WriteLine(i); })
0
1
2
3
4
5
 
Here's the code - compile it as a console app under Whidbey Beta 2 and party on!
 
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.CodeDom;
using System.CodeDom.Compiler;
 
namespace csrepl {
    class Program {
 
        static string funcprefix = "using System;\r\n"
            + "public delegate void Proc();\r\n"
            + "public class Wrapper { \r\n"
            + "  public static object Set(string name, object value) { \r\n"
            + "    AppDomain.CurrentDomain.SetData(name, value);\r\n"
            + "    return value; \r\n"
            + "  }\r\n"
            + "  public static object Get(string name) { \r\n"
            + "    return AppDomain.CurrentDomain.GetData(name);\r\n"
            + "  }\r\n"
            + "  public static object Invoke(Proc proc) { \r\n"
            + "    proc();\r\n"
            + "    return null; \r\n"
            + "  }\r\n"
            + "  public static object Eval() { return \r\n";
        static string funcsuffix = "; \r\n} }";
 

        static string StringEval(string expr) {
            string program = funcprefix + expr + funcsuffix;
 
            ICodeCompiler compiler = CodeDomProvider.CreateProvider("C#").CreateCompiler();
            CompilerParameters cp = new CompilerParameters();
            cp.GenerateExecutable = false;
            cp.GenerateInMemory = true;
 
            CompilerResults results = compiler.CompileAssemblyFromSource(cp, program);
            if (results.Errors.HasErrors) {
                if (results.Errors[0].ErrorNumber == "CS0029")
                    return StringEval("Invoke(delegate { " + expr + "; })");
                return results.Errors[0].ErrorText;
            }
            else {
                Assembly assm = results.CompiledAssembly;
                Type target = assm.GetType("Wrapper");
                MethodInfo method = target.GetMethod("Eval");
                object result = method.Invoke(null, null);
                return result == null ? null : result.ToString();
            }
        }
 
        static void Main(string[] args) {
 
            while (true ) {
                Console.Write("> ");
                Console.Out.Flush();
                string expr = Console.ReadLine();
                if (expr == null)
                    break;
                try {
                    string result = StringEval(expr);
                    Console.WriteLine(result);
                }
                catch (TargetInvocationException ex) {
                    Console.WriteLine(ex.InnerException.GetType().Name + ": " + ex.InnerException.Message);
                }
                catch (Exception ex) {
                    Console.WriteLine(ex.GetType().Name + ": " + ex.Message);
                }
            }
 
        }
    }
}

Posted May 26 2005, 08:16 PM by don-box

Comments

どっとねっとふぁん blog wrote CSREPL - REPL for C#
on 05-26-2005 11:24 PM
CSREPL - REPL for C#
showens wrote re: CSREPL - REPL for C#
on 05-27-2005 3:54 AM
Nice! Not knowing much about CodeDom, I am a bit baffled by the compiler warning I get:

Warning 1 'System.CodeDom.Compiler.CodeDomProvider.CreateCompiler()' is obsolete: 'Callers should not use the ICodeCompiler interface and should instead use the methods directly on the CodeDomProvider class. Those inheriting from CodeDomProvider must still implement this interface, and should exclude this warning or also obsolete this method.'

"Exclude the warning" meaning filter it out in the project settings??

"Also obsolete this method"?
Rubber Chicken wrote Concise Example of C# Code Generation
on 05-27-2005 9:38 AM
Everyone knows that Don Box is a sharp guy. Blah...blah...blah. But I really like this 75 line program he has posted on his weblog - if this doesn't help you understand how C# generates code, nothing will....
William Stacey wrote re: CSREPL - REPL for C#
on 05-27-2005 10:00 AM
Very cool Don. I made a little update to include multiple c# lines, changed Eval, added Go (ala Sql), and Exit keywords. Code at:
http://spaces.msn.com/members/staceyw/Blog/cns!1pnsZpX0fPvDxLKC6rAAhLsQ!393.entry

Example:

c:\>cscmd
Enter line(s) of C# code, type 'GO' and hit Enter.

> Eval 1+1
> go
2
> for(int i=0; i<5; i++)
> {
> Console.WriteLine("Hello");
> }
> go
Hello
Hello
Hello
Hello
Hello
ComputerZen.com - Scott Hanselman's Weblog wrote NotMuchofAREPL - CSREPL back-ported for .NET 1.1
on 05-27-2005 2:39 PM
Dejan Jelovic wrote re: CSREPL - REPL for C#
on 05-30-2005 1:31 AM
Now all you need to do is convince the C# team to produce a version of the compiler that can access private members through Reflection instead of direct IL access, and you got yourself a nice automated test scripting environment.

I talked to Jim Hugunin and he says that he'll definitely produce a build of IronPython that has access to private members, but I'd prefer a statically-typed language for my automated tests.
William Stacey wrote re: CSREPL - REPL for C#
on 05-30-2005 5:17 PM
"Now all you need to do is convince the C# team to produce a version of the compiler that can access private members through Reflection instead of direct IL access, and you got yourself a nice automated test scripting environment. "

Can't you already do this today? You can call private methods or get/set private fields like below - or are you talking about something else?

private static object GetPrivateField(object o, string fieldName)
{
Type t = o.GetType();
FieldInfo[] fia = t.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
if ( fia == null )
throw new Exception("Not found.");
FieldInfo fi = null;
foreach ( FieldInfo tfi in fia )
{
if ( tfi.Name == fieldName )
fi = tfi;
}
if ( fi == null )
throw new Exception("Not found");
return fi.GetValue(o);
}
Alex wrote re: CSREPL - REPL for C#
on 06-01-2005 3:10 AM
To get this quickly working on 1.1 simply remove the Using for Generics add a using for Microsoft.CSharp and change the declaration of the ICodeCompiler as so:

ICodeCompiler compiler = CodeDomProvider.CreateProvider("C#").CreateCompiler();

becomes

ICodeCompiler compiler = new CSharpCodeProvider().CreateCompiler();

Dejan Jelovic wrote re: CSREPL - REPL for C#
on 06-02-2005 3:29 AM
William:

"Can't you already do this today? You can call private methods or get/set private fields like below - or are you talking about something else?"

Sure you can. But you have to use reflection, which is cumbersome.

In general when doing automated tests and unit tests you want to call private methods and check various private variables all the time:

someObj.CallSomeMethod ();
Debug.Assert (someObj.somePrivateField == someValue);

If you can do this in a statically typed language then life is good. If you have to use reflection then it becomes cumbersome and the errors are not detected at compile time, so you are better off using a dynamically typed language like Python.

(The alternative, which is exposing private data and methods through other methods and properties surrounded by "#if TESTONLY/#endif" is also painful and unnecessary.)

Dejan
William Stacey wrote re: CSREPL - REPL for C#
on 06-03-2005 6:43 AM
"Sure you can. But you have to use reflection, which is cumbersome."

But that is what you asked for:

"Now all you need to do is convince the C# team to produce a version of the compiler that can access private members through Reflection instead of direct IL access"

I just said you could do this already. There is a reason you make things private. If you want access, you still have public, internal, and protected.

Dejan Jelovic wrote re: CSREPL - REPL for C#
on 06-03-2005 3:16 PM
Willaim:

"I just said you could do this already. There is a reason you make things private. If you want access, you still have public, internal, and protected."

What I meant was that this specific version of the compiler would cleanly compile something like:

int x = SomeClass.PrivateMember;

and output code that gets the value of PrivateMember through reflection. That compiler would be really useful for writing unit tests and automated tests.

Dejan
Sriram Krishnan wrote Object test bench rocks!
on 08-01-2005 2:48 PM
You ever get that feeling when you stumble across something truly mind-blowing and you want to blog about...
Ronald Mai wrote re: CSREPL - REPL for C#
on 11-30-2005 7:37 PM
I made some update to your C# repl at:
http://ronaldmatt.bokee.com/3718398.html

Example:
>>> int i = 1;
>>> ++i
2
>>> void Hello() {
... Console.WriteLine("Hello World");
... }
>>> Hello();
Hello World
>>> public class Foo {
... }
>>> Foo foo;
>>> foo = new Foo();
>>> foo
Foo
>>> for (int j = 0; j < 3; ++j) {
... Console.WriteLine(j);
... }
0
1
2
>>> using System.Reflection;
>>> namespace bar {
... }
>>> Assembly.LoadWithPartialName("System.Data");
>>> using System.Data;
» @jongalloway Don Box did a C# … Twitteresque wrote &raquo; @jongalloway Don Box did a C# &#8230; Twitteresque
on 09-10-2008 10:01 PM

Pingback from  » @jongalloway Don Box did a C# … Twitteresque

Add a Comment

(required)  
(optional)
(required)  
Remember Me?